realm: Add setting to disable avatar changes.

This is useful when syncing avatars from an integrated LDAP/active
directory.

The upload avatar and delete avatar buttons are hidden if avatar
changes are disabled and the user is a non-admin.
If the user has a gravatar set, then the user will not be able to
upload an image as their avatar if avatar changes are disabled.

Part of #12132.
This commit is contained in:
Shubham Padia
2019-04-23 10:51:04 +08:00
committed by Tim Abbott
parent 63c170c390
commit 4994a6c2c9
12 changed files with 91 additions and 8 deletions

View File

@@ -815,6 +815,16 @@ run_test('misc', () => {
settings_account.update_email_change_display();
assert.equal($("#change_email .button").attr('disabled'), 'disabled');
page_params.realm_avatar_changes_disabled = false;
settings_account.update_avatar_change_display();
assert.equal($("#user_avatar_upload_button .button").attr('disabled'), false);
assert.equal($("#user_avatar_delete_button .button").attr('disabled'), false);
page_params.realm_avatar_changes_disabled = true;
settings_account.update_avatar_change_display();
assert.equal($("#user_avatar_upload_button .button").attr('disabled'), 'disabled');
assert.equal($("#user_avatar_delete_button .button").attr('disabled'), 'disabled');
// If organization admin, these UI elements are never disabled.
page_params.is_admin = true;
settings_account.update_name_change_display();

View File

@@ -18,6 +18,7 @@ var admin_settings_label = {
// Organization permissions
realm_name_changes_disabled: i18n.t("Prevent users from changing their name"),
realm_email_changes_disabled: i18n.t("Prevent users from changing their email address"),
realm_avatar_changes_disabled: i18n.t("Prevent users from changing their avatar"),
};
exports.build_page = function () {
@@ -35,6 +36,7 @@ exports.build_page = function () {
realm_invite_to_stream_by_admins_only: page_params.realm_invite_to_stream_by_admins_only,
realm_name_changes_disabled: page_params.realm_name_changes_disabled,
realm_email_changes_disabled: page_params.realm_email_changes_disabled,
realm_avatar_changes_disabled: page_params.realm_avatar_changes_disabled,
realm_add_emoji_by_admins_only: page_params.realm_add_emoji_by_admins_only,
can_add_emojis: settings_emoji.can_add_emoji(),
realm_allow_community_topic_editing: page_params.realm_allow_community_topic_editing,

View File

@@ -90,6 +90,7 @@ exports.dispatch_normal_event = function dispatch_normal_event(event) {
allow_message_deleting: noop,
allow_message_editing: noop,
allow_community_topic_editing: noop,
avatar_changes_disabled: settings_account.update_avatar_change_display,
bot_creation_policy: settings_bots.update_bot_permissions_ui,
create_stream_by_admins_only: noop,
invite_to_stream_policy: noop,

View File

@@ -45,6 +45,16 @@ exports.update_email_change_display = function () {
}
};
exports.update_avatar_change_display = function () {
if (page_params.realm_avatar_changes_disabled && !page_params.is_admin) {
$('#user_avatar_upload_button .button').attr('disabled', 'disabled');
$('#user_avatar_delete_button .button').attr('disabled', 'disabled');
} else {
$('#user_avatar_upload_button .button').attr('disabled', false);
$('#user_avatar_delete_button .button').attr('disabled', false);
}
};
function settings_change_error(message, xhr) {
ui_report.error(message, xhr, $('#account-settings-status').expectOne());
}

View File

@@ -169,11 +169,13 @@
<div id="upload_avatar_spinner"></div>
</div>
<div class="avatar-controls">
<button class="button rounded sea-green w-200 block" id="user_avatar_upload_button">
<button class="button rounded sea-green w-200 block" id="user_avatar_upload_button"
{{#unless page_params.is_admin}}{{#if page_params.realm_avatar_changes_disabled}}style="display:none"{{/if}}{{/unless}}>
{{t 'Upload new profile picture' }}
</button>
<div id="user_avatar_file_input_error" class="text-error"></div>
<button class="button rounded btn-danger w-200 m-t-20 block" id="user_avatar_delete_button">
<button class="button rounded btn-danger w-200 m-t-20 block" id="user_avatar_delete_button"
{{#unless page_params.is_admin}}{{#if page_params.realm_avatar_changes_disabled}}style="display:none"{{/if}}{{/unless}}>
{{t 'Delete profile picture' }}
</button>
</div>

View File

@@ -50,6 +50,12 @@
"prefix" "id_"
"is_checked" realm_email_changes_disabled
"label" admin_settings_label.realm_email_changes_disabled}}
{{partial "settings_checkbox"
"setting_name" "realm_avatar_changes_disabled"
"prefix" "id_"
"is_checked" realm_avatar_changes_disabled
"label" admin_settings_label.realm_avatar_changes_disabled}}
</div>
</div>

View File

@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-04-23 02:47
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('zerver', '0214_realm_invite_to_stream_policy'),
]
operations = [
migrations.AddField(
model_name='realm',
name='avatar_changes_disabled',
field=models.BooleanField(default=False),
),
]

View File

@@ -200,6 +200,7 @@ class Realm(models.Model):
add_emoji_by_admins_only = models.BooleanField(default=False) # type: bool
name_changes_disabled = models.BooleanField(default=False) # type: bool
email_changes_disabled = models.BooleanField(default=False) # type: bool
avatar_changes_disabled = models.BooleanField(default=False) # type: bool
# Who in the organization is allowed to invite other users to streams.
INVITE_TO_STREAM_POLICY_MEMBERS = 1
@@ -318,6 +319,7 @@ class Realm(models.Model):
message_retention_days=(int, type(None)),
name=str,
name_changes_disabled=bool,
avatar_changes_disabled=bool,
emails_restricted_to_domains=bool,
send_welcome_emails=bool,
message_content_allowed_in_email_notifications=bool,
@@ -488,6 +490,9 @@ def name_changes_disabled(realm: Optional[Realm]) -> bool:
return settings.NAME_CHANGES_DISABLED
return settings.NAME_CHANGES_DISABLED or realm.name_changes_disabled
def avatar_changes_disabled(realm: Realm) -> bool:
return realm.avatar_changes_disabled
class RealmDomain(models.Model):
"""For an organization with emails_restricted_to_domains enabled, the list of
allowed domains"""

View File

@@ -121,6 +121,7 @@ class HomeTest(ZulipTestCase):
"realm_allow_message_editing",
"realm_authentication_methods",
"realm_available_video_chat_providers",
"realm_avatar_changes_disabled",
"realm_bot_creation_policy",
"realm_bot_domain",
"realm_bots",

View File

@@ -35,6 +35,7 @@ from zerver.lib.actions import (
do_change_logo_source,
do_delete_old_unclaimed_attachments,
internal_send_private_message,
do_set_realm_property
)
from zerver.lib.cache import get_realm_used_upload_space_cache_key, cache_get
from zerver.lib.create_user import copy_user_settings
@@ -801,6 +802,17 @@ class AvatarTest(UploadSerializeMixin, ZulipTestCase):
result = self.client_post("/json/users/me/avatar")
self.assert_json_error(result, "You must upload exactly one avatar.")
def test_avatar_changes_disabled_failure(self) -> None:
"""
Attempting to upload avatar on a realm with avatar changes disabled should fail.
"""
self.login(self.example_email("cordelia"))
do_set_realm_property(self.example_user("cordelia").realm, "avatar_changes_disabled", True)
with get_test_image_file('img.png') as fp1:
result = self.client_post("/json/users/me/avatar", {'f1': fp1})
self.assert_json_error(result, "Avatar changes are disabled in this organization.")
correct_files = [
('img.png', 'png_resized.png'),
('img.jpg', None), # jpeg resizing is platform-dependent
@@ -1019,13 +1031,18 @@ class AvatarTest(UploadSerializeMixin, ZulipTestCase):
"""
A DELETE request to /json/users/me/avatar should delete the profile picture and return gravatar URL
"""
self.login(self.example_email("hamlet"))
hamlet = self.example_user('hamlet')
hamlet.avatar_source = UserProfile.AVATAR_FROM_USER
hamlet.save()
self.login(self.example_email("cordelia"))
cordelia = self.example_user("cordelia")
cordelia.avatar_source = UserProfile.AVATAR_FROM_USER
cordelia.save()
do_set_realm_property(cordelia.realm, 'avatar_changes_disabled', True)
result = self.client_delete("/json/users/me/avatar")
user_profile = self.example_user('hamlet')
self.assert_json_error(result, "Avatar changes are disabled in this organization.", 400)
do_set_realm_property(cordelia.realm, 'avatar_changes_disabled', False)
result = self.client_delete("/json/users/me/avatar")
user_profile = self.example_user("cordelia")
self.assert_json_success(result)
self.assertIn("avatar_url", result.json())

View File

@@ -39,6 +39,7 @@ def update_realm(
invite_by_admins_only: Optional[bool]=REQ(validator=check_bool, default=None),
name_changes_disabled: Optional[bool]=REQ(validator=check_bool, default=None),
email_changes_disabled: Optional[bool]=REQ(validator=check_bool, default=None),
avatar_changes_disabled: Optional[bool]=REQ(validator=check_bool, default=None),
inline_image_preview: Optional[bool]=REQ(validator=check_bool, default=None),
inline_url_embed_preview: Optional[bool]=REQ(validator=check_bool, default=None),
create_stream_by_admins_only: Optional[bool]=REQ(validator=check_bool, default=None),

View File

@@ -21,11 +21,13 @@ from zerver.lib.upload import upload_avatar_image
from zerver.lib.validator import check_bool, check_string
from zerver.lib.request import JsonableError
from zerver.lib.timezone import get_all_timezones
from zerver.models import UserProfile, name_changes_disabled
from zerver.models import UserProfile, name_changes_disabled, avatar_changes_disabled
from confirmation.models import get_object_from_key, render_confirmation_key_error, \
ConfirmationKeyException, Confirmation
from zproject.backends import email_belongs_to_ldap
AVATAR_CHANGES_DISABLED_ERROR = _("Avatar changes are disabled in this organization.")
def confirm_email_change(request: HttpRequest, confirmation_key: str) -> HttpResponse:
try:
email_change_object = get_object_from_key(confirmation_key, Confirmation.EMAIL_CHANGE)
@@ -187,6 +189,9 @@ def set_avatar_backend(request: HttpRequest, user_profile: UserProfile) -> HttpR
if len(request.FILES) != 1:
return json_error(_("You must upload exactly one avatar."))
if avatar_changes_disabled(user_profile.realm) and not user_profile.is_realm_admin:
return json_error(AVATAR_CHANGES_DISABLED_ERROR)
user_file = list(request.FILES.values())[0]
if ((settings.MAX_AVATAR_FILE_SIZE * 1024 * 1024) < user_file.size):
return json_error(_("Uploaded file is larger than the allowed limit of %s MB") % (
@@ -201,6 +206,9 @@ def set_avatar_backend(request: HttpRequest, user_profile: UserProfile) -> HttpR
return json_success(json_result)
def delete_avatar_backend(request: HttpRequest, user_profile: UserProfile) -> HttpResponse:
if avatar_changes_disabled(user_profile.realm) and not user_profile.is_realm_admin:
return json_error(AVATAR_CHANGES_DISABLED_ERROR)
do_change_avatar_fields(user_profile, UserProfile.AVATAR_FROM_GRAVATAR)
gravatar_url = avatar_url(user_profile)