Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: optionally restrict list ownership to specific domains (owner_domain) #131

Merged
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions default/edit_list.conf
Original file line number Diff line number Diff line change
@@ -96,6 +96,11 @@ owner.info owner hidden

owner_include owner read

owner_domain owner read
owner_domain_min owner read
owner_domain privileged_owner write
owner_domain_min privileged_owner write

editor owner read
editor privileged_owner write

6 changes: 6 additions & 0 deletions default/mail_tt2/report.tt2
Original file line number Diff line number Diff line change
@@ -799,6 +799,12 @@ Warning: this message may already have been sent by one of the list's editors.[%
[%~ ELSIF report_entry == 'merge_failed' ~%]
[%|loc(report_param.error)%]Your message cannot be personalized due to error: %1. Please check template syntax.[%END%]

[%~ ELSIF report_entry == 'owner_domain' ~%]
[%|loc(report_param.value,report_param.owner_domain)%]%1: all list owners must be in the following domains: %2.[%END%]

[%~ ELSIF report_entry == 'owner_domain_min' ~%]
[%|loc(report_param.value,report_param.owner_domain_min,report_param.owner_domain)%]Unable to reduce the number of list owners in required domains to %1. Domains that count toward the minimum requirement of %2: %3[%END%]

[%~ END ~%]

[%~ END ~%]
24 changes: 24 additions & 0 deletions src/lib/Sympa/ConfDef.pm
Original file line number Diff line number Diff line change
@@ -2005,6 +2005,30 @@ our @params = (
'default' => 'off',
'file' => 'sympa.conf',
},
{ 'name' => 'owner_domain',
'sample' => 'domain1.tld domain2.tld',
'gettext_id' => 'List of required domains for list owner addresses',
'file' => 'sympa.conf',
'optional' => '1',
'split_char' => ' ',
'vhost' => '1',
'edit' => '1',
'gettext_comment' =>
'Restrict list ownership to addresses in the specified domains. This can be used to reserve list ownership to a group of trusted users from a set of domains associated with an organization, while allowing editors and subscribers from the Internet at large.',
'default' => undef,
},
{ 'name' => 'owner_domain_min',
'sample' => '1',
'gettext_id' => 'Minimum number of list owners that must match owner_domain restriction',
'file' => 'sympa.conf',
'default' => '0',
'optional' => '1',
'vhost' => '1',
'edit' => '1',
'gettext_comment' =>
'Minimum number of list owners that must satisfy the owner_domain restriction. The default of zero (0) means *all* list owners must match. Setting to 1 requires only one list owner to match owner_domain; all other owners can be from any domain. This setting can be used to ensure that there is always at least one known contact point for a mailing list.',
},

{ 'name' => 'edit_list', #FIXME:maybe not used
'default' => 'owner',
'file' => 'sympa.conf',
92 changes: 92 additions & 0 deletions src/lib/Sympa/List/Config.pm
Original file line number Diff line number Diff line change
@@ -31,6 +31,8 @@ use Sympa::Robot;
use Sympa::Tools::Data;
use Sympa::Tools::Text;

my $log = Sympa::Log->instance;

sub new {
my $class = shift;
my $context = shift;
@@ -774,6 +776,84 @@ sub _sanitize_changes_leaf {
}
}

#
# Global validations examine the entire configuration for semantic errors or
# requirements that can't be detected within a single paragraph.
#
# The 'owner_domain' option is an example of this need. The restriction applies
# to the entire set of owner addresses, not just a single owner.
#
# Error data is returned in a hashref with the usual keys.
#
my %global_validations = (
owner_domain => sub {
my $self = shift;
my $new = shift;

my $pinfo = $self->{_pinfo};
my $loglevel = 'debug'; # was set to 'info' during development

# gather parameters
my $owner_domain = $self->get('owner_domain');
if (defined($self->get_change('owner_domain'))) {
$owner_domain = $self->get_change('owner_domain');
}
(my $domainrex = "[.\@]($owner_domain)\$") =~ s/ /|/g;

my $owner_domain_min = $self->get('owner_domain_min');
if (defined($self->get_change('owner_domain_min'))) {
$owner_domain_min = $self->get_change('owner_domain_min');
}
$owner_domain_min ||= 0;

# if no owner_domain setting, do nothing
return if ($owner_domain =~ /^\s*$/);

# calculate updated owner list, including deletions
my @owner = map { $_->{'email'} } @{$self->get('owner')};
my $changes = $self->get_change('owner');
map { $owner[$_] = $changes->{$_}->{'email'} } CORE::keys %$changes;
@owner = grep defined, @owner;

# count matches and non-matches
my @non_matching_owners = grep {!/$domainrex/} @owner;
my @matching_owners = grep {/$domainrex/} @owner;

my $non_matching_count = 1 + $#non_matching_owners;
my $matching_owner_count = 1 + $#matching_owners;

# logging
$log->syslog($loglevel, "owner_domain: $owner_domain");
$log->syslog($loglevel, "owner_domain_min: $owner_domain_min");
$log->syslog($loglevel, "owners: " . join(",", @owner));
$log->syslog($loglevel, "total owners: " . ($#owner + 1));
$log->syslog($loglevel, "domainrex: $domainrex");
$log->syslog($loglevel, "matching_owners: " . join(",", @matching_owners));
$log->syslog($loglevel, "matching_owner_count: $matching_owner_count");
$log->syslog($loglevel, "non_matching_owners: " . join(",", @non_matching_owners));
$log->syslog($loglevel, "non_matching_count: $non_matching_count");

# apply different rules based on min domain requirement
if ($owner_domain_min == 0) {
return ('owner_domain',
{p_info => $pinfo->{'owner'},
p_paths => ['owner'],
owner_domain => $owner_domain,
value => join(' ', @non_matching_owners)})
unless ($non_matching_count == 0);
} else {
return ('owner_domain_min',
{p_info => $pinfo->{'owner'},
p_paths => ['owner'],
owner_domain => $owner_domain,
owner_domain_min => $owner_domain_min,
value => $matching_owner_count})
unless ($matching_owner_count >= $owner_domain_min);
}
return '';
},
);

# Validates changes on list configuration.
# Context:
# - $list: An instance of Sympa::List.
@@ -815,6 +895,18 @@ sub _validate_changes {
$ret = 'invalid' if $r eq 'invalid';
}

# review the entire new configuration as a whole
foreach my $validation (CORE::keys %global_validations) {
next unless ref $global_validations{$validation} eq 'CODE';
my ($error, $err_info) = $global_validations{$validation}->($self, $new);
next unless $error;

push @$errors,
[
'user', $error, $err_info
];
$ret = 'invalid';
}
return '' unless %$new;
return $ret;
}
25 changes: 25 additions & 0 deletions src/lib/Sympa/ListDef.pm
Original file line number Diff line number Diff line change
@@ -320,6 +320,31 @@ our %pinfo = (
'default' => {'conf' => 'default_list_priority'}
},

'owner_domain' => {
order => 10.13,
'group' => 'description',
'gettext_id' => "Required domains for list owners",
'gettext_comment' =>
'Restrict list ownership to addresses in the specified domains.',
'format' => '[\w\.\- ]+', # same as Sympa::Regexps::host plus space
'length' => 72,
'occurrence' => '0-1',
'split_char' => ' ',
'default' => {'conf' => 'owner_domain'},
},

'owner_domain_min' => {
order => 10.14,
'group' => 'description',
'gettext_id' => "Minimum owners in required domains",
'gettext_comment' =>
'Require list ownership by a minimum number of addresses in the specified domains.',
'format' => '\d+',
'length' => 2,
'occurrence' => '0-1',
'default' => {'conf' => 'owner_domain_min'},
},

### Sending page ###

'send' => {