diff options
author | Robert Speicher <robert@gitlab.com> | 2018-07-23 19:30:29 +0000 |
---|---|---|
committer | Robert Speicher <robert@gitlab.com> | 2018-07-23 19:30:29 +0000 |
commit | 34c57e09b9f7e774a9c142d6b6daf451fdbf4c87 (patch) | |
tree | 83a414aee1e721370ce6579bd6c0c530f372cb87 | |
parent | 9c121352aa59098be357d82538a4878540a676c9 (diff) | |
parent | e195e939a3a9836fc4da86b5f7762c12808d64a7 (diff) | |
download | gitlab-ce-34c57e09b9f7e774a9c142d6b6daf451fdbf4c87.tar.gz |
Merge branch 'api-minimal-access-level' into 'master'
Add filter for minimal access level in groups and projects API
Closes #48165
See merge request gitlab-org/gitlab-ce!20478
-rw-r--r-- | app/finders/groups_finder.rb | 16 | ||||
-rw-r--r-- | app/finders/personal_projects_finder.rb | 19 | ||||
-rw-r--r-- | app/finders/projects_finder.rb | 17 | ||||
-rw-r--r-- | app/models/project.rb | 1 | ||||
-rw-r--r-- | changelogs/unreleased/api-minimal-access-level.yml | 5 | ||||
-rw-r--r-- | doc/api/groups.md | 10 | ||||
-rw-r--r-- | doc/api/projects.md | 9 | ||||
-rw-r--r-- | lib/api/groups.rb | 3 | ||||
-rw-r--r-- | lib/api/helpers.rb | 1 | ||||
-rw-r--r-- | lib/api/projects.rb | 1 | ||||
-rw-r--r-- | lib/api/users.rb | 1 | ||||
-rw-r--r-- | spec/requests/api/groups_spec.rb | 19 | ||||
-rw-r--r-- | spec/requests/api/projects_spec.rb | 30 |
13 files changed, 119 insertions, 13 deletions
diff --git a/app/finders/groups_finder.rb b/app/finders/groups_finder.rb index 0754123a3cf..0eeba1d2428 100644 --- a/app/finders/groups_finder.rb +++ b/app/finders/groups_finder.rb @@ -8,6 +8,7 @@ # owned: boolean # parent: Group # all_available: boolean (defaults to true) +# min_access_level: integer # # Users with full private access can see all groups. The `owned` and `parent` # params can be used to restrict the groups that are returned. @@ -39,6 +40,7 @@ class GroupsFinder < UnionFinder def all_groups return [owned_groups] if params[:owned] + return [groups_with_min_access_level] if min_access_level? return [Group.all] if current_user&.full_private_access? && all_available? groups = [] @@ -56,6 +58,16 @@ class GroupsFinder < UnionFinder current_user.groups end + def groups_with_min_access_level + groups = current_user + .groups + .where('members.access_level >= ?', params[:min_access_level]) + + Gitlab::GroupHierarchy + .new(groups) + .base_and_descendants + end + def by_parent(groups) return groups unless params[:parent] @@ -73,4 +85,8 @@ class GroupsFinder < UnionFinder def all_available? params.fetch(:all_available, true) end + + def min_access_level? + current_user && params[:min_access_level].present? + end end diff --git a/app/finders/personal_projects_finder.rb b/app/finders/personal_projects_finder.rb index 5aea0cb8192..18adfea747f 100644 --- a/app/finders/personal_projects_finder.rb +++ b/app/finders/personal_projects_finder.rb @@ -1,6 +1,7 @@ class PersonalProjectsFinder < UnionFinder - def initialize(user) + def initialize(user, params = {}) @user = user + @params = params end # Finds the projects belonging to the user in "@user", limited to either @@ -8,6 +9,8 @@ class PersonalProjectsFinder < UnionFinder # # current_user - When given the list of projects is limited to those only # visible by this user. + # params - Optional query parameters + # min_access_level: integer # # Returns an ActiveRecord::Relation. def execute(current_user = nil) @@ -19,11 +22,21 @@ class PersonalProjectsFinder < UnionFinder private def all_projects(current_user) - projects = [] + return [projects_with_min_access_level(current_user)] if current_user && min_access_level? + projects = [] projects << @user.personal_projects.visible_to_user(current_user) if current_user projects << @user.personal_projects.public_to_user(current_user) - projects end + + def projects_with_min_access_level(current_user) + @user + .personal_projects + .visible_to_user_and_access_level(current_user, @params[:min_access_level]) + end + + def min_access_level? + @params[:min_access_level].present? + end end diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb index b06595081e7..cac6643eff3 100644 --- a/app/finders/projects_finder.rb +++ b/app/finders/projects_finder.rb @@ -17,6 +17,7 @@ # search: string # non_archived: boolean # archived: 'only' or boolean +# min_access_level: integer # class ProjectsFinder < UnionFinder include CustomAttributesFilter @@ -34,7 +35,7 @@ class ProjectsFinder < UnionFinder user = params.delete(:user) collection = if user - PersonalProjectsFinder.new(user).execute(current_user) + PersonalProjectsFinder.new(user, finder_params).execute(current_user) else init_collection end @@ -65,6 +66,8 @@ class ProjectsFinder < UnionFinder def collection_with_user if owned_projects? current_user.owned_projects + elsif min_access_level? + current_user.authorized_projects.where('project_authorizations.access_level >= ?', params[:min_access_level]) else if private_only? current_user.authorized_projects @@ -76,7 +79,7 @@ class ProjectsFinder < UnionFinder # Builds a collection for an anonymous user. def collection_without_user - if private_only? || owned_projects? + if private_only? || owned_projects? || min_access_level? Project.none else Project.public_to_user @@ -91,6 +94,10 @@ class ProjectsFinder < UnionFinder params[:non_public].present? end + def min_access_level? + params[:min_access_level].present? + end + def by_ids(items) project_ids_relation ? items.where(id: project_ids_relation) : items end @@ -143,4 +150,10 @@ class ProjectsFinder < UnionFinder projects end end + + def finder_params + return {} unless min_access_level? + + { min_access_level: params[:min_access_level] } + end end diff --git a/app/models/project.rb b/app/models/project.rb index 7d37c3b3893..f880d728839 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -327,6 +327,7 @@ class Project < ActiveRecord::Base scope :joined, ->(user) { where('namespace_id != ?', user.namespace_id) } scope :starred_by, ->(user) { joins(:users_star_projects).where('users_star_projects.user_id': user.id) } scope :visible_to_user, ->(user) { where(id: user.authorized_projects.select(:id).reorder(nil)) } + scope :visible_to_user_and_access_level, ->(user, access_level) { where(id: user.authorized_projects.where('project_authorizations.access_level >= ?', access_level).select(:id).reorder(nil)) } scope :archived, -> { where(archived: true) } scope :non_archived, -> { where(archived: false) } scope :for_milestones, ->(ids) { joins(:milestones).where('milestones.id' => ids).distinct } diff --git a/changelogs/unreleased/api-minimal-access-level.yml b/changelogs/unreleased/api-minimal-access-level.yml new file mode 100644 index 00000000000..43cab246d69 --- /dev/null +++ b/changelogs/unreleased/api-minimal-access-level.yml @@ -0,0 +1,5 @@ +--- +title: Add filter for minimal access level in groups and projects API +merge_request: 20478 +author: Marko, Peter +type: added diff --git a/doc/api/groups.md b/doc/api/groups.md index 11de75039ee..87be36cc815 100644 --- a/doc/api/groups.md +++ b/doc/api/groups.md @@ -10,13 +10,14 @@ Parameters: | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `skip_groups` | array of integers | no | Skip the group IDs passed | -| `all_available` | boolean | no | Show all the groups you have access to (defaults to `false` for authenticated users, `true` for admin) | +| `all_available` | boolean | no | Show all the groups you have access to (defaults to `false` for authenticated users, `true` for admin); Attributes `owned` and `min_access_level` have precedence | | `search` | string | no | Return the list of authorized groups matching the search criteria | | `order_by` | string | no | Order groups by `name`, `path` or `id`. Default is `name` | | `sort` | string | no | Order groups in `asc` or `desc` order. Default is `asc` | | `statistics` | boolean | no | Include group statistics (admins only) | | `with_custom_attributes` | boolean | no | Include [custom attributes](custom_attributes.md) in response (admins only) | -| `owned` | boolean | no | Limit to groups owned by the current user | +| `owned` | boolean | no | Limit to groups explicitly owned by the current user | +| `min_access_level` | integer | no | Limit to groups where current user has at least this [access level](members.md) | ``` GET /groups @@ -94,13 +95,14 @@ Parameters: | --------- | ---- | -------- | ----------- | | `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) of the parent group | | `skip_groups` | array of integers | no | Skip the group IDs passed | -| `all_available` | boolean | no | Show all the groups you have access to (defaults to `false` for authenticated users, `true` for admin) | +| `all_available` | boolean | no | Show all the groups you have access to (defaults to `false` for authenticated users, `true` for admin); Attributes `owned` and `min_access_level` have precedence | | `search` | string | no | Return the list of authorized groups matching the search criteria | | `order_by` | string | no | Order groups by `name`, `path` or `id`. Default is `name` | | `sort` | string | no | Order groups in `asc` or `desc` order. Default is `asc` | | `statistics` | boolean | no | Include group statistics (admins only) | | `with_custom_attributes` | boolean | no | Include [custom attributes](custom_attributes.md) in response (admins only) | -| `owned` | boolean | no | Limit to groups owned by the current user | +| `owned` | boolean | no | Limit to groups explicitly owned by the current user | +| `min_access_level` | integer | no | Limit to groups where current user has at least this [access level](members.md) | ``` GET /groups/:id/subgroups diff --git a/doc/api/projects.md b/doc/api/projects.md index 9409afc88a8..f360b49c293 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -48,7 +48,7 @@ GET /projects | `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc` | | `search` | string | no | Return list of projects matching the search criteria | | `simple` | boolean | no | Return only limited fields for each project. This is a no-op without authentication as then _only_ simple fields are returned. | -| `owned` | boolean | no | Limit by projects owned by the current user | +| `owned` | boolean | no | Limit by projects explicitly owned by the current user | | `membership` | boolean | no | Limit by projects that the current user is a member of | | `starred` | boolean | no | Limit by projects starred by the current user | | `statistics` | boolean | no | Include project statistics | @@ -57,6 +57,7 @@ GET /projects | `with_merge_requests_enabled` | boolean | no | Limit by enabled merge requests feature | | `wiki_checksum_failed` | boolean | no | Limit projects where the wiki checksum calculation has failed _([Introduced][ee-6137] in [GitLab Premium][eep] 11.2)_ | | `repository_checksum_failed` | boolean | no | Limit projects where the repository checksum calculation has failed _([Introduced][ee-6137] in [GitLab Premium][eep] 11.2)_ | +| `min_access_level` | integer | no | Limit by current user minimal [access level](members.md) | When `simple=true` or the user is unauthenticated this returns something like: @@ -273,13 +274,14 @@ GET /users/:user_id/projects | `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc` | | `search` | string | no | Return list of projects matching the search criteria | | `simple` | boolean | no | Return only limited fields for each project. This is a no-op without authentication as then _only_ simple fields are returned. | -| `owned` | boolean | no | Limit by projects owned by the current user | +| `owned` | boolean | no | Limit by projects explicitly owned by the current user | | `membership` | boolean | no | Limit by projects that the current user is a member of | | `starred` | boolean | no | Limit by projects starred by the current user | | `statistics` | boolean | no | Include project statistics | | `with_custom_attributes` | boolean | no | Include [custom attributes](custom_attributes.md) in response (admins only) | | `with_issues_enabled` | boolean | no | Limit by enabled issues feature | | `with_merge_requests_enabled` | boolean | no | Limit by enabled merge requests feature | +| `min_access_level` | integer | no | Limit by current user minimal [access level](members.md) | ```json [ @@ -769,13 +771,14 @@ GET /projects/:id/forks | `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc` | | `search` | string | no | Return list of projects matching the search criteria | | `simple` | boolean | no | Return only limited fields for each project. This is a no-op without authentication as then _only_ simple fields are returned. | -| `owned` | boolean | no | Limit by projects owned by the current user | +| `owned` | boolean | no | Limit by projects explicitly owned by the current user | | `membership` | boolean | no | Limit by projects that the current user is a member of | | `starred` | boolean | no | Limit by projects starred by the current user | | `statistics` | boolean | no | Include project statistics | | `with_custom_attributes` | boolean | no | Include [custom attributes](custom_attributes.md) in response (admins only) | | `with_issues_enabled` | boolean | no | Limit by enabled issues feature | | `with_merge_requests_enabled` | boolean | no | Limit by enabled merge requests feature | +| `min_access_level` | integer | no | Limit by current user minimal [access level](members.md) | ```bash curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/forks" diff --git a/lib/api/groups.rb b/lib/api/groups.rb index 797b04df059..b4f441f6a4f 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -34,11 +34,12 @@ module API optional :owned, type: Boolean, default: false, desc: 'Limit by owned by authenticated user' optional :order_by, type: String, values: %w[name path id], default: 'name', desc: 'Order by name, path or id' optional :sort, type: String, values: %w[asc desc], default: 'asc', desc: 'Sort by asc (ascending) or desc (descending)' + optional :min_access_level, type: Integer, values: Gitlab::Access.all_values, desc: 'Minimum access level of authenticated user' use :pagination end def find_groups(params, parent_id = nil) - find_params = params.slice(:all_available, :custom_attributes, :owned) + find_params = params.slice(:all_available, :custom_attributes, :owned, :min_access_level) find_params[:parent] = find_group!(parent_id) if parent_id find_params[:all_available] = find_params.fetch(:all_available, current_user&.full_private_access?) diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index f7737468148..be17653dbb2 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -389,6 +389,7 @@ module API finder_params[:search] = params[:search] if params[:search] finder_params[:user] = params.delete(:user) if params[:user] finder_params[:custom_attributes] = params[:custom_attributes] if params[:custom_attributes] + finder_params[:min_access_level] = params[:min_access_level] if params[:min_access_level] finder_params end diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 889e3d4f819..eadde7b17bb 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -54,6 +54,7 @@ module API optional :membership, type: Boolean, default: false, desc: 'Limit by projects that the current user is a member of' optional :with_issues_enabled, type: Boolean, default: false, desc: 'Limit by enabled issues feature' optional :with_merge_requests_enabled, type: Boolean, default: false, desc: 'Limit by enabled merge requests feature' + optional :min_access_level, type: Integer, values: Gitlab::Access.all_values, desc: 'Limit by minimum access level of authenticated user' use :optional_filter_params_ee end diff --git a/lib/api/users.rb b/lib/api/users.rb index 5aaaf104dff..6da6c2b43de 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -42,6 +42,7 @@ module API optional :can_create_group, type: Boolean, desc: 'Flag indicating the user can create groups' optional :external, type: Boolean, desc: 'Flag indicating the user is an external user' optional :avatar, type: File, desc: 'Avatar image for user' + optional :min_access_level, type: Integer, values: Gitlab::Access.all_values, desc: 'Limit by minimum access level of authenticated user' all_or_none_of :extern_uid, :provider end diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index 65b387a2170..3a8948f8477 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -226,6 +226,25 @@ describe API::Groups do expect(json_response.first['name']).to eq(group2.name) end end + + context 'when using min_access_level in the request' do + let!(:group3) { create(:group, :private) } + let(:response_groups) { json_response.map { |group| group['id'] } } + + before do + group1.add_developer(user2) + group3.add_master(user2) + end + + it 'returns an array of groups the user has at least master access' do + get api('/groups', user2), min_access_level: 40 + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(response_groups).to eq([group2.id, group3.id]) + end + end end describe "GET /groups/:id" do diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 5ac008c7e40..71e3436fa76 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -400,6 +400,22 @@ describe API::Projects do end end end + + context 'and with min_access_level' do + before do + project2.add_master(user2) + project3.add_developer(user2) + project4.add_reporter(user2) + end + + it 'returns an array of groups the user has at least developer access' do + get api('/projects', user2), { min_access_level: 30 } + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.map { |project| project['id'] }).to contain_exactly(project2.id, project3.id) + end + end end context 'when authenticated as a different user' do @@ -681,6 +697,20 @@ describe API::Projects do expect(json_response).to be_an Array expect(json_response.map { |project| project['id'] }).to contain_exactly(public_project.id) end + + it 'returns projects filetered by minimal access level' do + private_project1 = create(:project, :private, name: 'private_project1', creator_id: user4.id, namespace: user4.namespace) + private_project2 = create(:project, :private, name: 'private_project2', creator_id: user4.id, namespace: user4.namespace) + private_project1.add_developer(user2) + private_project2.add_reporter(user2) + + get api("/users/#{user4.id}/projects/", user2), { min_access_level: 30 } + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.map { |project| project['id'] }).to contain_exactly(private_project1.id) + end end describe 'POST /projects/user/:id' do |