Commit e3f361faedc24dcfc1a5b57f0913ecb2dbb89247

Authored by Dmitriy Zaporozhets
2 parents 8674e1c2 c6096e47

Merge branch 'refactor/group_owners' of /home/git/repositories/gitlab/gitlabhq

app/controllers/admin/groups_controller.rb
... ... @@ -20,9 +20,9 @@ class Admin::GroupsController < Admin::ApplicationController
20 20 def create
21 21 @group = Group.new(params[:group])
22 22 @group.path = @group.name.dup.parameterize if @group.name
23   - @group.owner = current_user
24 23  
25 24 if @group.save
  25 + @group.add_owner(current_user)
26 26 redirect_to [:admin, @group], notice: 'Group was successfully created.'
27 27 else
28 28 render "new"
... ... @@ -30,14 +30,7 @@ class Admin::GroupsController < Admin::ApplicationController
30 30 end
31 31  
32 32 def update
33   - group_params = params[:group].dup
34   - owner_id =group_params.delete(:owner_id)
35   -
36   - if owner_id
37   - @group.change_owner(User.find(owner_id))
38   - end
39   -
40   - if @group.update_attributes(group_params)
  33 + if @group.update_attributes(params[:group])
41 34 redirect_to [:admin, @group], notice: 'Group was successfully updated.'
42 35 else
43 36 render "edit"
... ...
app/controllers/groups_controller.rb
... ... @@ -21,9 +21,9 @@ class GroupsController < ApplicationController
21 21 def create
22 22 @group = Group.new(params[:group])
23 23 @group.path = @group.name.dup.parameterize if @group.name
24   - @group.owner = current_user
25 24  
26 25 if @group.save
  26 + @group.add_owner(current_user)
27 27 redirect_to @group, notice: 'Group was successfully created.'
28 28 else
29 29 render action: "new"
... ... @@ -73,15 +73,7 @@ class GroupsController < ApplicationController
73 73 end
74 74  
75 75 def update
76   - group_params = params[:group].dup
77   - owner_id = group_params.delete(:owner_id)
78   -
79   - if owner_id
80   - @group.owner = User.find(owner_id)
81   - @group.save
82   - end
83   -
84   - if @group.update_attributes(group_params)
  76 + if @group.update_attributes(params[:group])
85 77 redirect_to @group, notice: 'Group was successfully updated.'
86 78 else
87 79 render action: "edit"
... ...
app/controllers/profiles/groups_controller.rb
... ... @@ -8,8 +8,8 @@ class Profiles::GroupsController < ApplicationController
8 8 def leave
9 9 @users_group = group.users_groups.where(user_id: current_user.id).first
10 10  
11   - if group.owner == current_user
12   - redirect_to(profile_groups_path, alert: "You can't leave group. You must transfer it to another owner before leaving.")
  11 + if group.last_owner?(current_user)
  12 + redirect_to(profile_groups_path, alert: "You can't leave group. You must add at least one more owner to it.")
13 13 else
14 14 @users_group.destroy
15 15 redirect_to(profile_groups_path, info: "You left #{group.name} group.")
... ...
app/controllers/users_groups_controller.rb
... ... @@ -19,7 +19,7 @@ class UsersGroupsController < ApplicationController
19 19  
20 20 def destroy
21 21 @users_group = @group.users_groups.find(params[:id])
22   - @users_group.destroy unless @users_group.user == @group.owner
  22 + @users_group.destroy
23 23  
24 24 respond_to do |format|
25 25 format.html { redirect_to members_group_path(@group), notice: 'User was successfully removed from group.' }
... ...
app/models/ability.rb
... ... @@ -79,7 +79,7 @@ class Ability
79 79 rules << project_admin_rules
80 80 end
81 81  
82   - if project.group && project.group.owners.include?(user)
  82 + if project.group && project.group.has_owner?(user)
83 83 rules << project_admin_rules
84 84 end
85 85  
... ... @@ -159,7 +159,7 @@ class Ability
159 159 end
160 160  
161 161 # Only group owner and administrators can manage group
162   - if group.owners.include?(user) || user.admin?
  162 + if group.has_owner?(user) || user.admin?
163 163 rules << [
164 164 :manage_group,
165 165 :manage_namespace
... ...
app/models/group.rb
... ... @@ -16,14 +16,12 @@ class Group &lt; Namespace
16 16 has_many :users_groups, dependent: :destroy
17 17 has_many :users, through: :users_groups
18 18  
19   - after_create :add_owner
20   -
21 19 def human_name
22 20 name
23 21 end
24 22  
25 23 def owners
26   - @owners ||= (users_groups.owners.map(&:user) << owner).uniq
  24 + @owners ||= users_groups.owners.map(&:user)
27 25 end
28 26  
29 27 def add_users(user_ids, group_access)
... ... @@ -36,20 +34,19 @@ class Group &lt; Namespace
36 34 self.users_groups.create(user_id: user.id, group_access: group_access)
37 35 end
38 36  
39   - def change_owner(user)
40   - self.owner = user
41   - membership = users_groups.where(user_id: user.id).first
  37 + def add_owner(user)
  38 + self.add_user(user, UsersGroup::OWNER)
  39 + end
42 40  
43   - if membership
44   - membership.update_attributes(group_access: UsersGroup::OWNER)
45   - else
46   - add_owner
47   - end
  41 + def has_owner?(user)
  42 + owners.include?(user)
48 43 end
49 44  
50   - private
  45 + def last_owner?(user)
  46 + has_owner?(user) && owners.size == 1
  47 + end
51 48  
52   - def add_owner
53   - self.add_users([owner.id], UsersGroup::OWNER)
  49 + def members
  50 + users_groups
54 51 end
55 52 end
... ...
app/models/namespace.rb
... ... @@ -20,7 +20,7 @@ class Namespace &lt; ActiveRecord::Base
20 20 has_many :projects, dependent: :destroy
21 21 belongs_to :owner, class_name: "User"
22 22  
23   - validates :owner, presence: true
  23 + validates :owner, presence: true, unless: ->(n) { n.type == "Group" }
24 24 validates :name, presence: true, uniqueness: true,
25 25 length: { within: 0..255 },
26 26 format: { with: Gitlab::Regex.name_regex,
... ...
app/models/project.rb
... ... @@ -249,10 +249,10 @@ class Project &lt; ActiveRecord::Base
249 249 end
250 250  
251 251 def owner
252   - if namespace
253   - namespace_owner
  252 + if group
  253 + group
254 254 else
255   - creator
  255 + namespace.try(:owner)
256 256 end
257 257 end
258 258  
... ... @@ -276,10 +276,6 @@ class Project &lt; ActiveRecord::Base
276 276 end
277 277 end
278 278  
279   - def namespace_owner
280   - namespace.try(:owner)
281   - end
282   -
283 279 def path_with_namespace
284 280 if namespace
285 281 namespace.path + '/' + path
... ...
app/models/user.rb
... ... @@ -135,7 +135,7 @@ class User &lt; ActiveRecord::Base
135 135 # Remove user from all groups
136 136 user.users_groups.find_each do |membership|
137 137 # skip owned resources
138   - next if membership.group.owners.include?(user)
  138 + next if membership.group.last_owner?(user)
139 139  
140 140 return false unless membership.destroy
141 141 end
... ...
app/services/system_hooks_service.rb
... ... @@ -23,13 +23,15 @@ class SystemHooksService
23 23  
24 24 case model
25 25 when Project
  26 + owner = model.owner
  27 +
26 28 data.merge!({
27 29 name: model.name,
28 30 path: model.path,
29 31 path_with_namespace: model.path_with_namespace,
30 32 project_id: model.id,
31   - owner_name: model.owner.name,
32   - owner_email: model.owner.email
  33 + owner_name: owner.name,
  34 + owner_email: owner.respond_to?(:email) ? owner.email : nil
33 35 })
34 36 when User
35 37 data.merge!({
... ...
app/views/admin/groups/index.html.haml
... ... @@ -31,11 +31,8 @@
31 31  
32 32 .clearfix.light.append-bottom-10
33 33 %span
34   - %b Owner:
35   - - if group.owner
36   - = link_to group.owner_name, admin_user_path(group.owner)
37   - - else
38   - (deleted)
  34 + %b Members:
  35 + %span.badge= group.members.size
39 36 \|
40 37 %span
41 38 %b Projects:
... ...
app/views/admin/groups/show.html.haml
... ... @@ -25,26 +25,6 @@
25 25 = @group.description
26 26  
27 27 %li
28   - %span.light Owned by:
29   - %strong
30   - - if @group.owner
31   - = link_to @group.owner_name, admin_user_path(@group.owner)
32   - - else
33   - (deleted)
34   - .pull-right
35   - = link_to "#", class: "btn btn-small change-owner-link" do
36   - %i.icon-edit
37   - Change owner
38   - %li.change-owner-holder.hide.bgred
39   - .form-holder
40   - %strong.cred New Owner:
41   - = form_for [:admin, @group] do |f|
42   - = users_select_tag(:"group[owner_id]")
43   - .prepend-top-10
44   - = f.submit 'Change Owner', class: "btn btn-remove"
45   - = link_to "Cancel", "#", class: "btn change-owner-cancel-link"
46   -
47   - %li
48 28 %span.light Created at:
49 29 %strong
50 30 = @group.created_at.stamp("March 1, 1999")
... ... @@ -92,4 +72,5 @@
92 72 = link_to user.name, admin_user_path(user)
93 73 %span.pull-right.light
94 74 = member.human_access
95   -
  75 + = link_to group_users_group_path(@group, member), confirm: remove_user_from_group_message(@group, user), method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do
  76 + %i.icon-minus.icon-white
... ...
app/views/admin/projects/show.html.haml
... ... @@ -25,7 +25,7 @@
25 25 %span.light Owned by:
26 26 %strong
27 27 - if @project.owner
28   - = link_to @project.owner_name, admin_user_path(@project.owner)
  28 + = link_to @project.owner_name, [:admin, @project.owner]
29 29 - else
30 30 (deleted)
31 31  
... ...
app/views/groups/edit.html.haml
... ... @@ -10,8 +10,6 @@
10 10 %i.icon-folder-close
11 11 Projects
12 12 %li
13   - = link_to 'Transfer', '#tab-transfer', 'data-toggle' => 'tab'
14   - %li
15 13 = link_to 'Remove', '#tab-remove', 'data-toggle' => 'tab'
16 14  
17 15 .span10
... ... @@ -65,17 +63,6 @@
65 63 - if @group.projects.blank?
66 64 %p.nothing_here_message This group has no projects yet
67 65  
68   - .tab-pane#tab-transfer
69   - .ui-box.ui-box-danger
70   - .title Transfer group
71   - .ui-box-body
72   - %p
73   - Transferring group will cause loss of admin control over group and all child projects
74   - = form_for @group do |f|
75   - = users_select_tag(:'group[owner_id]')
76   - %hr
77   - = f.submit 'Transfer group', class: "btn btn-small btn-remove"
78   -
79 66 .tab-pane#tab-remove
80 67 .ui-box.ui-box-danger
81 68 .title Remove group
... ...
app/views/groups/members.html.haml
... ... @@ -2,6 +2,9 @@
2 2 Group members
3 3 %p.light
4 4 Members of group have access to all group projects.
  5 + Read more about permissions
  6 + %strong= link_to "here", help_permissions_path, class: "vlink"
  7 +
5 8 %hr
6 9 - can_manage_group = current_user.can? :manage_group, @group
7 10 .ui-box
... ...
app/views/help/permissions.html.haml
1 1 = render layout: 'help/layout' do
2 2 %h3.page-title Permissions
  3 + %p.light User has different abilities depends on access level he has in particular group or project
  4 + %hr
3 5  
  6 + %h4 Project:
4 7 %table.table
5 8 %thead
6 9 %tr
... ... @@ -158,3 +161,50 @@
158 161 %td
159 162 %td
160 163 %td.permission-x &#10003;
  164 +
  165 + %h4 Group
  166 + %table.table
  167 + %thead
  168 + %tr
  169 + %th Action
  170 + %th Guest
  171 + %th Reporter
  172 + %th Developer
  173 + %th Master
  174 + %th Owner
  175 + %tbody
  176 + %tr
  177 + %td Browse group
  178 + %td.permission-x &#10003;
  179 + %td.permission-x &#10003;
  180 + %td.permission-x &#10003;
  181 + %td.permission-x &#10003;
  182 + %td.permission-x &#10003;
  183 + %tr
  184 + %td Edit group
  185 + %td
  186 + %td
  187 + %td
  188 + %td
  189 + %td.permission-x &#10003;
  190 + %tr
  191 + %td Create project in group
  192 + %td
  193 + %td
  194 + %td
  195 + %td
  196 + %td.permission-x &#10003;
  197 + %tr
  198 + %td Manage group members
  199 + %td
  200 + %td
  201 + %td
  202 + %td
  203 + %td.permission-x &#10003;
  204 + %tr
  205 + %td Remove group
  206 + %td
  207 + %td
  208 + %td
  209 + %td
  210 + %td.permission-x &#10003;
... ...
app/views/users_groups/_users_group.html.haml
... ... @@ -10,7 +10,7 @@
10 10 %span.pull-right
11 11 %strong= member.human_access
12 12  
13   - - if show_controls && user != @group.owner && user != current_user
  13 + - if show_controls && can?(current_user, :manage_group, @group) && current_user != user
14 14 = link_to '#', class: "btn-tiny btn js-toggle-button", title: 'Edit access level' do
15 15 %i.icon-edit
16 16 = link_to group_users_group_path(@group, member), confirm: remove_user_from_group_message(@group, user), method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do
... ... @@ -20,4 +20,4 @@
20 20 = form_for [@group, member], remote: true do |f|
21 21 .alert.prepend-top-20
22 22 = f.select :group_access, options_for_select(UsersGroup.group_access_roles, member.group_access)
23   - = f.submit 'Save', class: 'btn btn-save'
  23 + = f.submit 'Save', class: 'btn btn-save btn-small'
... ...
db/migrate/20130926081215_change_owner_id_for_group.rb 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +class ChangeOwnerIdForGroup < ActiveRecord::Migration
  2 + def up
  3 + change_column :namespaces, :owner_id, :integer, null: true
  4 + end
  5 +
  6 + def down
  7 + change_column :namespaces, :owner_id, :integer, null: false
  8 + end
  9 +end
... ...
db/schema.rb
... ... @@ -11,7 +11,7 @@
11 11 #
12 12 # It's strongly recommended to check this file into your version control system.
13 13  
14   -ActiveRecord::Schema.define(:version => 20130909132950) do
  14 +ActiveRecord::Schema.define(:version => 20130926081215) do
15 15  
16 16 create_table "deploy_keys_projects", :force => true do |t|
17 17 t.integer "deploy_key_id", :null => false
... ... @@ -129,7 +129,7 @@ ActiveRecord::Schema.define(:version =&gt; 20130909132950) do
129 129 create_table "namespaces", :force => true do |t|
130 130 t.string "name", :null => false
131 131 t.string "path", :null => false
132   - t.integer "owner_id", :null => false
  132 + t.integer "owner_id"
133 133 t.datetime "created_at", :null => false
134 134 t.datetime "updated_at", :null => false
135 135 t.string "type"
... ...
features/steps/group/group.rb
... ... @@ -10,7 +10,8 @@ class Groups &lt; Spinach::FeatureSteps
10 10 end
11 11  
12 12 And 'I have group with projects' do
13   - @group = create(:group, owner: current_user)
  13 + @group = create(:group)
  14 + @group.add_owner(current_user)
14 15 @project = create(:project, namespace: @group)
15 16 @event = create(:closed_issue_event, project: @project)
16 17  
... ...
spec/contexts/projects_create_context_spec.rb
... ... @@ -25,13 +25,15 @@ describe Projects::CreateContext do
25 25  
26 26 context 'group namespace' do
27 27 before do
28   - @group = create :group, owner: @user
  28 + @group = create :group
  29 + @group.add_owner(@user)
  30 +
29 31 @opts.merge!(namespace_id: @group.id)
30 32 @project = create_project(@user, @opts)
31 33 end
32 34  
33 35 it { @project.should be_valid }
34   - it { @project.owner.should == @user }
  36 + it { @project.owner.should == @group }
35 37 it { @project.namespace.should == @group }
36 38 end
37 39  
... ...
spec/factories.rb
... ... @@ -71,7 +71,6 @@ FactoryGirl.define do
71 71 factory :group do
72 72 sequence(:name) { |n| "group#{n}" }
73 73 path { name.downcase.gsub(/\s/, '_') }
74   - owner
75 74 type 'Group'
76 75 end
77 76  
... ...
spec/features/security/group_access_spec.rb
... ... @@ -10,11 +10,13 @@ describe &quot;Group access&quot; do
10 10 describe "Group" do
11 11 let(:group) { create(:group) }
12 12  
  13 + let(:owner) { create(:owner) }
13 14 let(:master) { create(:user) }
14 15 let(:reporter) { create(:user) }
15 16 let(:guest) { create(:user) }
16 17  
17 18 before do
  19 + group.add_user(owner, Gitlab::Access::OWNER)
18 20 group.add_user(master, Gitlab::Access::MASTER)
19 21 group.add_user(reporter, Gitlab::Access::REPORTER)
20 22 group.add_user(guest, Gitlab::Access::GUEST)
... ... @@ -23,7 +25,7 @@ describe &quot;Group access&quot; do
23 25 describe "GET /groups/:path" do
24 26 subject { group_path(group) }
25 27  
26   - it { should be_allowed_for group.owner }
  28 + it { should be_allowed_for owner }
27 29 it { should be_allowed_for master }
28 30 it { should be_allowed_for reporter }
29 31 it { should be_allowed_for :admin }
... ... @@ -35,7 +37,7 @@ describe &quot;Group access&quot; do
35 37 describe "GET /groups/:path/issues" do
36 38 subject { issues_group_path(group) }
37 39  
38   - it { should be_allowed_for group.owner }
  40 + it { should be_allowed_for owner }
39 41 it { should be_allowed_for master }
40 42 it { should be_allowed_for reporter }
41 43 it { should be_allowed_for :admin }
... ... @@ -47,7 +49,7 @@ describe &quot;Group access&quot; do
47 49 describe "GET /groups/:path/merge_requests" do
48 50 subject { merge_requests_group_path(group) }
49 51  
50   - it { should be_allowed_for group.owner }
  52 + it { should be_allowed_for owner }
51 53 it { should be_allowed_for master }
52 54 it { should be_allowed_for reporter }
53 55 it { should be_allowed_for :admin }
... ... @@ -59,7 +61,7 @@ describe &quot;Group access&quot; do
59 61 describe "GET /groups/:path/members" do
60 62 subject { members_group_path(group) }
61 63  
62   - it { should be_allowed_for group.owner }
  64 + it { should be_allowed_for owner }
63 65 it { should be_allowed_for master }
64 66 it { should be_allowed_for reporter }
65 67 it { should be_allowed_for :admin }
... ... @@ -71,7 +73,7 @@ describe &quot;Group access&quot; do
71 73 describe "GET /groups/:path/edit" do
72 74 subject { edit_group_path(group) }
73 75  
74   - it { should be_allowed_for group.owner }
  76 + it { should be_allowed_for owner }
75 77 it { should be_denied_for master }
76 78 it { should be_denied_for reporter }
77 79 it { should be_allowed_for :admin }
... ...
spec/models/group_spec.rb
... ... @@ -26,10 +26,10 @@ describe Group do
26 26 it { should validate_uniqueness_of(:name) }
27 27 it { should validate_presence_of :path }
28 28 it { should validate_uniqueness_of(:path) }
29   - it { should validate_presence_of :owner }
  29 + it { should_not validate_presence_of :owner }
30 30  
31 31 describe :users do
32   - it { group.users.should == [group.owner] }
  32 + it { group.users.should == group.owners }
33 33 end
34 34  
35 35 describe :human_name do
... ... @@ -38,7 +38,7 @@ describe Group do
38 38  
39 39 describe :add_users do
40 40 let(:user) { create(:user) }
41   - before { group.add_users([user.id], UsersGroup::MASTER) }
  41 + before { group.add_user(user, UsersGroup::MASTER) }
42 42  
43 43 it { group.users_groups.masters.map(&:user).should include(user) }
44 44 end
... ...
spec/models/project_spec.rb
... ... @@ -85,7 +85,6 @@ describe Project do
85 85 it { should respond_to(:execute_hooks) }
86 86 it { should respond_to(:transfer) }
87 87 it { should respond_to(:name_with_namespace) }
88   - it { should respond_to(:namespace_owner) }
89 88 it { should respond_to(:owner) }
90 89 it { should respond_to(:path_with_namespace) }
91 90 end
... ...
spec/models/user_spec.rb
... ... @@ -130,11 +130,12 @@ describe User do
130 130 before do
131 131 ActiveRecord::Base.observers.enable(:user_observer)
132 132 @user = create :user
133   - @group = create :group, owner: @user
  133 + @group = create :group
  134 + @group.add_owner(@user)
134 135 end
135 136  
136 137 it { @user.several_namespaces?.should be_true }
137   - it { @user.namespaces.should include(@user.namespace, @group) }
  138 + it { @user.namespaces.should include(@user.namespace) }
138 139 it { @user.authorized_groups.should == [@group] }
139 140 it { @user.owned_groups.should == [@group] }
140 141 end
... ... @@ -144,9 +145,10 @@ describe User do
144 145 ActiveRecord::Base.observers.enable(:user_observer)
145 146 @user = create :user
146 147 @user2 = create :user
147   - @group = create :group, owner: @user
  148 + @group = create :group
  149 + @group.add_owner(@user)
148 150  
149   - @group.add_users([@user2.id], UsersGroup::OWNER)
  151 + @group.add_user(@user2, UsersGroup::OWNER)
150 152 end
151 153  
152 154 it { @user2.several_namespaces?.should be_true }
... ...
spec/requests/api/groups_spec.rb
... ... @@ -6,8 +6,13 @@ describe API::API do
6 6 let(:user1) { create(:user) }
7 7 let(:user2) { create(:user) }
8 8 let(:admin) { create(:admin) }
9   - let!(:group1) { create(:group, owner: user1) }
10   - let!(:group2) { create(:group, owner: user2) }
  9 + let!(:group1) { create(:group) }
  10 + let!(:group2) { create(:group) }
  11 +
  12 + before do
  13 + group1.add_owner(user1)
  14 + group2.add_owner(user2)
  15 + end
11 16  
12 17 describe "GET /groups" do
13 18 context "when unauthenticated" do
... ... @@ -130,14 +135,19 @@ describe API::API do
130 135 let(:master) { create(:user) }
131 136 let(:guest) { create(:user) }
132 137 let!(:group_with_members) do
133   - group = create(:group, owner: owner)
  138 + group = create(:group)
134 139 group.add_users([reporter.id], UsersGroup::REPORTER)
135 140 group.add_users([developer.id], UsersGroup::DEVELOPER)
136 141 group.add_users([master.id], UsersGroup::MASTER)
137 142 group.add_users([guest.id], UsersGroup::GUEST)
138 143 group
139 144 end
140   - let!(:group_no_members) { create(:group, owner: owner) }
  145 + let!(:group_no_members) { create(:group) }
  146 +
  147 + before do
  148 + group_with_members.add_owner owner
  149 + group_no_members.add_owner owner
  150 + end
141 151  
142 152 describe "GET /groups/:id/members" do
143 153 context "when authenticated as user that is part or the group" do
... ...