mirror of
https://github.com/discourse/discourse.git
synced 2024-11-26 20:33:38 +08:00
FEATURE: automatic group membership based on email address
This commit is contained in:
parent
a2099110aa
commit
256519dddf
|
@ -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}}
|
||||||
|
|
|
@ -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')
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
23
app/jobs/regular/automatic_group_membership.rb
Normal file
23
app/jobs/regular/automatic_group_membership.rb
Normal 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
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
|
@ -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
|
||||||
|
|
22
spec/jobs/automatic_group_membership_spec.rb
Normal file
22
spec/jobs/automatic_group_membership_spec.rb
Normal 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
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue
Block a user