diff --git a/app/assets/javascripts/discourse/controllers/invite.js.es6 b/app/assets/javascripts/discourse/controllers/invite.js.es6 index 965d4128dca..6adc0f02081 100644 --- a/app/assets/javascripts/discourse/controllers/invite.js.es6 +++ b/app/assets/javascripts/discourse/controllers/invite.js.es6 @@ -21,6 +21,7 @@ export default ObjectController.extend(ModalFunctionality, { if (this.get('saving')) return true; if (this.blank('email')) return true; if (!Discourse.Utilities.emailValid(this.get('email'))) return true; + if (this.get('model.details.can_invite_to')) return false; if (this.get('isPrivateTopic') && this.blank('groupNames')) return true; return false; }.property('email', 'isPrivateTopic', 'groupNames', 'saving'), diff --git a/app/models/invite.rb b/app/models/invite.rb index 3ad29103284..6b84554d2b5 100644 --- a/app/models/invite.rb +++ b/app/models/invite.rb @@ -56,6 +56,21 @@ class Invite < ActiveRecord::Base end + def add_groups_for_topic(topic) + if topic.category + (topic.category.groups - groups).each { |group| group.add(user) } + end + end + + def self.extend_permissions(topic, user, invited_by) + if topic.private_message? + topic.grant_permission_to_user(user.email) + elsif topic.category && topic.category.groups.any? + if Guardian.new(invited_by).can_invite_to?(topic) + (topic.category.groups - user.groups).each { |group| group.add(user) } + end + end + end # Create an invite for a user, supplying an optional topic # # Return the previously existing invite if already exists. Returns nil if the invite can't be created. @@ -64,7 +79,7 @@ class Invite < ActiveRecord::Base user = User.find_by(email: lower_email) if user - topic.grant_permission_to_user(lower_email) if topic && topic.private_message? + extend_permissions(topic, user, invited_by) if topic return nil end @@ -93,6 +108,11 @@ class Invite < ActiveRecord::Base group_ids.each do |group_id| invite.invited_groups.create!(group_id: group_id) end + else + if topic && topic.category # && Guardian.new(invited_by).can_invite_to?(topic) + group_ids = topic.category.groups.pluck(:id) - invite.invited_groups.pluck(:group_id) + group_ids.each { |group_id| invite.invited_groups.create!(group_id: group_id) } + end end Jobs.enqueue(:invite_email, invite_id: invite.id) diff --git a/lib/guardian.rb b/lib/guardian.rb index d644e5485c3..d3e2911ee1a 100644 --- a/lib/guardian.rb +++ b/lib/guardian.rb @@ -197,6 +197,12 @@ class Guardian is_me?(user) end + def invitations_allowed? + !SiteSetting.enable_sso && + SiteSetting.enable_local_logins && + (!SiteSetting.must_approve_users? || is_staff?) + end + def can_invite_to_forum?(groups=nil) authenticated? && !SiteSetting.enable_sso && @@ -209,10 +215,21 @@ class Guardian end def can_invite_to?(object, group_ids=nil) - can_invite = can_see?(object) && can_invite_to_forum? && ( group_ids.blank? || is_admin? ) - #TODO how should invite to PM work? - can_invite = can_invite && ( !object.category.read_restricted || is_admin? ) if object.is_a?(Topic) && object.category - can_invite + return false if ! authenticated? + return false if ! invitations_allowed? + return true if is_admin? + return false if ! can_see?(object) + + return false if group_ids.present? + + if object.is_a?(Topic) && object.category + if object.category.groups.any? + return true if object.category.groups.all? { |g| can_edit_group?(g) } + end + return false if object.category.read_restricted + end + + user.has_trust_level?(TrustLevel[2]) end def can_bulk_invite_to_forum?(user) diff --git a/spec/components/guardian_spec.rb b/spec/components/guardian_spec.rb index 6cb9eaf3bcb..f685842292d 100644 --- a/spec/components/guardian_spec.rb +++ b/spec/components/guardian_spec.rb @@ -266,6 +266,9 @@ describe Guardian do let(:user) { topic.user } let(:moderator) { Fabricate(:moderator) } let(:admin) { Fabricate(:admin) } + let(:private_category) { Fabricate(:private_category, group: group) } + let(:group_private_topic) { Fabricate(:topic, category: private_category) } + let(:group_manager) { group_private_topic.user.tap { |u| group.add(u); group.appoint_manager(u) } } it 'handles invitation correctly' do expect(Guardian.new(nil).can_invite_to?(topic)).to be_falsey @@ -298,6 +301,9 @@ describe Guardian do expect(Guardian.new(admin).can_invite_to?(private_topic)).to be_truthy end + it 'returns true for a group manager' do + expect(Guardian.new(group_manager).can_invite_to?(group_private_topic)).to be_truthy + end end describe 'can_see?' do diff --git a/spec/controllers/topics_controller_spec.rb b/spec/controllers/topics_controller_spec.rb index 490545cfe55..0036c209b42 100644 --- a/spec/controllers/topics_controller_spec.rb +++ b/spec/controllers/topics_controller_spec.rb @@ -790,6 +790,20 @@ describe TopicsController do expect { xhr :post, :invite, topic_id: 1, email: 'jake@adventuretime.ooo' }.to raise_error(Discourse::NotLoggedIn) end + describe 'when logged in as group manager' do + let(:group_manager) { log_in } + let(:group) { Fabricate(:group).tap { |g| g.add(group_manager); g.appoint_manager(group_manager) } } + let(:private_category) { Fabricate(:private_category, group: group) } + let(:group_private_topic) { Fabricate(:topic, category: private_category, user: group_manager) } + let(:recipient) { 'jake@adventuretime.ooo' } + + it "should attach group to the invite" do + xhr :post, :invite, topic_id: group_private_topic.id, user: recipient + expect(response).to be_success + expect(Invite.find_by(email: recipient).groups).to eq([group]) + end + end + describe 'when logged in' do before do @topic = Fabricate(:topic, user: log_in) @@ -806,7 +820,7 @@ describe TopicsController do end end - describe 'with permission' do + describe 'with admin permission' do let!(:admin) do log_in :admin diff --git a/spec/fabricators/category_fabricator.rb b/spec/fabricators/category_fabricator.rb index 1f237b5d684..5a23366276e 100644 --- a/spec/fabricators/category_fabricator.rb +++ b/spec/fabricators/category_fabricator.rb @@ -13,3 +13,15 @@ Fabricator(:happy_category, from: :category) do slug 'happy' user end + +Fabricator(:private_category, from: :category) do + transient :group + + name 'Private Category' + slug 'private' + user + after_build do |cat, transients| + cat.update!(read_restricted: true) + cat.category_groups.build(group_id: transients[:group].id, permission_type: :full) + end +end diff --git a/spec/models/invite_spec.rb b/spec/models/invite_spec.rb index a7a1b02a2ba..9430203306e 100644 --- a/spec/models/invite_spec.rb +++ b/spec/models/invite_spec.rb @@ -127,6 +127,28 @@ describe Invite do end end + context 'to a group-private topic' do + let(:group) { Fabricate(:group) } + let(:private_category) { Fabricate(:private_category, group: group) } + let(:group_private_topic) { Fabricate(:topic, category: private_category) } + let(:inviter) { group_private_topic.user } + + before do + @invite = group_private_topic.invite_by_email(inviter, iceking) + end + + it 'should add the groups to the invite' do + expect(@invite.groups).to eq([group]) + end + + context 'when duplicated' do + it 'should not duplicate the groups' do + expect(group_private_topic.invite_by_email(inviter, iceking)).to eq(@invite) + expect(@invite.groups).to eq([group]) + end + end + end + context 'an existing user' do let(:topic) { Fabricate(:topic, category_id: nil, archetype: 'private_message') } let(:coding_horror) { Fabricate(:coding_horror) } diff --git a/spec/models/topic_spec.rb b/spec/models/topic_spec.rb index 0f49c414312..66ce8e17b3f 100644 --- a/spec/models/topic_spec.rb +++ b/spec/models/topic_spec.rb @@ -1,4 +1,4 @@ -# encoding: UTF-8 +# encoding: utf-8 require 'spec_helper' require_dependency 'post_destroyer' @@ -1352,4 +1352,37 @@ describe Topic do topic.last_posted_at = 1.minute.ago expect(topic.save).to eq(true) end + + context 'invite by group manager' do + let(:group_manager) { Fabricate(:user) } + let(:group) { Fabricate(:group).tap { |g| g.add(group_manager); g.appoint_manager(group_manager) } } + let(:private_category) { Fabricate(:private_category, group: group) } + let(:group_private_topic) { Fabricate(:topic, category: private_category, user: group_manager) } + + context 'to an email' do + let(:randolph) { 'randolph@duke.ooo' } + + it "should attach group to the invite" do + invite = group_private_topic.invite(group_manager, randolph) + expect(invite.groups).to eq([group]) + end + end + + # should work for an existing user - give access, send notification + context 'to an existing user' do + let(:walter) { Fabricate(:walter_white) } + + it "should add user to the group" do + expect(Guardian.new(walter).can_see?(group_private_topic)).to be_falsey + invite = group_private_topic.invite(group_manager, walter.email) + expect(invite).to be_nil + expect(walter.groups).to include(group) + expect(Guardian.new(walter).can_see?(group_private_topic)).to be_truthy + end + end + + context 'to a previously-invited user' do + + end + end end