FEATURE: automatic group membership based on email address

This commit is contained in:
Régis Hanol 2015-01-23 18:25:43 +01:00
parent a2099110aa
commit 256519dddf
13 changed files with 138 additions and 4 deletions

View File

@ -43,6 +43,17 @@
{{combo-box name="alias" valueAttribute="value" value=alias_level content=aliasLevelOptions}} {{combo-box name="alias" valueAttribute="value" value=alias_level content=aliasLevelOptions}}
</div> </div>
{{#unless automatic}}
<div>
<label for="automatic_membership">{{i18n 'admin.groups.automatic_membership_email_domains'}}</label>
{{list-setting name="automatic_membership" settingValue=automatic_membership_email_domains}}
<label>
{{input type="checkbox" checked=automatic_membership_retroactive}}
{{i18n 'admin.groups.automatic_membership_retroactive'}}
</label>
</div>
{{/unless}}
<div class='buttons'> <div class='buttons'>
<button {{action "save"}} {{bind-attr disabled="disableSave"}} class='btn btn-primary'>{{i18n 'admin.customize.save'}}</button> <button {{action "save"}} {{bind-attr disabled="disableSave"}} class='btn btn-primary'>{{i18n 'admin.customize.save'}}</button>
{{#unless automatic}} {{#unless automatic}}

View File

@ -68,7 +68,9 @@ Discourse.Group = Discourse.Model.extend({
return { return {
name: this.get('name'), name: this.get('name'),
alias_level: this.get('alias_level'), alias_level: this.get('alias_level'),
visible: !!this.get('visible') visible: !!this.get('visible'),
automatic_membership_email_domains: this.get('automatic_membership_email_domains'),
automatic_membership_retroactive: !!this.get('automatic_membership_retroactive')
}; };
}, },

View File

@ -463,6 +463,7 @@ section.details {
.groups { .groups {
.ac-wrap { .ac-wrap {
width: 100% !important; width: 100% !important;
border-color: scale-color($primary, $lightness: 75%);
.item { .item {
width: 190px; width: 190px;
margin-right: 0 !important; margin-right: 0 !important;
@ -480,6 +481,13 @@ section.details {
.controls { .controls {
margin-top: 10px; margin-top: 10px;
} }
.select2-container {
width: 100%;
}
.select2-choices {
width: 100%;
border-color: scale-color($primary, $lightness: 75%);
}
} }
// Customise area // Customise area

View File

@ -21,8 +21,11 @@ class Admin::GroupsController < Admin::AdminController
def create def create
group = Group.new group = Group.new
group.name = (params[:name] || '').strip group.name = (params[:name] || '').strip
group.visible = params[:visible] == "true" group.visible = params[:visible] == "true"
group.automatic_membership_email_domains = params[:automatic_membership_email_domains]
group.automatic_membership_retroactive = params[:automatic_membership_retroactive] == "true"
if group.save if group.save
render_serialized(group, BasicGroupSerializer) render_serialized(group, BasicGroupSerializer)
@ -36,11 +39,13 @@ class Admin::GroupsController < Admin::AdminController
group.alias_level = params[:alias_level].to_i if params[:alias_level].present? group.alias_level = params[:alias_level].to_i if params[:alias_level].present?
group.visible = params[:visible] == "true" group.visible = params[:visible] == "true"
group.automatic_membership_email_domains = params[:automatic_membership_email_domains]
group.automatic_membership_retroactive = params[:automatic_membership_retroactive] == "true"
# group rename is ignored for automatic groups # group rename is ignored for automatic groups
group.name = params[:name] if params[:name] && !group.automatic group.name = params[:name] if params[:name] && !group.automatic
if group.save if group.save
render json: success_json render_serialized(group, BasicGroupSerializer)
else else
render_json_error group render_json_error group
end end

View File

@ -0,0 +1,23 @@
module Jobs
class AutomaticGroupMembership < Jobs::Base
def execute(args)
group_id = args[:group_id]
raise Discourse::InvalidParameters.new(:group_id) if group_id.blank?
group = Group.find(group_id)
return unless group.automatic_membership_retroactive
domains = group.automatic_membership_email_domains.gsub('.', '\.')
User.where("email ~* '@(#{domains})'").find_each do |user|
group.add(user) rescue ActiveRecord::RecordNotUnique
end
end
end
end

View File

@ -11,6 +11,7 @@ class Group < ActiveRecord::Base
has_many :managers, through: :group_managers has_many :managers, through: :group_managers
after_save :destroy_deletions after_save :destroy_deletions
after_save :automatic_group_membership
validate :name_format_validator validate :name_format_validator
validates_uniqueness_of :name, case_sensitive: false validates_uniqueness_of :name, case_sensitive: false
@ -301,6 +302,12 @@ class Group < ActiveRecord::Base
@deletions = nil @deletions = nil
end end
def automatic_group_membership
if self.automatic_membership_retroactive
Jobs.enqueue(:automatic_group_membership, group_id: self.id)
end
end
end end
# == Schema Information # == Schema Information

View File

@ -79,6 +79,7 @@ class User < ActiveRecord::Base
after_create :create_user_stat after_create :create_user_stat
after_create :create_user_profile after_create :create_user_profile
after_create :ensure_in_trust_level_group after_create :ensure_in_trust_level_group
after_create :automatic_group_membership
before_save :update_username_lower before_save :update_username_lower
before_save :ensure_password_is_hashed before_save :ensure_password_is_hashed
@ -715,6 +716,17 @@ class User < ActiveRecord::Base
Group.user_trust_level_change!(id, trust_level) Group.user_trust_level_change!(id, trust_level)
end end
def automatic_group_membership
Group.where(automatic: false)
.where("LENGTH(COALESCE(automatic_membership_email_domains, '')) > 0")
.each do |group|
domains = group.automatic_membership_email_domains.gsub('.', '\.')
if self.email =~ Regexp.new("@(#{domains})", true)
group.add(self) rescue ActiveRecord::RecordNotUnique
end
end
end
def create_user_stat def create_user_stat
stat = UserStat.new(new_since: Time.now) stat = UserStat.new(new_since: Time.now)
stat.user_id = id stat.user_id = id

View File

@ -1,3 +1,14 @@
class BasicGroupSerializer < ApplicationSerializer class BasicGroupSerializer < ApplicationSerializer
attributes :id, :automatic, :name, :user_count, :alias_level, :visible attributes :id,
:automatic,
:name,
:user_count,
:alias_level,
:visible,
:automatic_membership_email_domains,
:automatic_membership_retroactive
def automatic_membership_email_domains
object.automatic_membership_email_domains.presence || ""
end
end end

View File

@ -1633,6 +1633,8 @@ en:
add_members: "Add members" add_members: "Add members"
custom: "Custom" custom: "Custom"
automatic: "Automatic" automatic: "Automatic"
automatic_membership_email_domains: "Users registering with an email from that list will automatically be a member of this group"
automatic_membership_retroactive: "Registered users matching this list are also member of the group"
api: api:
generate_master: "Generate Master API Key" generate_master: "Generate Master API Key"

View File

@ -0,0 +1,6 @@
class AddAutomaticMembershipToGroup < ActiveRecord::Migration
def change
add_column :groups, :automatic_membership_email_domains, :text
add_column :groups, :automatic_membership_retroactive, :boolean, default: false
end
end

View File

@ -25,7 +25,9 @@ describe Admin::GroupsController do
"user_count"=>1, "user_count"=>1,
"automatic"=>false, "automatic"=>false,
"alias_level"=>0, "alias_level"=>0,
"visible"=>true "visible"=>true,
"automatic_membership_email_domains"=>"",
"automatic_membership_retroactive"=>false
}]) }])
end end
@ -57,6 +59,18 @@ describe Admin::GroupsController do
expect(group.visible).to eq(true) expect(group.visible).to eq(true)
end end
it "doesn't launch the 'automatic group membership' job when it's not retroactive" do
Jobs.expects(:enqueue).never
xhr :put, :update, id: 1, automatic_membership_retroactive: "false"
expect(response).to be_success
end
it "launches the 'automatic group membership' job when it's retroactive" do
Jobs.expects(:enqueue).with(:automatic_group_membership, group_id: 1)
xhr :put, :update, id: 1, automatic_membership_retroactive: "true"
expect(response).to be_success
end
end end
context ".destroy" do context ".destroy" do

View File

@ -0,0 +1,22 @@
require 'spec_helper'
require_dependency 'jobs/regular/automatic_group_membership'
describe Jobs::AutomaticGroupMembership do
it "raises an error when the group id is missing" do
expect { Jobs::AutomaticGroupMembership.new.execute({}) }.to raise_error(Discourse::InvalidParameters)
end
it "updates the membership" do
user1 = Fabricate(:user, email: "foo@wat.com")
user2 = Fabricate(:user, email: "foo@bar.com")
group = Fabricate(:group, automatic_membership_email_domains: "wat.com", automatic_membership_retroactive: true)
Jobs::AutomaticGroupMembership.new.execute(group_id: group.id)
group.reload
expect(group.users.include?(user1)).to eq(true)
expect(group.users.include?(user2)).to eq(false)
end
end

View File

@ -1213,4 +1213,15 @@ describe User do
end end
describe "automatic group membership" do
it "is automatically added to a group when the email matches" do
group = Fabricate(:group, automatic_membership_email_domains: "bar.com|wat.com")
user = Fabricate(:user, email: "foo@bar.com")
group.reload
expect(group.users.include?(user)).to eq(true)
end
end
end end