class AvatarSerializer(serializers.Serializer): avatar_photo = AvatarField(required=False) avatar_type = serializers.ChoiceField( choices=(("upload", "upload"), ("gravatar", "gravatar"), ("letter_avatar", "letter_avatar")) ) def validate(self, attrs): attrs = super().validate(attrs) if attrs.get("avatar_type") == "upload": model_type = self.context["type"] has_existing_file = model_type.objects.filter( file__isnull=False, **self.context["kwargs"] ).exists() if not has_existing_file and not attrs.get("avatar_photo"): raise serializers.ValidationError( {"avatar_type": "Cannot set avatar_type to upload without avatar_photo"} ) return attrs
class AvatarSerializer(serializers.Serializer): avatar_photo = AvatarField(required=False) avatar_type = serializers.ChoiceField(choices=( ('upload', 'upload'), ('gravatar', 'gravatar'), ('letter_avatar', 'letter_avatar'), )) def validate(self, attrs): attrs = super(AvatarSerializer, self).validate(attrs) if attrs.get('avatar_type') == 'upload': model_type = self.context['type'] has_existing_file = model_type.objects.filter( file__isnull=False, **self.context['kwargs']).exists() if not has_existing_file and not attrs.get('avatar_photo'): raise serializers.ValidationError({ 'avatar_type': 'Cannot set avatar_type to upload without avatar_photo', }) return attrs
class OrganizationSerializer(serializers.Serializer): name = serializers.CharField(max_length=64) slug = serializers.RegexField(r'^[a-z0-9_\-]+$', max_length=50) accountRateLimit = serializers.IntegerField( min_value=0, max_value=1000000, required=False) projectRateLimit = serializers.IntegerField( min_value=50, max_value=100, required=False) avatar = AvatarField(required=False) avatarType = serializers.ChoiceField( choices=(('upload', 'upload'), ('letter_avatar', 'letter_avatar'), ), required=False ) openMembership = serializers.BooleanField(required=False) allowSharedIssues = serializers.BooleanField(required=False) enhancedPrivacy = serializers.BooleanField(required=False) dataScrubber = serializers.BooleanField(required=False) dataScrubberDefaults = serializers.BooleanField(required=False) sensitiveFields = ListField(child=serializers.CharField(), required=False) safeFields = ListField(child=serializers.CharField(), required=False) scrubIPAddresses = serializers.BooleanField(required=False) isEarlyAdopter = serializers.BooleanField(required=False) require2FA = serializers.BooleanField(required=False) def validate_slug(self, attrs, source): value = attrs[source] # Historically, the only check just made sure there was more than 1 # character for the slug, but since then, there are many slugs that # fit within this new imposed limit. We're not fixing existing, but # just preventing new bad values. if len(value) < 3: raise serializers.ValidationError( 'This slug "%s" is too short. Minimum of 3 characters.' % (value, )) if value in RESERVED_ORGANIZATION_SLUGS: raise serializers.ValidationError( 'This slug "%s" is reserved and not allowed.' % (value, )) qs = Organization.objects.filter( slug=value, ).exclude(id=self.context['organization'].id) if qs.exists(): raise serializers.ValidationError('The slug "%s" is already in use.' % (value, )) return attrs def validate_sensitiveFields(self, attrs, source): value = attrs[source] if value and not all(value): raise serializers.ValidationError('Empty values are not allowed.') return attrs def validate_safeFields(self, attrs, source): value = attrs[source] if value and not all(value): raise serializers.ValidationError('Empty values are not allowed.') return attrs def validate_require2FA(self, attrs, source): value = attrs[source] user = self.context['user'] has_2fa = Authenticator.objects.user_has_2fa(user) if value and not has_2fa: raise serializers.ValidationError( 'Cannot require two-factor authentication without personal two-factor enabled.') return attrs def validate(self, attrs): attrs = super(OrganizationSerializer, self).validate(attrs) if attrs.get('avatarType') == 'upload': has_existing_file = OrganizationAvatar.objects.filter( organization=self.context['organization'], file__isnull=False, ).exists() if not has_existing_file and not attrs.get('avatar'): raise serializers.ValidationError( { 'avatarType': 'Cannot set avatarType to upload without avatar', } ) return attrs def save(self): org = self.context['organization'] changed_data = {} for key, option, type_ in ORG_OPTIONS: if key not in self.init_data: continue try: option_inst = OrganizationOption.objects.get( organization=org, key=option) except OrganizationOption.DoesNotExist: OrganizationOption.objects.set_value( organization=org, key=option, value=type_(self.init_data[key]), ) # TODO(kelly): This will not work if new ORG_OPTIONS are added and their # default value evaluates as truthy, but this should work for now with the # current ORG_OPTIONS (assumes ORG_OPTIONS are falsy) if type_(self.init_data[key]): changed_data[key] = self.init_data[key] else: option_inst.value = self.init_data[key] # check if ORG_OPTIONS changed if option_inst.has_changed('value'): old_val = option_inst.old_value('value') changed_data[key] = u'from {} to {}'.format(old_val, option_inst.value) option_inst.save() if 'openMembership' in self.init_data: org.flags.allow_joinleave = self.init_data['openMembership'] if 'allowSharedIssues' in self.init_data: org.flags.disable_shared_issues = not self.init_data['allowSharedIssues'] if 'enhancedPrivacy' in self.init_data: org.flags.enhanced_privacy = self.init_data['enhancedPrivacy'] if 'isEarlyAdopter' in self.init_data: org.flags.early_adopter = self.init_data['isEarlyAdopter'] if 'require2FA' in self.init_data: org.flags.require_2fa = self.init_data['require2FA'] if 'name' in self.init_data: org.name = self.init_data['name'] if 'slug' in self.init_data: org.slug = self.init_data['slug'] org_tracked_field = { 'name': org.name, 'slug': org.slug, 'default_role': org.default_role, 'flag_field': { 'allow_joinleave': org.flags.allow_joinleave.is_set, 'enhanced_privacy': org.flags.enhanced_privacy.is_set, 'disable_shared_issues': org.flags.disable_shared_issues.is_set, 'early_adopter': org.flags.early_adopter.is_set, 'require_2fa': org.flags.require_2fa.is_set, } } # check if fields changed for f, v in six.iteritems(org_tracked_field): if f is not 'flag_field': if org.has_changed(f): old_val = org.old_value(f) changed_data[f] = u'from {} to {}'.format(old_val, v) else: # check if flag fields changed for f, v in six.iteritems(org_tracked_field['flag_field']): if org.flag_has_changed(f): changed_data[f] = u'to {}'.format(v) org.save() if 'avatar' in self.init_data or 'avatarType' in self.init_data: OrganizationAvatar.save_avatar( relation={'organization': org}, type=self.init_data.get('avatarType', 'upload'), avatar=self.init_data.get('avatar'), filename='{}.png'.format(org.slug), ) if 'require2FA' in self.init_data and self.init_data['require2FA'] is True: org.send_setup_2fa_emails() return org, changed_data
class OrganizationSerializer(serializers.Serializer): name = serializers.CharField(max_length=64) slug = serializers.RegexField(r'^[a-z0-9_\-]+$', max_length=50) accountRateLimit = EmptyIntegerField( min_value=0, max_value=1000000, required=False, allow_null=True, ) projectRateLimit = EmptyIntegerField( min_value=50, max_value=100, required=False, allow_null=True, ) avatar = AvatarField(required=False, allow_null=True) avatarType = serializers.ChoiceField( choices=( ('upload', 'upload'), ('letter_avatar', 'letter_avatar'), ), required=False, allow_null=True, ) openMembership = serializers.BooleanField(required=False) allowSharedIssues = serializers.BooleanField(required=False) enhancedPrivacy = serializers.BooleanField(required=False) dataScrubber = serializers.BooleanField(required=False) dataScrubberDefaults = serializers.BooleanField(required=False) sensitiveFields = ListField(child=serializers.CharField(), required=False) safeFields = ListField(child=serializers.CharField(), required=False) storeCrashReports = serializers.BooleanField(required=False) attachmentsRole = serializers.CharField(required=True) scrubIPAddresses = serializers.BooleanField(required=False) scrapeJavaScript = serializers.BooleanField(required=False) isEarlyAdopter = serializers.BooleanField(required=False) require2FA = serializers.BooleanField(required=False) trustedRelays = ListField(child=serializers.CharField(), required=False) @memoize def _has_legacy_rate_limits(self): org = self.context['organization'] return OrganizationOption.objects.filter( organization=org, key__in=LEGACY_RATE_LIMIT_OPTIONS, ).exists() def _has_sso_enabled(self): org = self.context['organization'] return AuthProvider.objects.filter(organization=org).exists() def validate_slug(self, value): # Historically, the only check just made sure there was more than 1 # character for the slug, but since then, there are many slugs that # fit within this new imposed limit. We're not fixing existing, but # just preventing new bad values. if len(value) < 3: raise serializers.ValidationError( 'This slug "%s" is too short. Minimum of 3 characters.' % (value, )) if value in RESERVED_ORGANIZATION_SLUGS: raise serializers.ValidationError( 'This slug "%s" is reserved and not allowed.' % (value, )) qs = Organization.objects.filter( slug=value, ).exclude(id=self.context['organization'].id) if qs.exists(): raise serializers.ValidationError( 'The slug "%s" is already in use.' % (value, )) return value def validate_sensitiveFields(self, value): if value and not all(value): raise serializers.ValidationError('Empty values are not allowed.') return value def validate_safeFields(self, value): if value and not all(value): raise serializers.ValidationError('Empty values are not allowed.') return value def validate_attachmentsRole(self, value): try: roles.get(value) except KeyError: raise serializers.ValidationError('Invalid role') return value def validate_require2FA(self, value): user = self.context['user'] has_2fa = Authenticator.objects.user_has_2fa(user) if value and not has_2fa: raise serializers.ValidationError(ERR_NO_2FA) if value and self._has_sso_enabled(): raise serializers.ValidationError(ERR_SSO_ENABLED) return value def validate_trustedRelays(self, value): from sentry import features organization = self.context['organization'] request = self.context["request"] has_relays = features.has('organizations:relay', organization, actor=request.user) if not has_relays: raise serializers.ValidationError( 'Organization does not have the relay feature enabled') return value def validate_accountRateLimit(self, value): if not self._has_legacy_rate_limits: raise serializers.ValidationError( 'The accountRateLimit option cannot be configured for this organization' ) return value def validate_projectRateLimit(self, value): if not self._has_legacy_rate_limits: raise serializers.ValidationError( 'The accountRateLimit option cannot be configured for this organization' ) return value def validate(self, attrs): attrs = super(OrganizationSerializer, self).validate(attrs) if attrs.get('avatarType') == 'upload': has_existing_file = OrganizationAvatar.objects.filter( organization=self.context['organization'], file__isnull=False, ).exists() if not has_existing_file and not attrs.get('avatar'): raise serializers.ValidationError({ 'avatarType': 'Cannot set avatarType to upload without avatar', }) return attrs def save(self): org = self.context['organization'] changed_data = {} for key, option, type_, default_value in ORG_OPTIONS: if key not in self.initial_data: continue try: option_inst = OrganizationOption.objects.get(organization=org, key=option) except OrganizationOption.DoesNotExist: OrganizationOption.objects.set_value( organization=org, key=option, value=type_(self.initial_data[key]), ) if self.initial_data[key] != default_value: changed_data[key] = u'to {}'.format(self.initial_data[key]) else: option_inst.value = self.initial_data[key] # check if ORG_OPTIONS changed if option_inst.has_changed('value'): old_val = option_inst.old_value('value') changed_data[key] = u'from {} to {}'.format( old_val, option_inst.value) option_inst.save() if 'openMembership' in self.initial_data: org.flags.allow_joinleave = self.initial_data['openMembership'] if 'allowSharedIssues' in self.initial_data: org.flags.disable_shared_issues = not self.initial_data[ 'allowSharedIssues'] if 'enhancedPrivacy' in self.initial_data: org.flags.enhanced_privacy = self.initial_data['enhancedPrivacy'] if 'isEarlyAdopter' in self.initial_data: org.flags.early_adopter = self.initial_data['isEarlyAdopter'] if 'require2FA' in self.initial_data: org.flags.require_2fa = self.initial_data['require2FA'] if 'name' in self.initial_data: org.name = self.initial_data['name'] if 'slug' in self.initial_data: org.slug = self.initial_data['slug'] org_tracked_field = { 'name': org.name, 'slug': org.slug, 'default_role': org.default_role, 'flag_field': { 'allow_joinleave': org.flags.allow_joinleave.is_set, 'enhanced_privacy': org.flags.enhanced_privacy.is_set, 'disable_shared_issues': org.flags.disable_shared_issues.is_set, 'early_adopter': org.flags.early_adopter.is_set, 'require_2fa': org.flags.require_2fa.is_set, } } # check if fields changed for f, v in six.iteritems(org_tracked_field): if f is not 'flag_field': if org.has_changed(f): old_val = org.old_value(f) changed_data[f] = u'from {} to {}'.format(old_val, v) else: # check if flag fields changed for f, v in six.iteritems(org_tracked_field['flag_field']): if org.flag_has_changed(f): changed_data[f] = u'to {}'.format(v) org.save() if 'avatar' in self.initial_data or 'avatarType' in self.initial_data: OrganizationAvatar.save_avatar( relation={'organization': org}, type=self.initial_data.get('avatarType', 'upload'), avatar=self.initial_data.get('avatar'), filename=u'{}.png'.format(org.slug), ) if 'require2FA' in self.initial_data and self.initial_data[ 'require2FA'] is True: org.handle_2fa_required(self.context['request']) return org, changed_data
class OrganizationSerializer(serializers.Serializer): name = serializers.CharField(max_length=64) slug = serializers.RegexField(r"^[a-z0-9_\-]+$", max_length=50) accountRateLimit = EmptyIntegerField(min_value=0, max_value=1000000, required=False, allow_null=True) projectRateLimit = EmptyIntegerField(min_value=50, max_value=100, required=False, allow_null=True) avatar = AvatarField(required=False, allow_null=True) avatarType = serializers.ChoiceField( choices=(("upload", "upload"), ("letter_avatar", "letter_avatar")), required=False, allow_null=True, ) openMembership = serializers.BooleanField(required=False) allowSharedIssues = serializers.BooleanField(required=False) enhancedPrivacy = serializers.BooleanField(required=False) dataScrubber = serializers.BooleanField(required=False) dataScrubberDefaults = serializers.BooleanField(required=False) sensitiveFields = ListField(child=serializers.CharField(), required=False) safeFields = ListField(child=serializers.CharField(), required=False) storeCrashReports = serializers.IntegerField(min_value=-1, max_value=20, required=False) attachmentsRole = serializers.CharField(required=True) debugFilesRole = serializers.CharField(required=True) eventsMemberAdmin = serializers.BooleanField(required=False) alertsMemberWrite = serializers.BooleanField(required=False) scrubIPAddresses = serializers.BooleanField(required=False) scrapeJavaScript = serializers.BooleanField(required=False) isEarlyAdopter = serializers.BooleanField(required=False) require2FA = serializers.BooleanField(required=False) requireEmailVerification = serializers.BooleanField(required=False) trustedRelays = ListField(child=TrustedRelaySerializer(), required=False) allowJoinRequests = serializers.BooleanField(required=False) relayPiiConfig = serializers.CharField(required=False, allow_blank=True, allow_null=True) apdexThreshold = serializers.IntegerField(min_value=1, required=False) @memoize def _has_legacy_rate_limits(self): org = self.context["organization"] return OrganizationOption.objects.filter( organization=org, key__in=LEGACY_RATE_LIMIT_OPTIONS).exists() def _has_sso_enabled(self): org = self.context["organization"] return AuthProvider.objects.filter(organization=org).exists() def validate_slug(self, value): # Historically, the only check just made sure there was more than 1 # character for the slug, but since then, there are many slugs that # fit within this new imposed limit. We're not fixing existing, but # just preventing new bad values. if len(value) < 3: raise serializers.ValidationError( f'This slug "{value}" is too short. Minimum of 3 characters.') if value in RESERVED_ORGANIZATION_SLUGS: raise serializers.ValidationError( f'This slug "{value}" is reserved and not allowed.') qs = Organization.objects.filter(slug=value).exclude( id=self.context["organization"].id) if qs.exists(): raise serializers.ValidationError( f'The slug "{value}" is already in use.') return value def validate_relayPiiConfig(self, value): organization = self.context["organization"] return validate_pii_config_update(organization, value) def validate_sensitiveFields(self, value): if value and not all(value): raise serializers.ValidationError("Empty values are not allowed.") return value def validate_safeFields(self, value): if value and not all(value): raise serializers.ValidationError("Empty values are not allowed.") return value def validate_attachmentsRole(self, value): try: roles.get(value) except KeyError: raise serializers.ValidationError("Invalid role") return value def validate_debugFilesRole(self, value): try: roles.get(value) except KeyError: raise serializers.ValidationError("Invalid role") return value def validate_require2FA(self, value): user = self.context["user"] has_2fa = Authenticator.objects.user_has_2fa(user) if value and not has_2fa: raise serializers.ValidationError(ERR_NO_2FA) if value and self._has_sso_enabled(): raise serializers.ValidationError(ERR_SSO_ENABLED) return value def validate_requireEmailVerification(self, value): user = self.context["user"] has_verified = UserEmail.get_primary_email(user).is_verified if value and not has_verified: raise serializers.ValidationError(ERR_EMAIL_VERIFICATION) return value def validate_trustedRelays(self, value): from sentry import features organization = self.context["organization"] request = self.context["request"] has_relays = features.has("organizations:relay", organization, actor=request.user) if not has_relays: raise serializers.ValidationError( "Organization does not have the relay feature enabled") # make sure we don't have multiple instances of one public key public_keys = set() if value is not None: for key_info in value: key = key_info.get("public_key") if key in public_keys: raise serializers.ValidationError( f"Duplicated key in Trusted Relays: '{key}'") public_keys.add(key) return value def validate_accountRateLimit(self, value): if not self._has_legacy_rate_limits: raise serializers.ValidationError( "The accountRateLimit option cannot be configured for this organization" ) return value def validate_projectRateLimit(self, value): if not self._has_legacy_rate_limits: raise serializers.ValidationError( "The accountRateLimit option cannot be configured for this organization" ) return value def validate(self, attrs): attrs = super().validate(attrs) if attrs.get("avatarType") == "upload": has_existing_file = OrganizationAvatar.objects.filter( organization=self.context["organization"], file__isnull=False).exists() if not has_existing_file and not attrs.get("avatar"): raise serializers.ValidationError({ "avatarType": "Cannot set avatarType to upload without avatar" }) return attrs def save_trusted_relays(self, incoming, changed_data, organization): timestamp_now = datetime.utcnow().replace(tzinfo=UTC).isoformat() option_key = "sentry:trusted-relays" try: # get what we already have existing = OrganizationOption.objects.get( organization=organization, key=option_key) key_dict = {val.get("public_key"): val for val in existing.value} original_number_of_keys = len(existing.value) except OrganizationOption.DoesNotExist: key_dict = {} # we don't have anything set original_number_of_keys = 0 existing = None modified = False for option in incoming: public_key = option.get("public_key") existing_info = key_dict.get(public_key, {}) option["created"] = existing_info.get("created", timestamp_now) option["last_modified"] = existing_info.get("last_modified") # check if we modified the current public_key info and update last_modified if we did if (not existing_info or existing_info.get("name") != option.get("name") or existing_info.get("description") != option.get("description")): option["last_modified"] = timestamp_now modified = True # check to see if the only modifications were some deletions (which are not captured in the loop above) if len(incoming) != original_number_of_keys: modified = True if modified: # we have some modifications create a log message if existing is not None: # generate an update log message changed_data[ "trustedRelays"] = f"from {existing} to {incoming}" existing.value = incoming existing.save() else: # first time we set trusted relays, generate a create log message changed_data["trustedRelays"] = f"to {incoming}" OrganizationOption.objects.set_value(organization=organization, key=option_key, value=incoming) return incoming def save(self): from sentry import features org = self.context["organization"] changed_data = {} if not hasattr(org, "__data"): update_tracked_data(org) for key, option, type_, default_value in ORG_OPTIONS: if key not in self.initial_data: continue try: option_inst = OrganizationOption.objects.get(organization=org, key=option) update_tracked_data(option_inst) except OrganizationOption.DoesNotExist: OrganizationOption.objects.set_value( organization=org, key=option, value=type_(self.initial_data[key])) if self.initial_data[key] != default_value: changed_data[key] = f"to {self.initial_data[key]}" else: option_inst.value = self.initial_data[key] # check if ORG_OPTIONS changed if has_changed(option_inst, "value"): old_val = old_value(option_inst, "value") changed_data[ key] = f"from {old_val} to {option_inst.value}" option_inst.save() trusted_realy_info = self.validated_data.get("trustedRelays") if trusted_realy_info is not None: self.save_trusted_relays(trusted_realy_info, changed_data, org) if "openMembership" in self.initial_data: org.flags.allow_joinleave = self.initial_data["openMembership"] if "allowSharedIssues" in self.initial_data: org.flags.disable_shared_issues = not self.initial_data[ "allowSharedIssues"] if "enhancedPrivacy" in self.initial_data: org.flags.enhanced_privacy = self.initial_data["enhancedPrivacy"] if "isEarlyAdopter" in self.initial_data: org.flags.early_adopter = self.initial_data["isEarlyAdopter"] if "require2FA" in self.initial_data: org.flags.require_2fa = self.initial_data["require2FA"] if (features.has("organizations:required-email-verification", org) and "requireEmailVerification" in self.initial_data): org.flags.require_email_verification = self.initial_data[ "requireEmailVerification"] if "name" in self.initial_data: org.name = self.initial_data["name"] if "slug" in self.initial_data: org.slug = self.initial_data["slug"] org_tracked_field = { "name": org.name, "slug": org.slug, "default_role": org.default_role, "flag_field": { "allow_joinleave": org.flags.allow_joinleave.is_set, "enhanced_privacy": org.flags.enhanced_privacy.is_set, "disable_shared_issues": org.flags.disable_shared_issues.is_set, "early_adopter": org.flags.early_adopter.is_set, "require_2fa": org.flags.require_2fa.is_set, }, } # check if fields changed for f, v in org_tracked_field.items(): if f != "flag_field": if has_changed(org, f): old_val = old_value(org, f) changed_data[f] = f"from {old_val} to {v}" else: # check if flag fields changed for f, v in org_tracked_field["flag_field"].items(): if flag_has_changed(org, f): changed_data[f] = f"to {v}" org.save() if "avatar" in self.initial_data or "avatarType" in self.initial_data: OrganizationAvatar.save_avatar( relation={"organization": org}, type=self.initial_data.get("avatarType", "upload"), avatar=self.initial_data.get("avatar"), filename=f"{org.slug}.png", ) if self.initial_data.get("require2FA") is True: org.handle_2fa_required(self.context["request"]) if (features.has("organizations:required-email-verification", org) and self.initial_data.get("requireEmailVerification") is True): org.handle_email_verification_required(self.context["request"]) return org, changed_data
class OrganizationSerializer(serializers.Serializer): name = serializers.CharField(max_length=64) slug = serializers.RegexField(r'^[a-z0-9_\-]+$', max_length=50) accountRateLimit = serializers.IntegerField(min_value=0, max_value=1000000, required=False) projectRateLimit = serializers.IntegerField(min_value=50, max_value=100, required=False) avatar = AvatarField(required=False) avatarType = serializers.ChoiceField(choices=( ('upload', 'upload'), ('letter_avatar', 'letter_avatar'), ), required=False) openMembership = serializers.BooleanField(required=False) allowSharedIssues = serializers.BooleanField(required=False) enhancedPrivacy = serializers.BooleanField(required=False) dataScrubber = serializers.BooleanField(required=False) dataScrubberDefaults = serializers.BooleanField(required=False) sensitiveFields = ListField(child=serializers.CharField(), required=False) safeFields = ListField(child=serializers.CharField(), required=False) scrubIPAddresses = serializers.BooleanField(required=False) isEarlyAdopter = serializers.BooleanField(required=False) def validate_slug(self, attrs, source): value = attrs[source] if Organization.objects.filter(slug=value).exclude( id=self.context['organization'].id): raise serializers.ValidationError( 'The slug "%s" is already in use.' % (value, )) return attrs def validate(self, attrs): attrs = super(OrganizationSerializer, self).validate(attrs) if attrs.get('avatarType') == 'upload': has_existing_file = OrganizationAvatar.objects.filter( organization=self.context['organization'], file__isnull=False, ).exists() if not has_existing_file and not attrs.get('avatar'): raise serializers.ValidationError({ 'avatarType': 'Cannot set avatarType to upload without avatar', }) return attrs def save(self): org = self.context['organization'] if 'openMembership' in self.init_data: org.flags.allow_joinleave = self.init_data['openMembership'] if 'allowSharedIssues' in self.init_data: org.flags.disable_shared_issues = not self.init_data[ 'allowSharedIssues'] if 'enhancedPrivacy' in self.init_data: org.flags.enhanced_privacy = self.init_data['enhancedPrivacy'] if 'isEarlyAdopter' in self.init_data: org.flags.early_adopter = self.init_data['isEarlyAdopter'] if 'name' in self.init_data: org.name = self.init_data['name'] if 'slug' in self.init_data: org.slug = self.init_data['slug'] org.save() for key, option, type_ in ORG_OPTIONS: if key in self.init_data: OrganizationOption.objects.set_value( organization=org, key=option, value=type_(self.init_data[key]), ) if 'avatar' in self.init_data or 'avatarType' in self.init_data: OrganizationAvatar.save_avatar( relation={'organization': org}, type=self.init_data.get('avatarType', 'upload'), avatar=self.init_data.get('avatar'), filename='{}.png'.format(org.slug), ) return org
class OrganizationSerializer(serializers.Serializer): name = serializers.CharField(max_length=64) slug = serializers.RegexField(r'^[a-z0-9_\-]+$', max_length=50) accountRateLimit = serializers.IntegerField(min_value=0, max_value=1000000, required=False) projectRateLimit = serializers.IntegerField(min_value=50, max_value=100, required=False) avatar = AvatarField(required=False) avatarType = serializers.ChoiceField(choices=( ('upload', 'upload'), ('letter_avatar', 'letter_avatar'), ), required=False) openMembership = serializers.BooleanField(required=False) allowSharedIssues = serializers.BooleanField(required=False) enhancedPrivacy = serializers.BooleanField(required=False) dataScrubber = serializers.BooleanField(required=False) dataScrubberDefaults = serializers.BooleanField(required=False) sensitiveFields = ListField(child=serializers.CharField(), required=False) safeFields = ListField(child=serializers.CharField(), required=False) scrubIPAddresses = serializers.BooleanField(required=False) isEarlyAdopter = serializers.BooleanField(required=False) require2FA = serializers.BooleanField(required=False) def validate_slug(self, attrs, source): value = attrs[source] # Historically, the only check just made sure there was more than 1 # character for the slug, but since then, there are many slugs that # fit within this new imposed limit. We're not fixing existing, but # just preventing new bad values. if len(value) < 3: raise serializers.ValidationError( 'This slug "%s" is too short. Minimum of 3 characters.' % (value, )) if value in RESERVED_ORGANIZATION_SLUGS: raise serializers.ValidationError( 'This slug "%s" is reserved and not allowed.' % (value, )) qs = Organization.objects.filter( slug=value, ).exclude(id=self.context['organization'].id) if qs.exists(): raise serializers.ValidationError( 'The slug "%s" is already in use.' % (value, )) return attrs def validate_sensitiveFields(self, attrs, source): value = attrs[source] if value and not all(value): raise serializers.ValidationError('Empty values are not allowed.') return attrs def validate_safeFields(self, attrs, source): value = attrs[source] if value and not all(value): raise serializers.ValidationError('Empty values are not allowed.') return attrs def validate_require2FA(self, attrs, source): value = attrs[source] user = self.context['user'] has_2fa = Authenticator.objects.user_has_2fa(user) if value and not has_2fa: raise serializers.ValidationError( 'User setting two-factor authentication enforcement without two-factor authentication enabled.' ) return attrs def validate(self, attrs): attrs = super(OrganizationSerializer, self).validate(attrs) if attrs.get('avatarType') == 'upload': has_existing_file = OrganizationAvatar.objects.filter( organization=self.context['organization'], file__isnull=False, ).exists() if not has_existing_file and not attrs.get('avatar'): raise serializers.ValidationError({ 'avatarType': 'Cannot set avatarType to upload without avatar', }) return attrs def save(self): org = self.context['organization'] if 'openMembership' in self.init_data: org.flags.allow_joinleave = self.init_data['openMembership'] if 'allowSharedIssues' in self.init_data: org.flags.disable_shared_issues = not self.init_data[ 'allowSharedIssues'] if 'enhancedPrivacy' in self.init_data: org.flags.enhanced_privacy = self.init_data['enhancedPrivacy'] if 'isEarlyAdopter' in self.init_data: org.flags.early_adopter = self.init_data['isEarlyAdopter'] if 'require2FA' in self.init_data: org.flags.require_2fa = self.init_data['require2FA'] if 'name' in self.init_data: org.name = self.init_data['name'] if 'slug' in self.init_data: org.slug = self.init_data['slug'] org.save() for key, option, type_ in ORG_OPTIONS: if key in self.init_data: OrganizationOption.objects.set_value( organization=org, key=option, value=type_(self.init_data[key]), ) if 'avatar' in self.init_data or 'avatarType' in self.init_data: OrganizationAvatar.save_avatar( relation={'organization': org}, type=self.init_data.get('avatarType', 'upload'), avatar=self.init_data.get('avatar'), filename='{}.png'.format(org.slug), ) if 'require2FA' in self.init_data and self.init_data[ 'require2FA'] is True: org.send_setup_2fa_emails() return org
class OrganizationSerializer(serializers.Serializer): name = serializers.CharField(max_length=64) slug = serializers.RegexField(r"^[a-z0-9_\-]+$", max_length=50) accountRateLimit = EmptyIntegerField( min_value=0, max_value=1000000, required=False, allow_null=True ) projectRateLimit = EmptyIntegerField( min_value=50, max_value=100, required=False, allow_null=True ) avatar = AvatarField(required=False, allow_null=True) avatarType = serializers.ChoiceField( choices=(("upload", "upload"), ("letter_avatar", "letter_avatar")), required=False, allow_null=True, ) openMembership = serializers.BooleanField(required=False) allowSharedIssues = serializers.BooleanField(required=False) enhancedPrivacy = serializers.BooleanField(required=False) dataScrubber = serializers.BooleanField(required=False) dataScrubberDefaults = serializers.BooleanField(required=False) sensitiveFields = ListField(child=serializers.CharField(), required=False) safeFields = ListField(child=serializers.CharField(), required=False) storeCrashReports = serializers.IntegerField(min_value=-1, max_value=20, required=False) attachmentsRole = serializers.CharField(required=True) scrubIPAddresses = serializers.BooleanField(required=False) scrapeJavaScript = serializers.BooleanField(required=False) isEarlyAdopter = serializers.BooleanField(required=False) require2FA = serializers.BooleanField(required=False) trustedRelays = ListField(child=serializers.CharField(), required=False) allowJoinRequests = serializers.BooleanField(required=False) @memoize def _has_legacy_rate_limits(self): org = self.context["organization"] return OrganizationOption.objects.filter( organization=org, key__in=LEGACY_RATE_LIMIT_OPTIONS ).exists() def _has_sso_enabled(self): org = self.context["organization"] return AuthProvider.objects.filter(organization=org).exists() def validate_slug(self, value): # Historically, the only check just made sure there was more than 1 # character for the slug, but since then, there are many slugs that # fit within this new imposed limit. We're not fixing existing, but # just preventing new bad values. if len(value) < 3: raise serializers.ValidationError( 'This slug "%s" is too short. Minimum of 3 characters.' % (value,) ) if value in RESERVED_ORGANIZATION_SLUGS: raise serializers.ValidationError( 'This slug "%s" is reserved and not allowed.' % (value,) ) qs = Organization.objects.filter(slug=value).exclude(id=self.context["organization"].id) if qs.exists(): raise serializers.ValidationError('The slug "%s" is already in use.' % (value,)) return value def validate_sensitiveFields(self, value): if value and not all(value): raise serializers.ValidationError("Empty values are not allowed.") return value def validate_safeFields(self, value): if value and not all(value): raise serializers.ValidationError("Empty values are not allowed.") return value def validate_attachmentsRole(self, value): try: roles.get(value) except KeyError: raise serializers.ValidationError("Invalid role") return value def validate_require2FA(self, value): user = self.context["user"] has_2fa = Authenticator.objects.user_has_2fa(user) if value and not has_2fa: raise serializers.ValidationError(ERR_NO_2FA) if value and self._has_sso_enabled(): raise serializers.ValidationError(ERR_SSO_ENABLED) return value def validate_trustedRelays(self, value): from sentry import features organization = self.context["organization"] request = self.context["request"] has_relays = features.has("organizations:relay", organization, actor=request.user) if not has_relays: raise serializers.ValidationError( "Organization does not have the relay feature enabled" ) return value def validate_accountRateLimit(self, value): if not self._has_legacy_rate_limits: raise serializers.ValidationError( "The accountRateLimit option cannot be configured for this organization" ) return value def validate_projectRateLimit(self, value): if not self._has_legacy_rate_limits: raise serializers.ValidationError( "The accountRateLimit option cannot be configured for this organization" ) return value def validate(self, attrs): attrs = super(OrganizationSerializer, self).validate(attrs) if attrs.get("avatarType") == "upload": has_existing_file = OrganizationAvatar.objects.filter( organization=self.context["organization"], file__isnull=False ).exists() if not has_existing_file and not attrs.get("avatar"): raise serializers.ValidationError( {"avatarType": "Cannot set avatarType to upload without avatar"} ) return attrs def save(self): org = self.context["organization"] changed_data = {} for key, option, type_, default_value in ORG_OPTIONS: if key not in self.initial_data: continue try: option_inst = OrganizationOption.objects.get(organization=org, key=option) except OrganizationOption.DoesNotExist: OrganizationOption.objects.set_value( organization=org, key=option, value=type_(self.initial_data[key]) ) if self.initial_data[key] != default_value: changed_data[key] = u"to {}".format(self.initial_data[key]) else: option_inst.value = self.initial_data[key] # check if ORG_OPTIONS changed if option_inst.has_changed("value"): old_val = option_inst.old_value("value") changed_data[key] = u"from {} to {}".format(old_val, option_inst.value) option_inst.save() if "openMembership" in self.initial_data: org.flags.allow_joinleave = self.initial_data["openMembership"] if "allowSharedIssues" in self.initial_data: org.flags.disable_shared_issues = not self.initial_data["allowSharedIssues"] if "enhancedPrivacy" in self.initial_data: org.flags.enhanced_privacy = self.initial_data["enhancedPrivacy"] if "isEarlyAdopter" in self.initial_data: org.flags.early_adopter = self.initial_data["isEarlyAdopter"] if "require2FA" in self.initial_data: org.flags.require_2fa = self.initial_data["require2FA"] if "name" in self.initial_data: org.name = self.initial_data["name"] if "slug" in self.initial_data: org.slug = self.initial_data["slug"] org_tracked_field = { "name": org.name, "slug": org.slug, "default_role": org.default_role, "flag_field": { "allow_joinleave": org.flags.allow_joinleave.is_set, "enhanced_privacy": org.flags.enhanced_privacy.is_set, "disable_shared_issues": org.flags.disable_shared_issues.is_set, "early_adopter": org.flags.early_adopter.is_set, "require_2fa": org.flags.require_2fa.is_set, }, } # check if fields changed for f, v in six.iteritems(org_tracked_field): if f != "flag_field": if org.has_changed(f): old_val = org.old_value(f) changed_data[f] = u"from {} to {}".format(old_val, v) else: # check if flag fields changed for f, v in six.iteritems(org_tracked_field["flag_field"]): if org.flag_has_changed(f): changed_data[f] = u"to {}".format(v) org.save() if "avatar" in self.initial_data or "avatarType" in self.initial_data: OrganizationAvatar.save_avatar( relation={"organization": org}, type=self.initial_data.get("avatarType", "upload"), avatar=self.initial_data.get("avatar"), filename=u"{}.png".format(org.slug), ) if "require2FA" in self.initial_data and self.initial_data["require2FA"] is True: org.handle_2fa_required(self.context["request"]) return org, changed_data
class OrganizationSerializer(serializers.ModelSerializer): accountRateLimit = serializers.IntegerField(min_value=0, max_value=1000000) projectRateLimit = serializers.IntegerField(min_value=50, max_value=100) slug = serializers.RegexField(r'^[a-z0-9_\-]+$', max_length=50, required=False) avatar = AvatarField() avatarType = serializers.ChoiceField(choices=( ('upload', 'upload'), ('letter_avatar', 'letter_avatar'), )) class Meta: model = Organization fields = ('name', 'slug') def validate_slug(self, attrs, source): value = attrs[source] if Organization.objects.filter(slug=value).exclude(id=self.object.id): raise serializers.ValidationError( 'The slug "%s" is already in use.' % (value, )) return attrs def validate(self, attrs): attrs = super(OrganizationSerializer, self).validate(attrs) if attrs.get('avatarType') == 'upload': has_existing_file = OrganizationAvatar.objects.filter( organization=self.object, file__isnull=False, ).exists() if not has_existing_file and not attrs.get('avatar'): raise serializers.ValidationError({ 'avatarType': 'Cannot set avatarType to upload without avatar', }) return attrs def save(self): rv = super(OrganizationSerializer, self).save() # XXX(dcramer): this seems wrong, but cant find documentation on how to # actually access this data if 'projectRateLimit' in self.init_data: OrganizationOption.objects.set_value( organization=self.object, key='sentry:project-rate-limit', value=int(self.init_data['projectRateLimit']), ) if 'accountRateLimit' in self.init_data: OrganizationOption.objects.set_value( organization=self.object, key='sentry:account-rate-limit', value=int(self.init_data['accountRateLimit']), ) if 'avatar' in self.init_data or 'avatarType' in self.init_data: OrganizationAvatar.save_avatar( relation={'organization': self.object}, type=self.init_data.get('avatarType', 'upload'), avatar=self.init_data.get('avatar'), filename='{}.png'.format(self.object.slug), ) return rv