user-groups: Add create API endpoint.

Significantly modified by tabbott for better security structure.
This commit is contained in:
Umair Khan
2017-11-01 14:04:16 +05:00
committed by Tim Abbott
parent 39ca38837e
commit 1bbe84af49
7 changed files with 116 additions and 2 deletions

View File

@@ -4,6 +4,7 @@ from typing import (
) )
from mypy_extensions import TypedDict from mypy_extensions import TypedDict
import django.db.utils
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.utils.html import escape from django.utils.html import escape
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
@@ -48,6 +49,7 @@ from zerver.lib.topic_mutes import (
add_topic_mute, add_topic_mute,
remove_topic_mute, remove_topic_mute,
) )
from zerver.lib.user_groups import create_user_group
from zerver.models import Realm, RealmEmoji, Stream, UserProfile, UserActivity, \ from zerver.models import Realm, RealmEmoji, Stream, UserProfile, UserActivity, \
RealmDomain, \ RealmDomain, \
Subscription, Recipient, Message, Attachment, UserMessage, RealmAuditLog, \ Subscription, Recipient, Message, Attachment, UserMessage, RealmAuditLog, \
@@ -4183,3 +4185,10 @@ def do_update_user_custom_profile_data(user_profile, data):
update_or_create(user_profile=user_profile, update_or_create(user_profile=user_profile,
field_id=field['id'], field_id=field['id'],
defaults={'value': field['value']}) defaults={'value': field['value']})
def check_add_user_group(realm, name, initial_members, description):
# type: (Realm, Text, List[UserProfile], Text) -> None
try:
create_user_group(name, initial_members, realm, description=description)
except django.db.utils.IntegrityError:
raise JsonableError(_("User group '%s' already exists." % (name,)))

View File

@@ -1,9 +1,18 @@
from __future__ import absolute_import from __future__ import absolute_import
from django.db import transaction from django.db import transaction
from django.utils.translation import ugettext as _
from zerver.lib.exceptions import JsonableError
from zerver.models import UserProfile, Realm, UserGroupMembership, UserGroup from zerver.models import UserProfile, Realm, UserGroupMembership, UserGroup
from typing import Dict, Iterable, List, Text from typing import Dict, Iterable, List, Text
def access_user_group_by_id(user_group_id: int, realm: Realm) -> UserGroup:
try:
user_group = UserGroup.objects.get(id=user_group_id, realm=realm)
except UserGroup.DoesNotExist:
raise JsonableError(_("Invalid user group"))
return user_group
def user_groups_in_realm(realm): def user_groups_in_realm(realm):
# type: (Realm) -> List[UserGroup] # type: (Realm) -> List[UserGroup]
user_groups = UserGroup.objects.filter(realm=realm) user_groups = UserGroup.objects.filter(realm=realm)

View File

@@ -1,10 +1,11 @@
from typing import Text from typing import List, Text
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from zerver.lib.actions import do_change_full_name from zerver.lib.actions import do_change_full_name
from zerver.lib.request import JsonableError from zerver.lib.request import JsonableError
from zerver.models import UserProfile, Service from zerver.models import UserProfile, Service, Realm, \
get_user_profile_by_id
def check_full_name(full_name_raw): def check_full_name(full_name_raw):
# type: (Text) -> Text # type: (Text) -> Text
@@ -43,3 +44,20 @@ def check_valid_interface_type(interface_type):
# type: (int) -> None # type: (int) -> None
if interface_type not in Service.ALLOWED_INTERFACE_TYPES: if interface_type not in Service.ALLOWED_INTERFACE_TYPES:
raise JsonableError(_('Invalid interface type')) raise JsonableError(_('Invalid interface type'))
def user_ids_to_users(user_ids: List[int], realm: Realm) -> List[UserProfile]:
# TODO: Change this to do a single bulk query with
# generic_bulk_cached_fetch; it'll be faster.
#
# TODO: Consider adding a flag to control whether deactivated
# users should be included.
user_profiles = []
for user_id in user_ids:
try:
user_profile = get_user_profile_by_id(user_id)
except UserProfile.DoesNotExist:
raise JsonableError(_("Invalid user ID: %s" % (user_id,)))
if user_profile.realm != realm:
raise JsonableError(_("Invalid user ID: %s" % (user_id,)))
user_profiles.append(user_profile)
return user_profiles

View File

@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from typing import Any, List, Optional, Text from typing import Any, List, Optional, Text
import ujson
import django import django
import mock import mock
@@ -55,3 +56,39 @@ class UserGroupTestCase(ZulipTestCase):
with mock.patch('zerver.lib.user_groups.remove_user_from_user_group', with mock.patch('zerver.lib.user_groups.remove_user_from_user_group',
side_effect=Exception): side_effect=Exception):
self.assertFalse(check_remove_user_from_user_group(othello, user_group)) self.assertFalse(check_remove_user_from_user_group(othello, user_group))
class UserGroupAPITestCase(ZulipTestCase):
def test_user_group_create(self):
# type: () -> None
hamlet = self.example_user('hamlet')
# Test success
self.login(self.example_email("hamlet"))
params = {
'name': 'support',
'members': ujson.dumps([hamlet.id]),
'description': 'Support team',
}
result = self.client_post('/json/user_groups/create', info=params)
self.assert_json_success(result)
self.assert_length(UserGroup.objects.all(), 1)
# Test invalid member error
params = {
'name': 'backend',
'members': ujson.dumps([1111]),
'description': 'Backend team',
}
result = self.client_post('/json/user_groups/create', info=params)
self.assert_json_error(result, "Invalid user ID: 1111")
self.assert_length(UserGroup.objects.all(), 1)
# Test we cannot add hamlet again
params = {
'name': 'support',
'members': ujson.dumps([hamlet.id]),
'description': 'Support team',
}
result = self.client_post('/json/user_groups/create', info=params)
self.assert_json_error(result, "User group 'support' already exists.")
self.assert_length(UserGroup.objects.all(), 1)

View File

@@ -24,6 +24,7 @@ from zerver.models import UserProfile, Recipient, \
from zerver.lib.avatar import avatar_url from zerver.lib.avatar import avatar_url
from zerver.lib.email_mirror import create_missed_message_address from zerver.lib.email_mirror import create_missed_message_address
from zerver.lib.exceptions import JsonableError
from zerver.lib.send_email import send_future_email from zerver.lib.send_email import send_future_email
from zerver.lib.actions import ( from zerver.lib.actions import (
get_emails_from_user_ids, get_emails_from_user_ids,
@@ -35,6 +36,7 @@ from zerver.lib.actions import (
) )
from zerver.lib.topic_mutes import add_topic_mute from zerver.lib.topic_mutes import add_topic_mute
from zerver.lib.stream_topic import StreamTopicTarget from zerver.lib.stream_topic import StreamTopicTarget
from zerver.lib.users import user_ids_to_users
from django.conf import settings from django.conf import settings
@@ -265,6 +267,18 @@ class UserProfileTest(ZulipTestCase):
self.assertFalse(m.called) self.assertFalse(m.called)
def test_user_ids_to_users(self) -> None:
real_user_ids = [
self.example_user('hamlet').id,
self.example_user('cordelia').id,
]
user_ids_to_users(real_user_ids, get_realm("zulip"))
with self.assertRaises(JsonableError):
user_ids_to_users([1234], get_realm("zephyr"))
with self.assertRaises(JsonableError):
user_ids_to_users(real_user_ids, get_realm("zephyr"))
class ActivateTest(ZulipTestCase): class ActivateTest(ZulipTestCase):
def test_basics(self) -> None: def test_basics(self) -> None:
user = self.example_user('hamlet') user = self.example_user('hamlet')

View File

@@ -0,0 +1,22 @@
from django.http import HttpResponse, HttpRequest
from typing import List, Text
from zerver.context_processors import get_realm_from_request
from zerver.lib.actions import check_add_user_group
from zerver.lib.request import has_request_variables, REQ
from zerver.lib.response import json_success, json_error
from zerver.lib.users import user_ids_to_users
from zerver.lib.validator import check_list, check_string, check_int, \
check_short_string
from zerver.models import UserProfile
@has_request_variables
def add_user_group(request, user_profile,
name=REQ(),
members=REQ(validator=check_list(check_int), default=[]),
description=REQ()):
# type: (HttpRequest, UserProfile, Text, List[int], Text) -> HttpResponse
user_profiles = user_ids_to_users(members, user_profile.realm)
check_add_user_group(user_profile.realm, name, user_profiles, description)
return json_success()

View File

@@ -27,6 +27,7 @@ import zerver.views.zephyr
import zerver.views.users import zerver.views.users
import zerver.views.unsubscribe import zerver.views.unsubscribe
import zerver.views.integrations import zerver.views.integrations
import zerver.views.user_groups
import zerver.views.user_settings import zerver.views.user_settings
import zerver.views.muting import zerver.views.muting
import zerver.views.streams import zerver.views.streams
@@ -214,6 +215,10 @@ v1_api_and_json_patterns = [
{'POST': 'zerver.views.push_notifications.add_android_reg_id', {'POST': 'zerver.views.push_notifications.add_android_reg_id',
'DELETE': 'zerver.views.push_notifications.remove_android_reg_id'}), 'DELETE': 'zerver.views.push_notifications.remove_android_reg_id'}),
# user_groups -> zerver.views.user_groups
url(r'^user_groups/create$', rest_dispatch,
{'POST': 'zerver.views.user_groups.add_user_group'}),
# users/me -> zerver.views.user_settings # users/me -> zerver.views.user_settings
url(r'^users/me/api_key/regenerate$', rest_dispatch, url(r'^users/me/api_key/regenerate$', rest_dispatch,
{'POST': 'zerver.views.user_settings.regenerate_api_key'}), {'POST': 'zerver.views.user_settings.regenerate_api_key'}),