diff --git a/addons/share/__init__.py b/addons/share/__init__.py new file mode 100644 index 00000000000..eff88be137d --- /dev/null +++ b/addons/share/__init__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2010 Tiny SPRL (). +# +# 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 . +# +############################################################################## +import share +import wizard diff --git a/addons/share/__openerp__.py b/addons/share/__openerp__.py new file mode 100644 index 00000000000..e6762e91ef2 --- /dev/null +++ b/addons/share/__openerp__.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2010 Tiny SPRL (). +# +# 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 . +# +############################################################################## + + +{ + "name" : "Share Management", + "version" : "1.1", + "depends" : ["base"], + "author" : "Tiny", + "category": 'Generic Modules', + "description": """The goal is to implement a generic sharing mechanism, where user of OpenERP +can share data from OpenERP to their colleagues, customers, or friends. +The system will work by creating new users and groups on the fly, and by +combining the appropriate access rights and ir.rules to ensure that the /shared +users/ will only have access to the correct data. + """, + 'website': 'http://www.openerp.com', + 'init_xml': [], + 'update_xml': [ + 'share_view.xml', + 'wizard/share_wizard_view.xml' + ], + 'installable': True, + 'active': False, +} +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/share/share.py b/addons/share/share.py new file mode 100644 index 00000000000..6e3c4932a1e --- /dev/null +++ b/addons/share/share.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2010 Tiny SPRL (). +# +# 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 osv import fields, osv, orm +class res_groups(osv.osv): + _name = "res.groups" + _inherit = 'res.groups' + _columns = { + 'share': fields.boolean('Share') + } +res_groups() + +class res_users(osv.osv): + _name = 'res.users' + _inherit = 'res.users' + _columns = { + 'share': fields.boolean('Share') + } +res_users() diff --git a/addons/share/share_view.xml b/addons/share/share_view.xml new file mode 100644 index 00000000000..24736b3d53a --- /dev/null +++ b/addons/share/share_view.xml @@ -0,0 +1,27 @@ + + + + + res.groups.form + res.groups + form + + + + + + + + + res.users.form + res.users + form + + + + + + + + + diff --git a/addons/share/wizard/__init__.py b/addons/share/wizard/__init__.py new file mode 100644 index 00000000000..b906abf5e56 --- /dev/null +++ b/addons/share/wizard/__init__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2010 Tiny SPRL (). +# +# 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 . +# +############################################################################## + +import share_wizard diff --git a/addons/share/wizard/share_wizard.py b/addons/share/wizard/share_wizard.py new file mode 100644 index 00000000000..c5e2aef2ff6 --- /dev/null +++ b/addons/share/wizard/share_wizard.py @@ -0,0 +1,430 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2010 Tiny SPRL (). +# +# 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 osv import osv, fields +from tools.translate import _ +import tools + +def _generate_random_number(): + import random + RANDOM_PASS_CHARACTERS = [chr(x) for x in range(48,58) + range(97,123) + range(65,91)] + RANDOM_PASS_CHARACTERS.remove('l') #lowercase l, easily mistaken as one or capital i + RANDOM_PASS_CHARACTERS.remove('I') #uppercase i, easily mistaken as one or lowercase l + RANDOM_PASS_CHARACTERS.remove('O') #uppercase o, mistaken with zero + RANDOM_PASS_CHARACTERS.remove('o') #lowercase o, mistaken with zero + RANDOM_PASS_CHARACTERS.remove('0') #zero, mistaken with o-letter + def generate_random_pass(): + pass_chars = RANDOM_PASS_CHARACTERS[:] + random.shuffle(pass_chars) + return ''.join(pass_chars[0:10]) + return generate_random_pass() + +class share_create(osv.osv_memory): + _name = 'share.create' + _description = 'Create share' + + def _access(self, cr, uid, ids, field_name, arg, context=None): + if context is None: + context = {} + res = {} + action_id = context.get('action_id', False) + access_obj = self.pool.get('ir.model.access') + action_obj = self.pool.get('ir.actions.act_window') + model_obj = self.pool.get('ir.model') + user_obj = self.pool.get('res.users') + current_user = user_obj.browse(cr, uid, uid) + access_ids = [] + if action_id: + action = action_obj.browse(cr, uid, action_id, context=context) + active_model_ids = model_obj.search(cr, uid, [('model','=',action.res_model)]) + active_model_id = active_model_ids and active_model_ids[0] or False + access_ids = access_obj.search(cr, uid, [ + ('group_id','in',map(lambda x:x.id, current_user.groups_id)), + ('model_id','',active_model_id)]) + for rec_id in ids: + write_access = False + read_access = False + for access in access_obj.browse(cr, uid, access_ids, context=context): + if access.perm_write: + write_access = True + if access.perm_read: + read_access = True + res[rec_id]['write_access'] = write_access + res[rec_id]['read_access'] = read_access + return res + + _columns = { + 'action_id': fields.many2one('ir.actions.act_window', 'Action', required=True), + 'domain': fields.char('Domain', size=64), + 'user_type': fields.selection( [ ('existing','Existing'),('new','New')],'User Type'), + 'user_ids': fields.many2many('res.users', 'share_user_rel', 'share_id','user_id', 'Share Users'), + 'new_user': fields.text("New user"), + 'access_mode': fields.selection( [ ('readonly','READ ONLY'),('readwrite','READ & WRITE')],'Access Mode'), + 'write_access': fields.function(_access, method=True, string='Write Access',type='boolean', multi='write_access'), + 'read_access': fields.function(_access, method=True, string='Write Access',type='boolean', multi='read_access'), + } + _defaults = { + 'user_type' : 'existing', + 'domain': '[]', + 'access_mode': 'readonly' + } + + def default_get(self, cr, uid, fields, context=None): + """ + To get default values for the object. + """ + + res = super(share_create, self).default_get(cr, uid, fields, context=context) + if not context: + context={} + action_id = context.get('action_id', False) + domain = context.get('domain', '[]') + + + if 'action_id' in fields: + res['action_id'] = action_id + if 'domain' in fields: + res['domain'] = domain + return res + + def do_step_1(self, cr, uid, ids, context=None): + """ + This action to excute step 1 + + """ + if not context: + context = {} + + data_obj = self.pool.get('ir.model.data') + + step1_form_view = data_obj._get_id(cr, uid, 'share', 'share_step1_form') + + if step1_form_view: + step1_form_view_id = data_obj.browse(cr, uid, step1_form_view, context=context).res_id + + step1_id = False + for this in self.browse(cr, uid, ids, context=context): + vals ={ + 'domain': this.domain, + 'action_id': this.action_id and this.action_id.id or False, + } + + context.update(vals) + value = { + 'name': _('Step:2 Sharing Wizard'), + 'view_type': 'form', + 'view_mode': 'form', + 'res_model': 'share.create', + 'view_id': False, + 'views': [(step1_form_view_id, 'form'), (False, 'tree'), (False, 'calendar'), (False, 'graph')], + 'type': 'ir.actions.act_window', + 'context': context, + 'target': 'new' + } + return value + + + def do_step_2(self, cr, uid, ids, context=None): + """ + This action to excute step 2 + + """ + if not context: + context = {} + + data_obj = self.pool.get('ir.model.data') + + step2_form_view = data_obj._get_id(cr, uid, 'share', 'share_step2_form') + + if step2_form_view: + step2_form_view_id = data_obj.browse(cr, uid, step2_form_view, context=context).res_id + + step1_id = False + for this in self.browse(cr, uid, ids, context=context): + vals ={ + 'user_type': this.user_type, + 'existing_user_ids': map(lambda x:x.id, this.user_ids), + 'new_user': this.new_user, + 'domain': this.domain, + 'access_mode': this.access_mode, + 'action_id': this.action_id and this.action_id.id or False + } + + context.update(vals) + value = { + 'name': _('Step:3 Sharing Wizard'), + 'view_type': 'form', + 'view_mode': 'form', + 'res_model': 'share.create', + 'view_id': False, + 'views': [(step2_form_view_id, 'form'), (False, 'tree'), (False, 'calendar'), (False, 'graph')], + 'type': 'ir.actions.act_window', + 'context': context, + 'target': 'new' + } + return value + + + def do_step_3(self, cr, uid, ids, context=None): + """ + This action to excute step 3 + + """ + if not context: + context = {} + + group_obj = self.pool.get('res.groups') + user_obj = self.pool.get('res.users') + fields_obj = self.pool.get('ir.model.fields') + model_access_obj = self.pool.get('ir.model.access') + model_obj = self.pool.get('ir.model') + rule_obj = self.pool.get('ir.rule') + action_obj = self.pool.get('ir.actions.act_window') + + new_users = context.get('new_user', False) + action_id = context.get('action_id', False) + user_type = context.get('user_type', False) + access_mode = context.get('access_mode', False) + action = action_obj.browse(cr, uid, action_id, context=context) + active_model = action.res_model + + active_id = False #TODO: Pass record id of res_model of action + existing_user_ids = context.get('existing_user_ids', False) + domain = eval(context.get('domain', '[]')) + + # Create Share Group + share_group_name = '%s: %s' %('Sharing', active_model) + group_ids = group_obj.search(cr, uid, [('name','=',share_group_name)]) + group_id = group_ids and group_ids[0] or False + if not group_id: + group_id = group_obj.create(cr, uid, {'name': share_group_name, 'share': True}) + else: + group = group_obj.browse(cr, uid, group_id, context=context) + if not group.share: + raise osv.except_osv(_('Error'), _("Share Group is exits without sharing !")) + + # Create new user + + current_user = user_obj.browse(cr, uid, uid) + user_ids = [] + if user_type == 'new' and new_users: + for new_user in new_users.split('\n'): + password = _generate_random_number() + user_id = user_obj.create(cr, uid, { + 'login': new_user, + 'password': password, + 'name': new_user, + 'user_email': new_user, + 'groups_id': [(6,0,[group_id])], + 'action_id': action_id, + 'share': True, + 'company_id': current_user.company_id and current_user.company_id.id}) + user_ids.append(user_id) + context['new_user_ids'] = user_ids + + # Modify existing user + if user_type == 'existing': + user_obj.write(cr, uid, existing_user_ids , { + 'groups_id': [(4,group_id)], + 'action_id': action_id + }) + + + #ACCESS RIGHTS / IR.RULES COMPUTATION + #TODO: TO Resolve Recurrent Problems + active_model_ids = model_obj.search(cr, uid, [('model','=',active_model)]) + active_model_id = active_model_ids and active_model_ids[0] or False + + def _get_relation(model_id, ttypes, new_obj=[]): + obj = [] + models = map(lambda x:x[1].model, new_obj) + field_ids = fields_obj.search(cr, uid, [('model_id','=',model_id),('ttype','in', ttypes)]) + for field in fields_obj.browse(cr, uid, field_ids, context=context): + if field.relation not in models: + relation_model_ids = model_obj.search(cr, uid, [('model','=',field.relation)]) + relation_model_id = relation_model_ids and relation_model_ids[0] or False + relation_model = model_obj.browse(cr, uid, relation_model_id, context=context) + obj.append((field.relation_field, relation_model)) + + if relation_model_id != model_id and field.ttype in ['one2many', 'many2many']: + obj += _get_relation(relation_model_id, [field.ttype], obj) + + return obj + + obj0 = model_obj.browse(cr, uid, active_model_id, context=context) + obj1 = _get_relation(active_model_id, ['one2many']) + obj2 = _get_relation(active_model_id, ['one2many', 'many2many']) + obj3 = _get_relation(active_model_id, ['many2one']) + for rel_field, model in obj1: + obj3 += _get_relation(model.id, ['many2one']) + if access_mode == 'readonly': + for rel_field, model in obj1+obj2+obj3: + model_access_obj.create(cr, uid, { + 'name': 'Read Access of group %s on %s model'%(share_group_name, model.name), + 'model_id' : model.id, + 'group_id' : group_id, + 'perm_read' : True + }) + if access_mode == 'readwrite': + for rel_field, model in obj1+obj2+obj3: + model_access_obj.create(cr, uid, { + 'name': 'Read Access of group %s on %s model'%(share_group_name, model.name), + 'model_id' : model.id, + 'group_id' : group_id, + 'perm_read' : True, + 'perm_write' : True + }) + for rel_field, model in obj2+obj3: + model_access_obj.create(cr, uid, { + 'name': 'Read Access of group %s on %s model'%(share_group_name, model.name), + 'model_id' : model.id, + 'group_id' : group_id, + 'perm_read' : True + }) + + rule_obj.create(cr, uid, { + 'name': '%s-%s'%(share_group_name, obj0.model), + 'model_id': obj0.id, + 'domain': domain, + 'group_ids': [(6,0,[group_id])] + }) + for rel_field, model in obj1: + obj1_domain = [] + for opr1, opt, opr2 in domain: + new_opr1 = '%s.%s'%(rel_field, opr1) + obj1_domain.append((new_opr1, opt, opr2)) + + rule_obj.create(cr, uid, { + 'name': '%s-%s'%(share_group_name, model.model), + 'model_id': model.id, + 'domain': obj1_domain, + 'groups': [(6,0,[group_id])] + }) + # TODO: + # And on OBJ0, OBJ1, OBJ2, OBJ3: add all rules from groups of the user + # that is sharing in the many2many of the rules on the new group + # (rule must be copied instead of adding it if it contains a reference to uid + # or user.xxx so it can be replaced correctly) + context['share_model'] = active_model + context['share_rec_id'] = active_id + + + data_obj = self.pool.get('ir.model.data') + + form_view = data_obj._get_id(cr, uid, 'share', 'share_result_form') + form_view_id = False + if form_view: + form_view_id = data_obj.browse(cr, uid, form_view, context=context).res_id + + + value = { + 'name': _('Step:4 Share Users Detail'), + 'view_type': 'form', + 'view_mode': 'form', + 'res_model': 'share.result', + 'view_id': False, + 'views': [(form_view_id, 'form'), (False, 'tree'), (False, 'calendar'), (False, 'graph')], + 'type': 'ir.actions.act_window', + 'context': context, + 'target': 'new' + } + return value +share_create() + + +class share_result(osv.osv_memory): + _name = "share.result" + _columns = { + 'users': fields.text("Users", readonly=True), + } + + + + def do_send_email(self, cr, uid, ids, context=None): + user_obj = self.pool.get('res.users') + if not context: + context={} + existing_user_ids = context.get('existing_user_ids', []) + new_user_ids = context.get('new_user_ids', []) + share_url = tools.config.get('share_root_url', False) + user = user_obj.browse(cr, uid, uid, context=context) + for share_user in user_obj.browse(cr, uid, new_user_ids+existing_user_ids, context=context): + email_to = share_user.user_email + subject = '%s wants to share private data with you' %(user.name) + body = """ + Dear, + + %s wants to share private data from OpenERP with you! + """%(user.name) + if share_url: + body += """ + To view it, you can access the following URL: + %s + """%(user.name, share_url) + if share_user.id in new_user_ids: + body += """ + You may use the following login and password to get access to this + protected area: + login: %s + password: %s + """%(user.login, user.password) + elif share_user.id in existing_user_ids: + body += """ + You may use your existing login and password to get access to this + additional data. As a reminder, your login is %s. + """%(user.name) + + flag = tools.email_send( + user.user_email, + email_to, + subject, + body + ) + return flag + + + def default_get(self, cr, uid, fields, context=None): + """ + To get default values for the object. + """ + + res = super(share_result, self).default_get(cr, uid, fields, context=context) + user_obj = self.pool.get('res.users') + if not context: + context={} + existing_user_ids = context.get('existing_user_ids', []) + new_user_ids = context.get('new_user_ids', []) + share_url = tools.config.get('share_root_url', False) + if 'users' in fields: + users = [] + for user in user_obj.browse(cr, uid, new_user_ids): + txt = 'Login: %s Password: %s' %(user.login, user.password) + if share_url: + txt += ' Share URL: %s' %(share_url) + users.append(txt) + for user in user_obj.browse(cr, uid, existing_user_ids): + txt = 'Login: %s' %(user.login) + if share_url: + txt += ' Share URL: %s' %(share_url) + users.append(txt) + res['users'] = '\n'.join(users) + return res + +share_result() diff --git a/addons/share/wizard/share_wizard_view.xml b/addons/share/wizard/share_wizard_view.xml new file mode 100644 index 00000000000..9e9b23f7f43 --- /dev/null +++ b/addons/share/wizard/share_wizard_view.xml @@ -0,0 +1,108 @@ + + + + + + share.step0.form + share.create + form + +
+ + + + + +