diff --git a/ckan/logic/action/create.py b/ckan/logic/action/create.py index edef0a95f30..5e1e2a477e0 100644 --- a/ckan/logic/action/create.py +++ b/ckan/logic/action/create.py @@ -843,19 +843,25 @@ def user_create(context, data_dict): def user_invite(context, data_dict): '''Invite a new user. - You must be authorized to invite users. + You must be authorized to create organization members. - :param email: the email address for the new user + :param email: the email of the user to be invited to the organization :type email: string + :param organization_id: the id or name of the organization + :type organization_id: string + :param role: role of the user in the group. One of ``member``, ``editor``, + or ``admin`` + :type role: string :returns: the newly created yser :rtype: dictionary - ''' _check_access('user_invite', context, data_dict) user_invite_schema = { - 'email': [validators.not_empty, unicode] + 'email': [validators.not_empty, unicode], + 'organization_id': [validators.not_empty], + 'role': [validators.not_empty], } _, errors = _validate(data_dict, user_invite_schema, context) if errors: @@ -870,6 +876,12 @@ def user_invite(context, data_dict): data_dict['state'] = ckan.model.State.PENDING user_dict = _get_action('user_create')(context, data_dict) user = ckan.model.User.get(user_dict['id']) + member_dict = { + 'username': user.id, + 'id': data_dict['organization_id'], + 'role': data_dict['role'] + } + _get_action('organization_member_create')(context, member_dict) mailer.send_invite(user) return model_dictize.user_dictize(user, context) except ValidationError as e: @@ -1187,7 +1199,7 @@ def _group_or_org_member_create(context, data_dict, is_org=False): role = data_dict.get('role') group_id = data_dict.get('id') group = model.Group.get(group_id) - result = session.query(model.User).filter_by(name=username).first() + result = model.User.get(username) if result: user_id = result.id else: diff --git a/ckan/logic/auth/create.py b/ckan/logic/auth/create.py index fafbc5a2e4c..55ec2d7df6f 100644 --- a/ckan/logic/auth/create.py +++ b/ckan/logic/auth/create.py @@ -113,7 +113,7 @@ def user_create(context, data_dict=None): return {'success': True} def user_invite(context, data_dict=None): - return {'success': False} + return organization_member_create(context, data_dict) def _check_group_auth(context, data_dict): # FIXME This code is shared amoung other logic.auth files and should be diff --git a/ckan/model/user.py b/ckan/model/user.py index a6d6c44b612..4f87175edf4 100644 --- a/ckan/model/user.py +++ b/ckan/model/user.py @@ -181,20 +181,21 @@ def is_deleted(self): def is_pending(self): return self.state == core.State.PENDING - def is_in_group(self, group): - return group in self.get_group_ids() + def is_in_group(self, group_id): + return group_id in self.get_group_ids() - def is_in_groups(self, groupids): + def is_in_groups(self, group_ids): ''' Given a list of group ids, returns True if this user is in any of those groups ''' guser = set(self.get_group_ids()) - gids = set(groupids) + gids = set(group_ids) return len(guser.intersection(gids)) > 0 - def get_group_ids(self, group_type=None): + def get_group_ids(self, group_type=None, capacity=None): ''' Returns a list of group ids that the current user belongs to ''' - return [g.id for g in self.get_groups(group_type=group_type)] + return [g.id for g in + self.get_groups(group_type=group_type, capacity=capacity)] def get_groups(self, group_type=None, capacity=None): import ckan.model as model diff --git a/ckan/tests/lib/test_mailer.py b/ckan/tests/lib/test_mailer.py index 22b3985930b..83a35c3b92c 100644 --- a/ckan/tests/lib/test_mailer.py +++ b/ckan/tests/lib/test_mailer.py @@ -114,8 +114,10 @@ def test_send_reset_email(self): # reset link tested in user functional test def test_send_invite_email(self): + user = model.User.by_name(u'bob') + assert user.reset_key is None, user # send email - mailer.send_invite(model.User.by_name(u'bob')) + mailer.send_invite(user) # check it went to the mock smtp server msgs = self.get_smtp_messages() @@ -127,5 +129,6 @@ def test_send_invite_email(self): expected_body = self.mime_encode(test_msg, u'bob') assert expected_body in msg[3], '%r not in %r' % (expected_body, msg[3]) + assert user.reset_key is not None, user # reset link tested in user functional test diff --git a/ckan/tests/logic/test_action.py b/ckan/tests/logic/test_action.py index b6361803f0e..f2ef0df9f7d 100644 --- a/ckan/tests/logic/test_action.py +++ b/ckan/tests/logic/test_action.py @@ -562,12 +562,18 @@ def test_12_user_update_errors(self): for expected_message in test_call['messages']: assert expected_message[1] in ''.join(res_obj['error'][expected_message[0]]) - @mock.patch('ckan.lib.mailer.mail_user') - def test_user_invite(self, mail_user): + @mock.patch('ckan.lib.mailer.send_invite') + def test_user_invite(self, send_invite): email_username = 'invited_user$ckan' email = '%s@email.com' % email_username - user_dict = {'email': email} - postparams = '%s=1' % json.dumps(user_dict) + organization_name = 'an_organization' + CreateTestData.create_groups([{'name': organization_name}]) + role = 'member' + organization = model.Group.get(organization_name) + params = {'email': email, + 'organization_id': organization.id, + 'role': role} + postparams = '%s=1' % json.dumps(params) extra_environ = {'Authorization': str(self.sysadmin_user.apikey)} res = self.app.post('/api/action/user_invite', params=postparams, @@ -575,27 +581,14 @@ def test_user_invite(self, mail_user): res_obj = json.loads(res.body) user = model.User.get(res_obj['result']['id']) - expected_username = email_username.replace('$', '-') assert res_obj['success'] is True, res_obj assert user.email == email, (user.email, email) - assert user.name.startswith(expected_username), (user.name, expected_username) assert user.is_pending(), user - assert user.reset_key is not None, user - - @mock.patch('ckan.lib.mailer.send_invite') - def test_user_invite_sends_email(self, send_invite): - email_username = 'invited_user' - email = '%s@email.com' % email_username - user_dict = {'email': email} - postparams = '%s=1' % json.dumps(user_dict) - extra_environ = {'Authorization': str(self.sysadmin_user.apikey)} - - res = self.app.post('/api/action/user_invite', params=postparams, - extra_environ=extra_environ) - - res_obj = json.loads(res.body) - user = model.User.get(res_obj['result']['id']) - assert res_obj['success'] is True, res_obj + expected_username = email_username.replace('$', '-') + assert user.name.startswith(expected_username), (user.name, + expected_username) + group_ids = user.get_group_ids(capacity=role) + assert organization.id in group_ids, (group_ids, organization.id) assert send_invite.called assert send_invite.call_args[0][0].id == res_obj['result']['id'] @@ -618,8 +611,14 @@ def test_user_invite_should_work_even_if_tried_username_already_exists(self, ran patcher = mock.patch('ckan.lib.mailer.mail_user') patcher.start() email = 'invited_user@email.com' - user_dict = {'email': email} - postparams = '%s=1' % json.dumps(user_dict) + organization_name = 'an_organization' + CreateTestData.create_groups([{'name': organization_name}]) + role = 'member' + organization = model.Group.get(organization_name) + params = {'email': email, + 'organization_id': organization.id, + 'role': role} + postparams = '%s=1' % json.dumps(params) extra_environ = {'Authorization': str(self.sysadmin_user.apikey)} usernames = ['first', 'first', 'second'] diff --git a/ckan/tests/logic/test_auth.py b/ckan/tests/logic/test_auth.py index 13e7b38f2a5..9ae69f32081 100644 --- a/ckan/tests/logic/test_auth.py +++ b/ckan/tests/logic/test_auth.py @@ -1,3 +1,5 @@ +import mock + import ckan.tests as tests from ckan.logic import get_action import ckan.model as model @@ -49,11 +51,10 @@ def create_user(self, name): class TestAuthUsers(TestAuth): - def test_only_sysadmins_can_invite_users(self): - username = 'normal_user' - self.create_user(username) - - assert not new_authz.is_authorized_boolean('user_invite', {'user': username}) + @mock.patch('ckan.logic.auth.create.organization_member_create') + def test_invite_user_delegates_to_organization_member_create(self, organization_member_create): + new_authz.is_authorized_boolean('user_invite', {}) + organization_member_create.assert_called() def test_only_sysadmins_can_delete_users(self): username = 'username'