diff --git a/addons/account/account_invoice_view.xml b/addons/account/account_invoice_view.xml index 1c4d83106ca..7a58b359008 100644 --- a/addons/account/account_invoice_view.xml +++ b/addons/account/account_invoice_view.xml @@ -278,6 +278,7 @@
+
@@ -437,6 +438,7 @@
+
diff --git a/addons/account_voucher/account_voucher_view.xml b/addons/account_voucher/account_voucher_view.xml index 5e26c366778..697d2affcf0 100644 --- a/addons/account_voucher/account_voucher_view.xml +++ b/addons/account_voucher/account_voucher_view.xml @@ -108,6 +108,7 @@
+
diff --git a/addons/account_voucher/voucher_payment_receipt_view.xml b/addons/account_voucher/voucher_payment_receipt_view.xml index 78b76e5c9b7..8e86e44942a 100644 --- a/addons/account_voucher/voucher_payment_receipt_view.xml +++ b/addons/account_voucher/voucher_payment_receipt_view.xml @@ -243,6 +243,7 @@
+
@@ -518,6 +519,7 @@
+
diff --git a/addons/account_voucher/voucher_sales_purchase_view.xml b/addons/account_voucher/voucher_sales_purchase_view.xml index 5bcecfa268e..d3449cf6027 100644 --- a/addons/account_voucher/voucher_sales_purchase_view.xml +++ b/addons/account_voucher/voucher_sales_purchase_view.xml @@ -146,6 +146,7 @@
+
@@ -301,6 +302,7 @@
+
diff --git a/addons/analytic/analytic_view.xml b/addons/analytic/analytic_view.xml index 51f5428002f..bed588015ad 100644 --- a/addons/analytic/analytic_view.xml +++ b/addons/analytic/analytic_view.xml @@ -53,6 +53,7 @@
+
diff --git a/addons/base_calendar/crm_meeting_view.xml b/addons/base_calendar/crm_meeting_view.xml index 2da18447820..44b5773a657 100644 --- a/addons/base_calendar/crm_meeting_view.xml +++ b/addons/base_calendar/crm_meeting_view.xml @@ -222,6 +222,7 @@
+
diff --git a/addons/crm/crm_lead_view.xml b/addons/crm/crm_lead_view.xml index f91a8756598..f0311f8fce5 100644 --- a/addons/crm/crm_lead_view.xml +++ b/addons/crm/crm_lead_view.xml @@ -223,6 +223,7 @@
+
@@ -525,6 +526,7 @@
+
diff --git a/addons/crm/crm_phonecall_view.xml b/addons/crm/crm_phonecall_view.xml index 8c1e1c9ec76..b8bc6b3a13e 100644 --- a/addons/crm/crm_phonecall_view.xml +++ b/addons/crm/crm_phonecall_view.xml @@ -146,10 +146,10 @@ -
+
diff --git a/addons/crm_claim/crm_claim_view.xml b/addons/crm_claim/crm_claim_view.xml index 9643a56bfe2..b319339bcfd 100644 --- a/addons/crm_claim/crm_claim_view.xml +++ b/addons/crm_claim/crm_claim_view.xml @@ -181,6 +181,7 @@
+
diff --git a/addons/crm_helpdesk/crm_helpdesk_view.xml b/addons/crm_helpdesk/crm_helpdesk_view.xml index 5399c9880ca..695def7801b 100644 --- a/addons/crm_helpdesk/crm_helpdesk_view.xml +++ b/addons/crm_helpdesk/crm_helpdesk_view.xml @@ -99,6 +99,7 @@
+
diff --git a/addons/event/event_view.xml b/addons/event/event_view.xml index 40019513126..f786592d06c 100644 --- a/addons/event/event_view.xml +++ b/addons/event/event_view.xml @@ -196,6 +196,7 @@
+
@@ -246,12 +247,12 @@
-
-
- - -
-
+
+
+ + +
+

@@ -271,10 +272,10 @@

- - + + @@ -478,6 +479,7 @@
+
diff --git a/addons/hr_expense/hr_expense_view.xml b/addons/hr_expense/hr_expense_view.xml index 388e5262f6e..f35547927fa 100644 --- a/addons/hr_expense/hr_expense_view.xml +++ b/addons/hr_expense/hr_expense_view.xml @@ -139,6 +139,7 @@
+
diff --git a/addons/hr_holidays/hr_holidays_view.xml b/addons/hr_holidays/hr_holidays_view.xml index 347e2b17ca5..0a2054b14d2 100644 --- a/addons/hr_holidays/hr_holidays_view.xml +++ b/addons/hr_holidays/hr_holidays_view.xml @@ -121,6 +121,7 @@
+
@@ -158,6 +159,7 @@
+
diff --git a/addons/hr_recruitment/hr_recruitment_view.xml b/addons/hr_recruitment/hr_recruitment_view.xml index 08e5f31a2d2..32c26573e8c 100644 --- a/addons/hr_recruitment/hr_recruitment_view.xml +++ b/addons/hr_recruitment/hr_recruitment_view.xml @@ -183,6 +183,7 @@
+
diff --git a/addons/idea/idea_view.xml b/addons/idea/idea_view.xml index eab3d03ceea..1b7cc5446b8 100644 --- a/addons/idea/idea_view.xml +++ b/addons/idea/idea_view.xml @@ -77,6 +77,7 @@
+
diff --git a/addons/mail/mail_followers.py b/addons/mail/mail_followers.py index b8f6c21ecf5..23417850f2a 100644 --- a/addons/mail/mail_followers.py +++ b/addons/mail/mail_followers.py @@ -19,6 +19,7 @@ # ############################################################################## +from openerp import SUPERUSER_ID from osv import osv from osv import fields import tools @@ -91,7 +92,7 @@ class mail_notification(osv.Model): :param browse_record message: mail.message to notify """ notify_pids = [] - for partner in self.pool.get('res.partner').browse(cr, uid, partner_ids, context=context): + for partner in self.pool.get('res.partner').browse(cr, SUPERUSER_ID, partner_ids, context=context): # Do not send an email to the writer if partner.user_ids and partner.user_ids[0].id == uid: continue diff --git a/addons/mail/mail_group_menu.py b/addons/mail/mail_group_menu.py index 25b85bb7b22..707ece6a180 100644 --- a/addons/mail/mail_group_menu.py +++ b/addons/mail/mail_group_menu.py @@ -19,9 +19,10 @@ # ############################################################################## +from openerp import SUPERUSER_ID from osv import osv from osv import fields -from tools.translate import _ + class ir_ui_menu(osv.osv): """ Override of ir.ui.menu class. When adding mail_thread module, each @@ -38,13 +39,14 @@ class ir_ui_menu(osv.osv): def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False): """ Override to take off menu entries (mail.group) the user is not - following. """ + following. Access are done using SUPERUSER_ID to avoid access + rights issues for an internal back-end algorithm. """ ids = super(ir_ui_menu, self).search(cr, uid, args, offset=0, limit=None, order=order, context=context, count=False) - partner_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id + partner_id = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0] follower_obj = self.pool.get('mail.followers') for menu in self.browse(cr, uid, ids, context=context): if menu.mail_group_id: - sub_ids = follower_obj.search(cr, uid, [ + sub_ids = follower_obj.search(cr, SUPERUSER_ID, [ ('partner_id', '=', partner_id), ('res_model', '=', 'mail.group'), ('res_id', '=', menu.mail_group_id.id) ], context=context) diff --git a/addons/mail/mail_group_view.xml b/addons/mail/mail_group_view.xml index a551a549b06..94b9f8f9b0b 100644 --- a/addons/mail/mail_group_view.xml +++ b/addons/mail/mail_group_view.xml @@ -81,6 +81,7 @@
+ diff --git a/addons/mail/mail_mail.py b/addons/mail/mail_mail.py index 6bbfa9bc30e..e9a569a8192 100644 --- a/addons/mail/mail_mail.py +++ b/addons/mail/mail_mail.py @@ -19,13 +19,12 @@ # ############################################################################## -# import ast import base64 import logging import tools -from osv import osv -from osv import fields +from openerp import SUPERUSER_ID +from osv import osv, fields from tools.translate import _ _logger = logging.getLogger(__name__) @@ -134,7 +133,8 @@ class mail_mail(osv.Model): :return: True """ if mail.auto_delete: - mail.unlink() + # done with SUPERUSER_ID to avoid giving large unlink access rights + self.unlink(cr, SUPERUSER_ID, [mail.id], context=context) return True def send_get_mail_subject(self, cr, uid, mail, force=False, partner=None, context=None): diff --git a/addons/mail/mail_message.py b/addons/mail/mail_message.py index 1d5d85e36e0..7d9c7b3085f 100644 --- a/addons/mail/mail_message.py +++ b/addons/mail/mail_message.py @@ -24,8 +24,9 @@ import openerp import tools from email.header import decode_header +from openerp import SUPERUSER_ID from operator import itemgetter -from osv import osv, fields +from osv import osv, orm, fields from tools.translate import _ _logger = logging.getLogger(__name__) @@ -62,14 +63,14 @@ class mail_message(osv.Model): continue try: result[message.id] = self._shorten_name(self.pool.get(message.model).name_get(cr, uid, [message.res_id], context=context)[0][1]) - except openerp.exceptions.AccessDenied, e: + except (orm.except_orm, osv.except_osv): pass return result def _get_unread(self, cr, uid, ids, name, arg, context=None): """ Compute if the message is unread by the current user. """ res = dict((id, {'unread': False}) for id in ids) - partner_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id + partner_id = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0] notif_obj = self.pool.get('mail.notification') notif_ids = notif_obj.search(cr, uid, [ ('partner_id', 'in', [partner_id]), @@ -87,7 +88,7 @@ class mail_message(osv.Model): read_cond = '(read = false or read is null)' else: read_cond = 'read = true' - partner_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id + partner_id = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0] cr.execute("SELECT message_id FROM mail_notification "\ "WHERE partner_id = %%s AND %s" % read_cond, (partner_id,)) @@ -140,7 +141,8 @@ class mail_message(osv.Model): return [] def _get_default_author(self, cr, uid, context=None): - return self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id + # remove context to avoid possible hack in browse with superadmin using context keys that could trigger a specific behavior + return self.pool.get('res.users').browse(cr, SUPERUSER_ID, uid, context=None).partner_id.id _defaults = { 'type': 'email', @@ -154,16 +156,17 @@ class mail_message(osv.Model): #------------------------------------------------------ def vote_toggle(self, cr, uid, ids, user_ids=None, context=None): - ''' Toggles voting ''' + ''' Toggles voting. Done as SUPERUSER_ID because of write access on + mail.message not always granted. ''' if not user_ids: user_ids = [uid] for message in self.read(cr, uid, ids, ['vote_user_ids'], context=context): for user_id in user_ids: has_voted = user_id in message.get('vote_user_ids') if not has_voted: - self.write(cr, uid, message.get('id'), {'vote_user_ids': [(4, user_id)]}, context=context) + self.write(cr, SUPERUSER_ID, message.get('id'), {'vote_user_ids': [(4, user_id)]}, context=context) else: - self.write(cr, uid, message.get('id'), {'vote_user_ids': [(3, user_id)]}, context=context) + self.write(cr, SUPERUSER_ID, message.get('id'), {'vote_user_ids': [(3, user_id)]}, context=context) return True #------------------------------------------------------ @@ -171,17 +174,31 @@ class mail_message(osv.Model): #------------------------------------------------------ def _message_dict_get(self, cr, uid, msg, context=None): - """ Return a dict representation of the message browse record. """ + """ Return a dict representation of the message browse record. A read + is performed to because of access rights issues (reading many2one + fields allow to have the foreign record name without having + to check external access rights). + """ has_voted = False - vote_ids = self.pool.get('res.users').name_get(cr, uid, [user.id for user in msg.vote_user_ids], context=context) + vote_ids = self.pool.get('res.users').name_get(cr, SUPERUSER_ID, [user.id for user in msg.vote_user_ids], context=context) for vote in vote_ids: if vote[0] == uid: has_voted = True break - attachment_ids = [{'id': attach[0], 'name': attach[1]} for attach in self.pool.get('ir.attachment').name_get(cr, uid, [x.id for x in msg.attachment_ids], context=context)] - author_id = self.pool.get('res.partner').name_get(cr, uid, [msg.author_id.id], context=context)[0] - author_user_id = self.pool.get('res.users').name_get(cr, uid, [msg.author_id.user_ids[0].id], context=context)[0] - partner_ids = self.pool.get('res.partner').name_get(cr, uid, [x.id for x in msg.partner_ids], context=context) + try: + attachment_ids = [{'id': attach[0], 'name': attach[1]} for attach in self.pool.get('ir.attachment').name_get(cr, uid, [x.id for x in msg.attachment_ids], context=context)] + except (orm.except_orm, osv.except_osv): + attachment_ids = [] + try: + author_id = self.pool.get('res.partner').name_get(cr, uid, [msg.author_id.id], context=context)[0] + is_author = uid in msg.author_id.user_ids + except (orm.except_orm, osv.except_osv): + author_id = False + is_author = False + try: + partner_ids = self.pool.get('res.partner').name_get(cr, uid, [x.id for x in msg.partner_ids], context=context) + except (orm.except_orm, osv.except_osv): + partner_ids = [] return { 'id': msg.id, 'type': msg.type, @@ -193,7 +210,7 @@ class mail_message(osv.Model): 'subject': msg.subject, 'date': msg.date, 'author_id': author_id, - 'author_user_id': author_user_id, + 'is_author': is_author, 'partner_ids': partner_ids, 'child_ids': [], 'vote_user_ids': vote_ids, @@ -246,7 +263,7 @@ class mail_message(osv.Model): limit = limit or self._message_read_limit context = context or {} if not ids: - ids = self.search(cr, uid, domain, context=context, limit=limit) + ids = self.search(cr, SUPERUSER_ID, domain, context=context, limit=limit) messages = self.browse(cr, uid, ids, context=context) result = [] @@ -294,41 +311,99 @@ class mail_message(osv.Model): cr.execute("""CREATE INDEX mail_message_model_res_id_idx ON mail_message (model, res_id)""") def check_access_rule(self, cr, uid, ids, operation, context=None): - """ mail.message access rule check - - message received (a notification exists) -> ok - - check rules of related document if exists - - fallback on normal mail.message check """ + """ Access rules of mail.message: + - read: if + - notification exist (I receive pushed message) OR + - author_id = pid (I am the author) OR + - I can read the related document if res_model, res_id + - Otherwise: raise + - create: if + - I am in the document message_follower_ids OR + - I can write on the related document if res_model, res_id + - Otherwise: raise + - write: if + - I can write on the related document if res_model, res_id + - Otherwise: raise + - unlink: if + - I can write on the related document if res_model, res_id + - Otherwise: raise + """ + if uid == SUPERUSER_ID: + return if isinstance(ids, (int, long)): ids = [ids] + partner_id = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=None)['partner_id'][0] - # check messages for which you have a notification - partner_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id - not_obj = self.pool.get('mail.notification') - not_ids = not_obj.search(cr, uid, [ - ('partner_id', '=', partner_id), - ('message_id', 'in', ids), - ], context=context) - notified_ids = [notification.message_id.id for notification in not_obj.browse(cr, uid, not_ids, context=context) - if notification.message_id.id in ids] - - # check messages linked to an existing document + # Read mail_message.ids to have their values model_record_ids = {} - document_ids = [] - cr.execute('SELECT DISTINCT id, model, res_id FROM mail_message WHERE id = ANY (%s)', (ids,)) - for id, rmod, rid in cr.fetchall(): - if not (rmod and rid): - continue - document_ids.append(id) - model_record_ids.setdefault(rmod, set()).add(rid) + message_values = dict.fromkeys(ids) + cr.execute('SELECT DISTINCT id, model, res_id, author_id FROM "%s" WHERE id = ANY (%%s)' % self._table, (ids,)) + for id, rmod, rid, author_id in cr.fetchall(): + message_values[id] = {'res_model': rmod, 'res_id': rid, 'author_id': author_id} + if rmod: + model_record_ids.setdefault(rmod, set()).add(rid) + + # Read: Check for received notifications -> could become an ir.rule, but not till we do not have a many2one variable field + if operation == 'read': + not_obj = self.pool.get('mail.notification') + not_ids = not_obj.search(cr, SUPERUSER_ID, [ + ('partner_id', '=', partner_id), + ('message_id', 'in', ids), + ], context=context) + notified_ids = [notification.message_id.id for notification in not_obj.browse(cr, SUPERUSER_ID, not_ids, context=context)] + else: + notified_ids = [] + # Read: Check messages you are author -> could become an ir.rule, but not till we do not have a many2one variable field + if operation == 'read': + author_ids = [mid for mid, message in message_values.iteritems() + if message.get('author_id') and message.get('author_id') == partner_id] + else: + author_ids = [] + + # Create: Check message_follower_ids + if operation == 'create': + doc_follower_ids = [] + for model, mids in model_record_ids.items(): + fol_obj = self.pool.get('mail.followers') + fol_ids = fol_obj.search(cr, SUPERUSER_ID, [ + ('res_model', '=', model), + ('res_id', 'in', list(mids)), + ('partner_id', '=', partner_id), + ], context=context) + fol_mids = [follower.res_id for follower in fol_obj.browse(cr, SUPERUSER_ID, fol_ids, context=context)] + doc_follower_ids += [mid for mid, message in message_values.iteritems() + if message.get('res_model') == model and message.get('res_id') in fol_mids] + else: + doc_follower_ids = [] + + # Calculate remaining ids, and related model/res_ids + model_record_ids = {} + other_ids = set(ids).difference(set(notified_ids), set(author_ids), set(doc_follower_ids)) + for id in other_ids: + if message_values[id]['res_model']: + model_record_ids.setdefault(message_values[id]['res_model'], set()).add(message_values[id]['res_id']) + + # CRUD: Access rights related to the document + document_related_ids = [] for model, mids in model_record_ids.items(): model_obj = self.pool.get(model) mids = model_obj.exists(cr, uid, mids) - model_obj.check_access_rights(cr, uid, operation) - model_obj.check_access_rule(cr, uid, mids, operation, context=context) + if operation in ['create', 'write', 'unlink']: + model_obj.check_access_rights(cr, uid, 'write') + model_obj.check_access_rule(cr, uid, mids, 'write', context=context) + else: + model_obj.check_access_rights(cr, uid, operation) + model_obj.check_access_rule(cr, uid, mids, operation, context=context) + document_related_ids += [mid for mid, message in message_values.iteritems() + if message.get('res_model') == model and message.get('res_id') in mids] - # fall back on classic operation for other ids - other_ids = set(ids).difference(set(notified_ids), set(document_ids)) - super(mail_message, self).check_access_rule(cr, uid, other_ids, operation, context=None) + # Calculate remaining ids: if not void, raise an error + other_ids = set(ids).difference(set(notified_ids), set(author_ids), set(doc_follower_ids), set(document_related_ids)) + if not other_ids: + return + raise orm.except_orm(_('Access Denied'), + _('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)) def create(self, cr, uid, values, context=None): if not values.get('message_id') and values.get('res_id') and values.get('model'): @@ -337,6 +412,13 @@ class mail_message(osv.Model): self.notify(cr, uid, newid, context=context) return newid + def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'): + """ Override to explicitely call check_access_rule, that is not called + by the ORM. It instead directly fetches ir.rules and apply them. """ + res = super(mail_message, self).read(cr, uid, ids, fields=fields, context=context, load=load) + self.check_access_rule(cr, uid, ids, 'read', context=context) + return res + def unlink(self, cr, uid, ids, context=None): # cascade-delete attachments that are directly attached to the message (should only happen # for mail.messages that act as parent for a standalone mail.mail record). @@ -360,12 +442,14 @@ class mail_message(osv.Model): partners_to_notify |= set(partner.id for partner in message.partner_ids) # add all followers and set add them in partner_ids if message.model and message.res_id: - record = self.pool.get(message.model).browse(cr, uid, message.res_id, context=context) + record = self.pool.get(message.model).browse(cr, SUPERUSER_ID, message.res_id, context=context) extra_notified = set(partner.id for partner in record.message_follower_ids) missing_notified = extra_notified - partners_to_notify if missing_notified: - message.write({'partner_ids': [(4, p_id) for p_id in missing_notified]}) + self.write(cr, SUPERUSER_ID, [newid], {'partner_ids': [(4, p_id) for p_id in missing_notified]}, context=context) partners_to_notify |= extra_notified + # # remove uid from partners + self.write(cr, SUPERUSER_ID, [newid], {'partner_ids': [(3, uid)]}, context=context) self.pool.get('mail.notification').notify(cr, uid, list(partners_to_notify), newid, context=context) def copy(self, cr, uid, id, default=None, context=None): diff --git a/addons/mail/mail_message_view.xml b/addons/mail/mail_message_view.xml index 914937a34f3..814702f6db4 100644 --- a/addons/mail/mail_message_view.xml +++ b/addons/mail/mail_message_view.xml @@ -95,7 +95,7 @@ - My Feeds + My Posts mail.wall diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index abee20a35f8..c671dd55ec4 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -30,6 +30,7 @@ import xmlrpclib from email.message import Message from mail_message import decode +from openerp import SUPERUSER_ID from osv import osv, fields from tools.safe_eval import safe_eval as eval @@ -118,26 +119,27 @@ class mail_thread(osv.AbstractModel): def _get_message_data(self, cr, uid, ids, name, args, context=None): res = dict((id, dict(message_unread=False, message_summary='')) for id in ids) - user = self.pool.get('res.users').browse(cr, uid, uid, context=context) + partner_id = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0] + # search for unread messages, by reading directly mail.notification, as SUPERUSER notif_obj = self.pool.get('mail.notification') - notif_ids = notif_obj.search(cr, uid, [ + notif_ids = notif_obj.search(cr, SUPERUSER_ID, [ ('partner_id.user_ids', 'in', [uid]), ('message_id.res_id', 'in', ids), ('message_id.model', '=', self._name), ('read', '=', False) ], context=context) - for notif in notif_obj.browse(cr, uid, notif_ids, context=context): + for notif in notif_obj.browse(cr, SUPERUSER_ID, notif_ids, context=context): res[notif.message_id.res_id]['message_unread'] = True - for thread in self.browse(cr, uid, ids, context=context): - cls = res[thread.id]['message_unread'] and ' class="oe_kanban_mail_new"' or '' - res[thread.id]['message_summary'] = "9 %d + %d" % (cls, len(thread.message_comment_ids), len(thread.message_follower_ids)) - res[thread.id]['message_is_follower'] = user.partner_id.id in [follower.id for follower in thread.message_follower_ids] + for thread in self.read(cr, uid, ids, ['message_follower_ids', 'message_comment_ids', 'message_ids'], context=context): + cls = res[thread['id']]['message_unread'] and ' class="oe_kanban_mail_new"' or '' + res[thread['id']]['message_summary'] = "9 %d + %d" % (cls, len(thread['message_comment_ids']), len(thread['message_follower_ids'])) + res[thread['id']]['message_is_follower'] = partner_id in thread['message_follower_ids'] return res def _search_unread(self, cr, uid, obj=None, name=None, domain=None, context=None): - partner_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id + partner_id = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0] res = {} notif_obj = self.pool.get('mail.notification') notif_ids = notif_obj.search(cr, uid, [ @@ -196,6 +198,13 @@ class mail_thread(osv.AbstractModel): fol_obj.unlink(cr, uid, fol_ids, context=context) return super(mail_thread, self).unlink(cr, uid, ids, context=context) + def copy(self, cr, uid, id, default=None, context=None): + default = default or {} + default['message_ids'] = [] + default['message_comment_ids'] = [] + default['message_follower_ids'] = [] + return super(mail_thread, self).copy(cr, uid, id, default=default, context=context) + #------------------------------------------------------ # mail.message wrappers and tools #------------------------------------------------------ @@ -620,7 +629,7 @@ class mail_thread(osv.AbstractModel): """ Wrapper on message_subscribe, using users. If user_ids is not provided, subscribe uid instead. """ if not user_ids: user_ids = [uid] - partner_ids = [user.partner_id.id for user in self.pool.get('res.users').browse(cr, uid, user_ids, context=context)] + partner_ids = [user['partner_id'][0] for user in self.pool.get('res.users').read(cr, uid, user_ids, ['partner_id'], context=context)] return self.message_subscribe(cr, uid, ids, partner_ids, context=context) def message_subscribe(self, cr, uid, ids, partner_ids, context=None): @@ -631,7 +640,7 @@ class mail_thread(osv.AbstractModel): """ Wrapper on message_subscribe, using users. If user_ids is not provided, unsubscribe uid instead. """ if not user_ids: user_ids = [uid] - partner_ids = [user.partner_id.id for user in self.pool.get('res.users').browse(cr, uid, user_ids, context=context)] + partner_ids = [user['partner_id'][0] for user in self.pool.get('res.users').read(cr, uid, user_ids, ['partner_id'], context=context)] return self.message_unsubscribe(cr, uid, ids, partner_ids, context=context) def message_unsubscribe(self, cr, uid, ids, partner_ids, context=None): diff --git a/addons/mail/res_partner_view.xml b/addons/mail/res_partner_view.xml index 2a8430926d5..5c051fc89b3 100644 --- a/addons/mail/res_partner_view.xml +++ b/addons/mail/res_partner_view.xml @@ -9,6 +9,7 @@
+ diff --git a/addons/mail/security/ir.model.access.csv b/addons/mail/security/ir.model.access.csv index 2ec74086a34..26048e7a40c 100644 --- a/addons/mail/security/ir.model.access.csv +++ b/addons/mail/security/ir.model.access.csv @@ -1,13 +1,14 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -access_mail_global,mail.mail,model_mail_mail,base.group_system,1,1,1,1 -access_mail_message_all,mail.message.all,model_mail_message,,1,0,0,0 +access_mail_message_all,mail.message.all,model_mail_message,,1,0,1,0 access_mail_message_group_user,mail.message.group.user,model_mail_message,base.group_user,1,1,1,1 -access_mail_thread,mail.thread,model_mail_thread,base.group_user,1,1,1,0 -access_mail_followers_all,mail.followers.all,model_mail_followers,,1,1,1,1 -access_mail_notification_all,mail.notification.all,model_mail_notification,,1,1,1,1 -access_mail_group,mail.group,model_mail_group,base.group_user,1,1,1,1 +access_mail_mail_all,mail.mail.all,model_mail_mail,,0,0,1,0 +access_mail_mail_system,mail.mail.system,model_mail_mail,base.group_system,1,1,1,1 +access_mail_followers_all,mail.followers.all,model_mail_followers,,0,0,0,0 +access_mail_followers_system,mail.followers.system,model_mail_followers,base.group_system,1,1,1,1 +access_mail_notification_all,mail.notification.all,model_mail_notification,,1,0,0,0 +access_mail_notification_aystem,mail.notification.system,model_mail_notification,base.group_system,1,1,1,1 +access_mail_group_all,mail.group.all,model_mail_group,,1,0,0,0 +access_mail_group_user,mail.group.user,model_mail_group,base.group_user,1,1,1,1 +access_mail_alias_all,mail.alias.all,model_mail_alias,,1,0,0,0 access_mail_alias_user,mail.alias,model_mail_alias,base.group_user,1,1,1,0 -access_mail_alias_system,mail.alias,model_mail_alias,base.group_system,1,1,1,1 -access_mail_mail_user,mail.mail,model_mail_mail,base.group_user,1,1,1,0 -access_mail_mail_manager,mail.mail,model_mail_mail,group_mail_manager,1,1,1,1 access_mail_vote_all,mail.vote.all,model_mail_vote,,1,1,1,1 diff --git a/addons/mail/security/mail_security.xml b/addons/mail/security/mail_security.xml index 1d6d8d90579..b503565bd11 100644 --- a/addons/mail/security/mail_security.xml +++ b/addons/mail/security/mail_security.xml @@ -2,20 +2,6 @@ - - - Home - - 26 - - - - - Mail manager - - - - Mail.group: access only public and joined groups @@ -24,5 +10,18 @@ ['|', '|', ('public', '=', 'public'), ('message_follower_ids', 'in', [user.id]), '&', ('public','=','groups'), ('group_public_id','in', [x.id for x in user.groups_id])] + + diff --git a/addons/mail/static/src/css/mail.css b/addons/mail/static/src/css/mail.css index 717d0439486..e69eaadbcdb 100644 --- a/addons/mail/static/src/css/mail.css +++ b/addons/mail/static/src/css/mail.css @@ -124,6 +124,14 @@ } +/* ------------------------------------------------------------ */ +/* Followers +/* ------------------------------------------------------------ */ + +.openerp div.oe_mail_recthread_aside h4 { + display: inline-block; +} + /* ------------------------------------------------------------ */ /* Thread /* ------------------------------------------------------------ */ @@ -224,7 +232,7 @@ clear: both; } -.openerp .oe_mail_msg_content a { +.openerp .oe_chatter a { cursor: pointer; } @@ -258,30 +266,6 @@ display: none; } -/*--------------------------------------------------------------*/ -/* mail.vote -/*--------------------------------------------------------------*/ - -.openerp .oe_mail_msg_content button.oe_mail_msg_vote { - height:21px; - width: 30px; - padding: 1px; - background: #8A89BA; - margin-top: -4px; -} - -.openerp .oe_mail_msg_content button.oe_mail_msg_vote_true { - background:#DC5F59; -} - -.openerp .oe_mail_msg_content button.oe_mail_msg_vote span { - color: white; -} - -.openerp .oe_mail_msg_content span.oe_mail_vote_count{ - color: #807FB4; -} - /* ------------------------------------------------------------ */ /* mail.compose.message form view & OpenERP hacks diff --git a/addons/mail/static/src/js/mail.js b/addons/mail/static/src/js/mail.js index 326b2f442b1..196c54c8296 100644 --- a/addons/mail/static/src/js/mail.js +++ b/addons/mail/static/src/js/mail.js @@ -56,11 +56,6 @@ openerp.mail = function(session) { return session.origin + '/web/binary/saveas?session_id=' + session.session_id + '&model=ir.attachment&field=datas&filename_field=datas_fname&id=' + attachment['id']; }, - /** Check if the current user is the message author */ - is_author: function (widget, message_user_id) { - return (widget.session && widget.session.uid != 0 && widget.session.uid == message_user_id); - }, - /** Replaces some expressions * - :name - shortcut to an image */ @@ -301,6 +296,7 @@ openerp.mail = function(session) { // TDE TODO: check for deferred, not sure it is correct this._super.apply(this, arguments); this.bind_events(); + this.display_user_avatar(); // fetch and display message, using message_ids if set var display_done = $.when(this.message_fetch(true, [], {})).then(this.proxy('do_customize_display')); // add message composition form view @@ -313,7 +309,6 @@ openerp.mail = function(session) { /** Customize the display * - show_header_compose: show the composition form in the header */ do_customize_display: function() { - this.display_user_avatar(); if (this.options.show_header_compose) { this.$el.find('div.oe_mail_thread_action').eq(0).show(); } @@ -491,8 +486,7 @@ openerp.mail = function(session) { * - record.date: formatting according to the user timezone * - record.timerelative: relative time givein by timeago lib * - record.avatar: image url - * - record.attachment_ids[].url: url of each attachment - * - record.is_author: is the current user the author of the record */ + * - record.attachment_ids[].url: url of each attachment */ display_record: function (record) { // formatting and additional fields record.date = session.web.format_value(record.date, {type:"datetime"}); @@ -506,7 +500,6 @@ openerp.mail = function(session) { var attach = record.attachment_ids[l]; attach['url'] = mail.ChatterUtils.get_attachment_url(this.session, attach); } - record.is_author = mail.ChatterUtils.is_author(this, record.author_user_id[0]); // add to internal storage this.records[record.id] = record; // render, add the expand feature @@ -649,12 +642,15 @@ openerp.mail = function(session) { // update domain var domain = this.options.domain.concat([['model', '=', this.view.model], ['res_id', '=', this.view.datarecord.id]]); // create and render Thread widget + var show_header_compose = this.view.is_action_enabled('edit') || + (this.getParent().fields.message_is_follower && this.getParent().fields.message_is_follower.get_value()); this.$el.find('div.oe_mail_recthread_main').empty(); var thread = new mail.Thread(self, domain, this.options.context, { 'thread_level': this.options.thread_level, - 'use_composer': true, + 'show_header_compose': show_header_compose, + 'use_composer': show_header_compose, 'show_dd_delete': true, - 'show_reply_by_email': true }); + 'show_reply_by_email': show_header_compose }); return thread.appendTo(this.$el.find('div.oe_mail_recthread_main')); }, }); diff --git a/addons/mail/static/src/js/mail_followers.js b/addons/mail/static/src/js/mail_followers.js index d1dd9f89fb7..743c4704330 100644 --- a/addons/mail/static/src/js/mail_followers.js +++ b/addons/mail/static/src/js/mail_followers.js @@ -45,8 +45,8 @@ openerp_mail_followers = function(session, mail) { }, reinit: function() { - this.$el.find('button.oe_mail_button_follow').hide(); - this.$el.find('button.oe_mail_button_unfollow').hide(); + this.message_is_follower == undefined; + this.display_buttons(); }, bind_events: function() { @@ -55,7 +55,7 @@ openerp_mail_followers = function(session, mail) { .mouseover(function () { $(this).html('Unfollow').removeClass('oe_mail_button_mouseout').addClass('oe_mail_button_mouseover'); }) .mouseleave(function () { $(this).html('Following').removeClass('oe_mail_button_mouseover').addClass('oe_mail_button_mouseout'); }); this.$el.on('click', 'button.oe_mail_button_follow', function () { self.do_follow(); }); - this.$el.on('click', 'button.oe_mail_button_invite', function(event) { + this.$el.on('click', 'a.oe_mail_invite', function(event) { action = { type: 'ir.actions.act_window', res_model: 'mail.wizard.invite', @@ -74,41 +74,65 @@ openerp_mail_followers = function(session, mail) { read_value: function() { var self = this; - return this.ds_model.read_ids([this.view.datarecord.id], ['message_follower_ids']).pipe(function (results) { - return results[0].message_follower_ids; - }).pipe(this.proxy('set_value')); + return this.ds_model.read_ids([this.view.datarecord.id], ['message_is_follower', 'message_follower_ids']).then(function (results) { + self.set_value(results[0].message_follower_ids, results[0].message_is_follower); + }); }, - set_value: function(value_) { + set_value: function(value_, message_is_follower) { this.reinit(); if (! this.view.datarecord.id || session.web.BufferedDataSet.virtual_id_regex.test(this.view.datarecord.id)) { - this.$el.find('div.oe_mail_recthread_aside').hide(); + this.$('div.oe_mail_recthread_aside').hide(); return; } - return this.fetch_followers(value_ || this.get_value()); + return this.fetch_followers(value_ || this.get_value(), message_is_follower); }, - fetch_followers: function (value_) { - return this.ds_follow.call('read', [value_, ['name', 'user_ids']]).pipe(this.proxy('display_followers')); + fetch_followers: function (value_, message_is_follower) { + this.value = value_; + this.message_is_follower = message_is_follower || (this.getParent().fields.message_is_follower && this.getParent().fields.message_is_follower.get_value()); + return this.ds_follow.call('read', [value_, ['name', 'user_ids']]).pipe(this.proxy('display_followers'), this.proxy('display_generic')); + }, + + + /* Display generic info about follower, for people not having access to res_partner */ + display_generic: function (error, event) { + event.preventDefault(); + var node_user_list = this.$('ul.oe_mail_followers_display').empty(); + // format content: Followers (You and 0 other) // Followers (3) + var content = this.options.title; + if (this.message_is_follower) { + content += ' (You and ' + (this.value.length-1) + ' other)'; + } + else { + content += ' (' + this.value.length + ')' + } + this.$('div.oe_mail_recthread_followers h4').html(content); + this.display_buttons(); + return $.when(); }, /** Display the followers, evaluate is_follower directly */ display_followers: function (records) { var self = this; - this.message_is_follower = _.indexOf(_.flatten(_.pluck(records, 'user_ids')), this.session.uid) != -1; - var node_user_list = this.$el.find('ul.oe_mail_followers_display').empty(); - this.$el.find('div.oe_mail_recthread_followers h4').html(this.options.title + ' (' + records.length + ')'); + var node_user_list = this.$('ul.oe_mail_followers_display').empty(); + this.$('div.oe_mail_recthread_followers h4').html(this.options.title + ' (' + records.length + ')'); _(records).each(function (record) { record.avatar_url = mail.ChatterUtils.get_image(self.session, 'res.partner', 'image_small', record.id); $(session.web.qweb.render('mail.followers.partner', {'record': record})).appendTo(node_user_list); }); - if (this.message_is_follower) { - this.$el.find('button.oe_mail_button_follow').hide(); - this.$el.find('button.oe_mail_button_unfollow').show(); } - else { - this.$el.find('button.oe_mail_button_follow').show(); - this.$el.find('button.oe_mail_button_unfollow').hide(); } + this.display_buttons(); + }, + + display_buttons: function () { + this.$('button.oe_mail_button_follow').hide(); + this.$('button.oe_mail_button_unfollow').hide(); + this.$('span.oe_mail_invite_wrapper').hide(); + if (! this.view.is_action_enabled('edit')) return; + this.$('span.oe_mail_invite_wrapper').show(); + if (this.message_is_follower) { this.$('button.oe_mail_button_unfollow').show(); } + else if (this.message_is_follower == false) { this.$('button.oe_mail_button_follow').show(); } }, do_follow: function () { diff --git a/addons/mail/static/src/xml/mail.xml b/addons/mail/static/src/xml/mail.xml index e3586e723e4..c54bd4dbb2d 100644 --- a/addons/mail/static/src/xml/mail.xml +++ b/addons/mail/static/src/xml/mail.xml @@ -140,7 +140,7 @@
  • -
  • +
  • Reply
  • @@ -185,7 +185,7 @@ votes - diff --git a/addons/mail/static/src/xml/mail_followers.xml b/addons/mail/static/src/xml/mail_followers.xml index cf31ab3e24c..203a7bce789 100644 --- a/addons/mail/static/src/xml/mail_followers.xml +++ b/addons/mail/static/src/xml/mail_followers.xml @@ -7,7 +7,6 @@ -->
    -
    @@ -18,7 +17,7 @@
    -

    +

    ยท Invite partners
      diff --git a/addons/mail/tests/__init__.py b/addons/mail/tests/__init__.py index d63c5634cc3..2d48fa233c4 100644 --- a/addons/mail/tests/__init__.py +++ b/addons/mail/tests/__init__.py @@ -18,10 +18,11 @@ # along with this program. If not, see . # ############################################################################## -from . import test_mail +from . import test_mail, test_mail_access_rights checks = [ test_mail, + test_mail_access_rights, ] -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: \ No newline at end of file +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/mail/tests/test_mail_access_rights.py b/addons/mail/tests/test_mail_access_rights.py new file mode 100644 index 00000000000..104d7f28ffe --- /dev/null +++ b/addons/mail/tests/test_mail_access_rights.py @@ -0,0 +1,154 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Business Applications +# Copyright (c) 2012-TODAY OpenERP S.A. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +from openerp.addons.mail.tests import test_mail +from osv.orm import except_orm + + +class test_mail_access_rights(test_mail.TestMailMockups): + + def setUp(self): + super(test_mail_access_rights, self).setUp() + cr, uid = self.cr, self.uid + self.mail_group = self.registry('mail.group') + self.mail_message = self.registry('mail.message') + self.mail_notification = self.registry('mail.notification') + self.res_users = self.registry('res.users') + self.res_groups = self.registry('res.groups') + self.res_partner = self.registry('res.partner') + + # create a 'pigs' group that will be used through the various tests + self.group_pigs_id = self.mail_group.create(self.cr, self.uid, + {'name': 'Pigs', 'description': 'Fans of Pigs, unite !'}) + + # Find Employee group + group_employee_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'base', 'group_user') + self.group_employee_id = group_employee_ref and group_employee_ref[1] or False + + # Create Bert (without groups) and Raoul( employee) + self.user_bert_id = self.res_users.create(cr, uid, {'name': 'Bert Tartopoils', 'login': 'bert', 'groups_id': [(6, 0, [])]}) + self.user_raoul_id = self.res_users.create(cr, uid, {'name': 'Raoul Grosbedon', 'login': 'raoul', 'groups_id': [(6, 0, [self.group_employee_id])]}) + self.user_bert = self.res_users.browse(cr, uid, self.user_bert_id) + self.partner_bert_id = self.user_bert.partner_id.id + self.user_raoul = self.res_users.browse(cr, uid, self.user_raoul_id) + self.partner_raoul_id = self.user_raoul.partner_id.id + + def test_00_mail_message_read_access_rights(self): + """ Test basic mail_message read access rights. """ + cr, uid = self.cr, self.uid + partner_bert_id, partner_raoul_id = self.partner_bert_id, self.partner_raoul_id + user_bert_id, user_raoul_id = self.user_bert_id, self.user_raoul_id + + # Prepare groups: Pigs (employee), Jobs (public) + self.mail_group.message_post(cr, uid, self.group_pigs_id, body='Message') + self.group_jobs_id = self.mail_group.create(cr, uid, {'name': 'Jobs', 'public': 'public'}) + + # ---------------------------------------- + # CASE1: Bert, basic mail.message read access + # ---------------------------------------- + + # Do: create a new mail.message + message_id = self.mail_message.create(cr, uid, {'body': 'My Body'}) + # Test: Bert reads the message, crash because not notification/not in doc followers/not read on doc + self.assertRaises(except_orm, self.mail_message.read, + cr, user_bert_id, message_id) + # Do: message is pushed to Bert + notif_id = self.mail_notification.create(cr, uid, {'message_id': message_id, 'partner_id': partner_bert_id}) + # Test: Bert reads the message, ok because notification pushed + self.mail_message.read(cr, user_bert_id, message_id) + # Do: remove notification + self.mail_notification.unlink(cr, uid, notif_id) + # Test: Bert reads the message, crash because not notification/not in doc followers/not read on doc + self.assertRaises(except_orm, self.mail_message.read, + cr, self.user_bert_id, message_id) + # Do: Bert is now the author + self.mail_message.write(cr, uid, [message_id], {'author_id': partner_bert_id}) + # Test: Bert reads the message, ok because Bert is the author + self.mail_message.read(cr, user_bert_id, message_id) + # Do: Bert is not the author anymore + self.mail_message.write(cr, uid, [message_id], {'author_id': partner_raoul_id}) + # Test: Bert reads the message, crash because not notification/not in doc followers/not read on doc + self.assertRaises(except_orm, self.mail_message.read, + cr, user_bert_id, message_id) + # Do: message is attached to a document Bert can read, Jobs + self.mail_message.write(cr, uid, [message_id], {'model': 'mail.group', 'res_id': self.group_jobs_id}) + # Test: Bert reads the message, ok because linked to a doc he is allowed to read + self.mail_message.read(cr, user_bert_id, message_id) + # Do: message is attached to a document Bert cannot read, Pigs + self.mail_message.write(cr, uid, [message_id], {'model': 'mail.group', 'res_id': self.group_pigs_id}) + # Test: Bert reads the message, crash because not notification/not in doc followers/not read on doc + self.assertRaises(except_orm, self.mail_message.read, + cr, user_bert_id, message_id) + + def test_05_mail_message_search_access_rights(self): + """ Test mail_message search override about access rights. """ + self.assertTrue(1 == 1, 'Test not implemented, do not replace by return True') + + def test_10_mail_flow_access_rights(self): + """ Test a Chatter-looks alike flow. """ + cr, uid = self.cr, self.uid + partner_bert_id, partner_raoul_id = self.partner_bert_id, self.partner_raoul_id + user_bert_id, user_raoul_id = self.user_bert_id, self.user_raoul_id + + # Prepare groups: Pigs (employee), Jobs (public) + self.mail_group.message_post(cr, uid, self.group_pigs_id, body='Message') + self.group_jobs_id = self.mail_group.create(cr, uid, {'name': 'Jobs', 'public': 'public'}) + + # ---------------------------------------- + # CASE1: Bert, without groups + # ---------------------------------------- + # Do: Bert creates a group, should crash because perm_create only for employees + self.assertRaises(except_orm, + self.mail_group.create, + cr, user_bert_id, {'name': 'Bert\'s Group'}) + # Do: Bert reads Jobs basic fields, ok because public = read access on the group + self.mail_group.read(cr, user_bert_id, self.group_jobs_id, ['name', 'description']) + # Do: Bert browse Pigs, ok (no direct browse of partners) + self.mail_group.browse(cr, user_bert_id, self.group_jobs_id) + # Do: Bert reads Jobs messages, ok because read access on the group => read access on its messages + jobs_message_ids = self.mail_group.read(cr, user_bert_id, self.group_jobs_id, ['message_ids'])['message_ids'] + self.mail_message.read(cr, user_bert_id, jobs_message_ids) + # Do: Bert reads Jobs followers, ko because partner are accessible to employees or partner manager + jobs_followers_ids = self.mail_group.read(cr, user_bert_id, self.group_jobs_id, ['message_follower_ids'])['message_follower_ids'] + self.assertRaises(except_orm, + self.res_partner.read, + cr, user_bert_id, jobs_followers_ids) + # Do: Bert comments Jobs, ko because no write access on the group and not in the followers + self.assertRaises(except_orm, + self.mail_group.message_post, + cr, user_bert_id, self.group_jobs_id, body='I love Pigs') + # Do: add Bert to jobs followers + self.mail_group.message_subscribe(cr, uid, [self.group_jobs_id], [partner_bert_id]) + # Do: Bert comments Jobs, ok because he is now in the followers + self.mail_group.message_post(cr, user_bert_id, self.group_jobs_id, body='I love Pigs') + + # Do: Bert reads Pigs, should crash because mail.group security=groups only for employee group + self.assertRaises(except_orm, + self.mail_group.read, + cr, user_bert_id, self.group_pigs_id) + + # ---------------------------------------- + # CASE1: Raoul, employee + # ---------------------------------------- + # Do: Bert read Pigs, ok because public + self.mail_group.read(cr, user_raoul_id, self.group_pigs_id) + # Do: Bert read Jobs, ok because group_public_id = employee + self.mail_group.read(cr, user_raoul_id, self.group_jobs_id) diff --git a/addons/mrp/mrp_view.xml b/addons/mrp/mrp_view.xml index c003711ceb1..53431b6c37b 100644 --- a/addons/mrp/mrp_view.xml +++ b/addons/mrp/mrp_view.xml @@ -409,8 +409,9 @@
      - - + + +
      @@ -799,6 +800,7 @@
      +
      diff --git a/addons/mrp_operations/mrp_operations_view.xml b/addons/mrp_operations/mrp_operations_view.xml index 834f3000472..0bcce6063f1 100644 --- a/addons/mrp_operations/mrp_operations_view.xml +++ b/addons/mrp_operations/mrp_operations_view.xml @@ -107,6 +107,7 @@
      +
      diff --git a/addons/mrp_repair/mrp_repair_view.xml b/addons/mrp_repair/mrp_repair_view.xml index 55cbe49c75b..976961693c6 100644 --- a/addons/mrp_repair/mrp_repair_view.xml +++ b/addons/mrp_repair/mrp_repair_view.xml @@ -189,6 +189,7 @@
      +
      diff --git a/addons/note/note_view.xml b/addons/note/note_view.xml index 62b007aab6e..d5af65f8763 100644 --- a/addons/note/note_view.xml +++ b/addons/note/note_view.xml @@ -117,8 +117,11 @@ - - +
      + + + +
      diff --git a/addons/portal/portal_data.xml b/addons/portal/portal_data.xml index a980552a854..90a570bc1d5 100644 --- a/addons/portal/portal_data.xml +++ b/addons/portal/portal_data.xml @@ -5,6 +5,7 @@ Company's news + public @@ -17,6 +18,7 @@ Company's jobs + public diff --git a/addons/portal/portal_demo.xml b/addons/portal/portal_demo.xml index a8727b99556..e8d0fb4159b 100644 --- a/addons/portal/portal_demo.xml +++ b/addons/portal/portal_demo.xml @@ -9,7 +9,9 @@ portal - + + + demo@portal.wrong.address diff --git a/addons/portal/security/ir.model.access.csv b/addons/portal/security/ir.model.access.csv index 2d7a17e0016..1c4ee5964bd 100644 --- a/addons/portal/security/ir.model.access.csv +++ b/addons/portal/security/ir.model.access.csv @@ -1,8 +1,2 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -access_mail_message,mail.message,mail.model_mail_message,group_portal,1,0,1,1 -access_mail_message_all,mail.message.all,mail.model_mail_message,group_portal,1,0,0,0 -access_mail_thread,mail.thread,mail.model_mail_thread,group_portal,1,0,0,0 -access_mail_followers,mail.followers,mail.model_mail_followers,group_portal,1,0,1,1 -access_mail_notification,mail.notification,mail.model_mail_notification,group_portal,1,0,1,0 -access_mail_group,mail.group,mail.model_mail_group,group_portal,1,0,0,0 -access_mail_alias,mail.alias,mail.model_mail_alias,group_portal,1,0,0,0 +access_mail_message_portal,mail.message.portal,mail.model_mail_message,group_portal,1,0,1,1 diff --git a/addons/portal/tests/test_portal.py b/addons/portal/tests/test_portal.py index 5e992fa06ea..653a05d40aa 100644 --- a/addons/portal/tests/test_portal.py +++ b/addons/portal/tests/test_portal.py @@ -21,15 +21,18 @@ from openerp.addons.mail.tests import test_mail from openerp.tools import append_content_to_html +from osv.orm import except_orm class test_portal(test_mail.TestMailMockups): def setUp(self): super(test_portal, self).setUp() + cr, uid = self.cr, self.uid self.ir_model = self.registry('ir.model') self.mail_group = self.registry('mail.group') self.mail_mail = self.registry('mail.mail') + self.mail_message = self.registry('mail.message') self.res_users = self.registry('res.users') self.res_partner = self.registry('res.partner') @@ -37,7 +40,42 @@ class test_portal(test_mail.TestMailMockups): self.group_pigs_id = self.mail_group.create(self.cr, self.uid, {'name': 'Pigs', 'description': 'Fans of Pigs, unite !'}) - def test_00_mail_invite(self): + # Find Portal group + group_portal_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'portal', 'group_portal') + self.group_portal_id = group_portal_ref and group_portal_ref[1] or False + + # Create Chell (portal user) + self.user_chell_id = self.res_users.create(cr, uid, {'name': 'Chell Gladys', 'login': 'chell', 'groups_id': [(6, 0, [self.group_portal_id])]}) + self.user_chell = self.res_users.browse(cr, uid, self.user_chell_id) + self.partner_chell_id = self.user_chell.partner_id.id + + def test_00_access_rights(self): + """ Test basic mail_message and mail_group access rights for portal users. """ + cr, uid = self.cr, self.uid + partner_chell_id = self.partner_chell_id + user_chell_id = self.user_chell_id + + # Prepare group: Pigs (portal) + self.mail_group.message_post(cr, uid, self.group_pigs_id, body='Message') + self.mail_group.write(cr, uid, [self.group_pigs_id], {'name': 'Jobs', 'public': 'groups', 'group_public_id': self.group_portal_id}) + + # ---------------------------------------- + # CASE1: Chell will use the Chatter + # ---------------------------------------- + + # Do: Chell reads Pigs messages, ok because restricted to portal group + message_ids = self.mail_group.read(cr, user_chell_id, self.group_pigs_id, ['message_ids'])['message_ids'] + self.mail_message.read(cr, user_chell_id, message_ids) + # Do: Chell posts a message on Pigs, crash because can not write on group or is not in the followers + self.assertRaises(except_orm, + self.mail_group.message_post, + cr, user_chell_id, self.group_pigs_id, body='Message') + # Do: Chell is added to Pigs followers + self.mail_group.message_subscribe(cr, uid, [self.group_pigs_id], [partner_chell_id]) + # Test: Chell posts a message on Pigs, ok because in the followers + self.mail_group.message_post(cr, user_chell_id, self.group_pigs_id, body='Message') + + def test_50_mail_invite(self): cr, uid = self.cr, self.uid user_admin = self.res_users.browse(cr, uid, uid) self.mail_invite = self.registry('mail.wizard.invite') diff --git a/addons/portal_claim/security/portal_security.xml b/addons/portal_claim/security/portal_security.xml index 9fc47c4cfb0..a88c8ce7835 100644 --- a/addons/portal_claim/security/portal_security.xml +++ b/addons/portal_claim/security/portal_security.xml @@ -5,7 +5,7 @@ Portal Personal Claims - [('partner_id','child_of',user.partner_id.id)] + [('message_follower_ids','in',user.partner_id.id)] diff --git a/addons/portal_crm/wizard/contact.py b/addons/portal_crm/wizard/contact.py index 036993e1720..b671ced2275 100644 --- a/addons/portal_crm/wizard/contact.py +++ b/addons/portal_crm/wizard/contact.py @@ -40,32 +40,51 @@ class crm_contact_us(osv.TransientModel): r = self.pool.get('res.company').search(cr, uid, [], context=context) return r - def _get_current_user_email(self, cr, uid, context=None): + def _get_user_name(self, cr, uid, context=None): """ - If the user is logged in (i.e. not anonymous), get the user's email to - pre-fill the email_from field. + If the user is logged in (i.e. not anonymous), get the user's name to + pre-fill the partner_name field. + Same goes for the other _get_user_attr methods. - @return current user's email if the user isn't "anonymous", None otherwise + @return current user's name if the user isn't "anonymous", None otherwise """ - user = self.pool.get('res.users').browse(cr, uid, uid, context=context) + user = self.pool.get('res.users').read(cr, uid, uid, ['login'], context) - if (user.login != 'anonymous'): - return user.email + if (user['login'] != 'anonymous'): + return self.pool.get('res.users').name_get(cr, uid, uid, context)[0][1] + else: + return None + + def _get_user_email(self, cr, uid, context=None): + user = self.pool.get('res.users').read(cr, uid, uid, ['login', 'email'], context) + + if (user['login'] != 'anonymous' and user['email']): + return user['email'] + else: + return None + + def _get_user_phone(self, cr, uid, context=None): + user = self.pool.get('res.users').read(cr, uid, uid, ['login', 'phone'], context) + + if (user['login'] != 'anonymous' and user['phone']): + return user['phone'] else: return None _defaults = { - 'email_from' : _get_current_user_email, - 'company_ids' : _get_companies, + 'partner_name': _get_user_name, + 'email_from': _get_user_email, + 'phone': _get_user_phone, + 'company_ids': _get_companies, } def create(self, cr, uid, values, context=None): """ - Since they potentially sensitive, we don't want any user to be able to - read datas generated through this module. That's why we'll write those - information in the crm.lead table and leave blank entries in the - portal_crm.crm_contact_us table. This is why the create() method is - overwritten. + Since they are potentially sensitive, we don't want any user to be able + to read datas generated through this module. That's why we'll write + those information directly in the crm.lead table and leave blank + entries in the portal_crm.crm_contact_us table. + This is why the create() method is overwritten. """ crm_lead = self.pool.get('crm.lead') @@ -88,11 +107,20 @@ class crm_contact_us(osv.TransientModel): def submit(self, cr, uid, ids, context=None): """ When the form is submitted, redirect the user to a "Thanks" message """ - return {'type': 'ir.actions.act_window', - 'view_mode': 'form', - 'view_type': 'form', - 'res_model': self._name, - 'res_id': ids[0], - 'view_id': self.pool.get('ir.model.data').get_object_reference(cr, uid, 'portal_crm', 'wizard_contact_form_view_thanks')[1], - 'target': 'inline' - } + return { + 'type': 'ir.actions.act_window', + 'view_mode': 'form', + 'view_type': 'form', + 'res_model': self._name, + 'res_id': ids[0], + 'view_id': self.pool.get('ir.model.data').get_object_reference(cr, uid, 'portal_crm', 'wizard_contact_form_view_thanks')[1], + 'target': 'inline', + } + + def _needaction_domain_get(self, cr, uid, context=None): + """ + This model doesn't need the needactions mechanism inherited from + crm_lead, so simply override the method to return an empty domain + and, therefore, 0 needactions. + """ + return False diff --git a/addons/portal_event/security/ir.model.access.csv b/addons/portal_event/security/ir.model.access.csv index 7418b08bb4b..fc652f4bcd1 100644 --- a/addons/portal_event/security/ir.model.access.csv +++ b/addons/portal_event/security/ir.model.access.csv @@ -1,3 +1,3 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_event,event,event.model_event_event,portal.group_portal,1,0,0,0 -access_registration,registration,event.model_event_registration,portal.group_portal,1,0,0,0 +access_registration,registration,event.model_event_registration,portal.group_portal,1,1,1,1 diff --git a/addons/portal_event/security/portal_security.xml b/addons/portal_event/security/portal_security.xml index 74b2372c147..8f17808345b 100644 --- a/addons/portal_event/security/portal_security.xml +++ b/addons/portal_event/security/portal_security.xml @@ -12,7 +12,7 @@ Portal Personal Registrations - [('partner_id','child_of',user.partner_id.id)] + [('user_id','=',user.id)] diff --git a/addons/portal_project/security/portal_security.xml b/addons/portal_project/security/portal_security.xml index b9d37cec099..316fb1b4a15 100644 --- a/addons/portal_project/security/portal_security.xml +++ b/addons/portal_project/security/portal_security.xml @@ -5,7 +5,7 @@ Portal Personal Task - [('partner_id','child_of',user.partner_id.id)] + [('message_follower_ids','in',user.partner_id.id)] diff --git a/addons/portal_project_issue/security/portal_security.xml b/addons/portal_project_issue/security/portal_security.xml index 358b5e6a0ad..d58e160051b 100644 --- a/addons/portal_project_issue/security/portal_security.xml +++ b/addons/portal_project_issue/security/portal_security.xml @@ -5,7 +5,7 @@ Portal Personal Issues - [('partner_id','child_of',user.partner_id.id)] + [('message_follower_ids','in',user.partner_id.id)] diff --git a/addons/portal_sale/security/portal_security.xml b/addons/portal_sale/security/portal_security.xml index 2f161a73281..d27317d0305 100644 --- a/addons/portal_sale/security/portal_security.xml +++ b/addons/portal_sale/security/portal_security.xml @@ -5,43 +5,43 @@ Portal Personal Quotations/Sales Orders - - [('partner_id','child_of',user.partner_id.id)] + + [('message_follower_ids','in',user.partner_id.id)] Portal Personal Delivery Orders - [('partner_id','child_of',[user.partner_id.id])] + [('message_follower_ids','in',user.partner_id.id)] Portal Personal Delivery Orders Out - - [('partner_id','child_of',[user.partner_id.id])] + + [('message_follower_ids','in',user.partner_id.id)] Portal Personal Account Invoices - - [('partner_id','child_of',user.partner_id.id)] + + [('message_follower_ids','in',user.partner_id.id)] Portal Personal Payments - - [('partner_id','child_of',user.partner_id.id)] + + [('message_follower_ids','in',user.partner_id.id)] Portal Personal Contacts - - [('id','child_of',user.partner_id.id)] + + [('message_follower_ids','in',user.partner_id.id)] diff --git a/addons/procurement/procurement_view.xml b/addons/procurement/procurement_view.xml index bf73b383216..34715f19e11 100644 --- a/addons/procurement/procurement_view.xml +++ b/addons/procurement/procurement_view.xml @@ -103,6 +103,7 @@
      +
      diff --git a/addons/product/product_view.xml b/addons/product/product_view.xml index 8c19aee026f..8ffa4732c13 100644 --- a/addons/product/product_view.xml +++ b/addons/product/product_view.xml @@ -203,6 +203,7 @@
      +
      diff --git a/addons/project/project_view.xml b/addons/project/project_view.xml index 18d74652187..540884e85b6 100644 --- a/addons/project/project_view.xml +++ b/addons/project/project_view.xml @@ -150,6 +150,7 @@
      +
      @@ -467,6 +468,7 @@
      +
      diff --git a/addons/project_issue/project_issue_demo.xml b/addons/project_issue/project_issue_demo.xml index 0a4eee3067b..9759d15dabb 100644 --- a/addons/project_issue/project_issue_demo.xml +++ b/addons/project_issue/project_issue_demo.xml @@ -31,7 +31,7 @@
      - + 3 diff --git a/addons/project_issue/project_issue_view.xml b/addons/project_issue/project_issue_view.xml index 3214b93c8d3..e41495590d7 100644 --- a/addons/project_issue/project_issue_view.xml +++ b/addons/project_issue/project_issue_view.xml @@ -161,6 +161,7 @@
      +
      diff --git a/addons/purchase/purchase_view.xml b/addons/purchase/purchase_view.xml index 7702d182b09..23bb8e7e278 100644 --- a/addons/purchase/purchase_view.xml +++ b/addons/purchase/purchase_view.xml @@ -285,6 +285,7 @@
      +
      diff --git a/addons/purchase_requisition/purchase_requisition_view.xml b/addons/purchase_requisition/purchase_requisition_view.xml index 1fafec92bc7..eab920113a8 100644 --- a/addons/purchase_requisition/purchase_requisition_view.xml +++ b/addons/purchase_requisition/purchase_requisition_view.xml @@ -102,8 +102,9 @@
      - - + + +
      diff --git a/addons/sale/sale_view.xml b/addons/sale/sale_view.xml index d97c9924036..343f06c2d69 100644 --- a/addons/sale/sale_view.xml +++ b/addons/sale/sale_view.xml @@ -310,6 +310,7 @@
      +
      diff --git a/addons/stock/stock_view.xml b/addons/stock/stock_view.xml index 83fed186f42..0f10fe4e999 100644 --- a/addons/stock/stock_view.xml +++ b/addons/stock/stock_view.xml @@ -917,6 +917,7 @@
      +
      @@ -1043,6 +1044,7 @@
      +