Commit 43645436 authored by GitLab Release Tools Bot's avatar GitLab Release Tools Bot

Merge remote-tracking branch 'dev/14-10-stable-ee' into 14-10-stable-ee

parents d118e6c4 94a44086
...@@ -2,6 +2,18 @@ ...@@ -2,6 +2,18 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. entry.
## 14.10.4 (2022-06-01)
### Security (7 changes)
- [Fix IP restrictions not applying to deploy tokens](gitlab-org/security/gitlab@8866d00e50f1d2857d54130239851f21404d7432) ([merge request](gitlab-org/security/gitlab!2471))
- [Trigger token should respect group IP restrictions](gitlab-org/security/gitlab@8534ca1be10f115dad2e0c1a4e167673049e401a) ([merge request](gitlab-org/security/gitlab!2478))
- [Fix content injection in Jira issue title](gitlab-org/security/gitlab@b8f82ec8d7ddf30c656642bff12de8fc8b5930a2) ([merge request](gitlab-org/security/gitlab!2464))
- [Subgroup member can list members of parent group](gitlab-org/security/gitlab@b59c49fa7b681a93bbe4bc69b20e72930a8b9d8d) ([merge request](gitlab-org/security/gitlab!2480))
- [Do not allow project member import when membership is locked](gitlab-org/security/gitlab@baed30570206b5ed9973ad8bfac5462721745a5d) ([merge request](gitlab-org/security/gitlab!2447))
- [Disable changing user attributes when updating SCIM provisioned user](gitlab-org/security/gitlab@ae4eb58668513f38c0daf1dc3b977c6b22a9a476) ([merge request](gitlab-org/security/gitlab!2454))
- [Allow only job owner to run interactive terminal](gitlab-org/security/gitlab@b0819e77b5a65d4412b42f27a513c02cc056a2b8) ([merge request](gitlab-org/security/gitlab!2433))
## 14.10.3 (2022-05-20) ## 14.10.3 (2022-05-20)
### Added (1 change) ### Added (1 change)
14.10.3 14.10.4
\ No newline at end of file \ No newline at end of file
14.10.3-ee 14.10.4-ee
\ No newline at end of file \ No newline at end of file
...@@ -67,6 +67,12 @@ class Groups::ApplicationController < ApplicationController ...@@ -67,6 +67,12 @@ class Groups::ApplicationController < ApplicationController
end end
end end
def authorize_read_group_member!
unless can?(current_user, :read_group_member, group)
render_403
end
end
def build_canonical_path(group) def build_canonical_path(group)
params[:group_id] = group.to_param params[:group_id] = group.to_param
......
...@@ -14,6 +14,7 @@ class Groups::GroupMembersController < Groups::ApplicationController ...@@ -14,6 +14,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
# Authorize # Authorize
before_action :authorize_admin_group_member!, except: admin_not_required_endpoints before_action :authorize_admin_group_member!, except: admin_not_required_endpoints
before_action :authorize_read_group_member!, only: :index
skip_before_action :check_two_factor_requirement, only: :leave skip_before_action :check_two_factor_requirement, only: :leave
skip_cross_project_access_check :index, :update, :destroy, :request_access, skip_cross_project_access_check :index, :update, :destroy, :request_access,
......
...@@ -84,7 +84,7 @@ module Ci ...@@ -84,7 +84,7 @@ module Ci
enable :update_commit_status enable :update_commit_status
end end
rule { can?(:update_build) & terminal }.enable :create_build_terminal rule { can?(:update_build) & terminal & owner_of_job }.enable :create_build_terminal
rule { can?(:update_build) }.enable :play_job rule { can?(:update_build) }.enable :play_job
......
...@@ -22,6 +22,7 @@ class GroupPolicy < Namespaces::GroupProjectNamespaceSharedPolicy ...@@ -22,6 +22,7 @@ class GroupPolicy < Namespaces::GroupProjectNamespaceSharedPolicy
condition(:share_with_group_locked, scope: :subject) { @subject.share_with_group_lock? } condition(:share_with_group_locked, scope: :subject) { @subject.share_with_group_lock? }
condition(:parent_share_with_group_locked, scope: :subject) { @subject.parent&.share_with_group_lock? } condition(:parent_share_with_group_locked, scope: :subject) { @subject.parent&.share_with_group_lock? }
condition(:can_change_parent_share_with_group_lock) { can?(:change_share_with_group_lock, @subject.parent) } condition(:can_change_parent_share_with_group_lock) { can?(:change_share_with_group_lock, @subject.parent) }
condition(:can_read_group_member) { can_read_group_member? }
desc "User is a project bot" desc "User is a project bot"
condition(:project_bot) { user.project_bot? && access_level >= GroupMember::GUEST } condition(:project_bot) { user.project_bot? && access_level >= GroupMember::GUEST }
...@@ -127,6 +128,10 @@ class GroupPolicy < Namespaces::GroupProjectNamespaceSharedPolicy ...@@ -127,6 +128,10 @@ class GroupPolicy < Namespaces::GroupProjectNamespaceSharedPolicy
rule { ~public_group & ~has_access }.prevent :read_counts rule { ~public_group & ~has_access }.prevent :read_counts
rule { ~can_read_group_member }.policy do
prevent :read_group_member
end
rule { ~can?(:read_group) }.policy do rule { ~can?(:read_group) }.policy do
prevent :read_design_activity prevent :read_design_activity
end end
...@@ -308,6 +313,10 @@ class GroupPolicy < Namespaces::GroupProjectNamespaceSharedPolicy ...@@ -308,6 +313,10 @@ class GroupPolicy < Namespaces::GroupProjectNamespaceSharedPolicy
true true
end end
def can_read_group_member?
!(@subject.private? && access_level == GroupMember::NO_ACCESS)
end
def resource_access_token_creation_allowed? def resource_access_token_creation_allowed?
resource_access_token_feature_available? && group.root_ancestor.namespace_settings.resource_access_token_creation_allowed? resource_access_token_feature_available? && group.root_ancestor.namespace_settings.resource_access_token_creation_allowed?
end end
......
...@@ -748,6 +748,10 @@ class ProjectPolicy < BasePolicy ...@@ -748,6 +748,10 @@ class ProjectPolicy < BasePolicy
prevent :register_project_runners prevent :register_project_runners
end end
rule { can?(:admin_project_member) }.policy do
enable :import_project_members_from_another_project
end
private private
def user_is_user? def user_is_user?
......
...@@ -26,6 +26,7 @@ module Ci ...@@ -26,6 +26,7 @@ module Ci
def create_pipeline_from_trigger(trigger) def create_pipeline_from_trigger(trigger)
# this check is to not leak the presence of the project if user cannot read it # this check is to not leak the presence of the project if user cannot read it
return unless trigger.project == project return unless trigger.project == project
return unless can?(trigger.owner, :read_project, project)
response = Ci::CreatePipelineService response = Ci::CreatePipelineService
.new(project, trigger.owner, ref: params[:ref], variables_attributes: variables) .new(project, trigger.owner, ref: params[:ref], variables_attributes: variables)
......
...@@ -29,7 +29,7 @@ module Members ...@@ -29,7 +29,7 @@ module Members
def import_project_team def import_project_team
return false unless target_project.present? && source_project.present? && current_user.present? return false unless target_project.present? && source_project.present? && current_user.present?
return false unless can?(current_user, :read_project_member, source_project) return false unless can?(current_user, :read_project_member, source_project)
return false unless can?(current_user, :admin_project_member, target_project) return false unless can?(current_user, :import_project_members_from_another_project, target_project)
target_project.team.import(source_project, current_user) target_project.team.import(source_project, current_user)
end end
......
...@@ -170,13 +170,13 @@ Returns a `201` status code if successful. ...@@ -170,13 +170,13 @@ Returns a `201` status code if successful.
Fields that can be updated are: Fields that can be updated are:
| SCIM/IdP field | GitLab field | | SCIM/IdP field | GitLab field |
|:---------------------------------|:---------------------------------------| |:---------------------------------|:-----------------------------------------------------------------------------|
| `id/externalId` | `extern_uid` | | `id/externalId` | `extern_uid` |
| `name.formatted` | `name` | | `name.formatted` | `name` ([Removed](https://gitlab.com/gitlab-org/gitlab/-/issues/363058)) |
| `emails\[type eq "work"\].value` | `email` | | `emails\[type eq "work"\].value` | `email` ([Removed](https://gitlab.com/gitlab-org/gitlab/-/issues/363058)) |
| `active` | Identity removal if `active` = `false` | | `active` | Identity removal if `active` = `false` |
| `userName` | `username` | | `userName` | `username` ([Removed](https://gitlab.com/gitlab-org/gitlab/-/issues/363058)) |
```plaintext ```plaintext
PATCH /api/scim/v2/groups/:group_path/Users/:id PATCH /api/scim/v2/groups/:group_path/Users/:id
......
...@@ -93,6 +93,9 @@ For more information, view the [permissions table](../../permissions.md#group-me ...@@ -93,6 +93,9 @@ For more information, view the [permissions table](../../permissions.md#group-me
## Subgroup membership ## Subgroup membership
NOTE:
There is a bug that causes some pages in the parent group to be accessible by subgroup members. For more details, see [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/340421).
When you add a member to a group, that member is also added to all subgroups. The user's permissions are inherited from When you add a member to a group, that member is also added to all subgroups. The user's permissions are inherited from
the group's parent. the group's parent.
......
...@@ -433,6 +433,13 @@ module EE ...@@ -433,6 +433,13 @@ module EE
super super
end end
override :can_read_group_member?
def can_read_group_member?
return true if user&.can_read_all_resources?
super
end
def ldap_lock_bypassable? def ldap_lock_bypassable?
return false unless ::Feature.enabled?(:ldap_settings_unlock_groups_by_owners) return false unless ::Feature.enabled?(:ldap_settings_unlock_groups_by_owners)
return false unless ::Gitlab::CurrentSettings.allow_group_owners_to_manage_ldap? return false unless ::Gitlab::CurrentSettings.allow_group_owners_to_manage_ldap?
......
...@@ -145,6 +145,15 @@ module EE ...@@ -145,6 +145,15 @@ module EE
::Gitlab::IncidentManagement.escalation_policies_available?(@subject) ::Gitlab::IncidentManagement.escalation_policies_available?(@subject)
end end
with_scope :subject
condition(:membership_locked_via_parent_group) do
@subject.group && (@subject.group.membership_lock? || ::Gitlab::CurrentSettings.lock_memberships_to_ldap?)
end
rule { membership_locked_via_parent_group }.policy do
prevent :import_project_members_from_another_project
end
rule { visual_review_bot }.policy do rule { visual_review_bot }.policy do
prevent :read_note prevent :read_note
enable :create_note enable :create_note
......
...@@ -13,6 +13,10 @@ module Integrations ...@@ -13,6 +13,10 @@ module Integrations
jira_issue.summary jira_issue.summary
end end
expose :title_html do |jira_issue|
html_escape jira_issue.summary
end
expose :created_at do |jira_issue| expose :created_at do |jira_issue|
jira_issue.created.to_datetime.utc jira_issue.created.to_datetime.utc
end end
......
...@@ -75,13 +75,10 @@ module API ...@@ -75,13 +75,10 @@ module API
elsif parsed_hash[:extern_uid] elsif parsed_hash[:extern_uid]
identity.update(parsed_hash.slice(:extern_uid)) identity.update(parsed_hash.slice(:extern_uid))
else else
scim_conflict!(message: 'Email has already been taken') if email_taken?(parsed_hash[:email], identity) # With 15.0, we no longer allow modifying user attributes.
# However, we mark the operation as successful to avoid breaking
result = ::Users::UpdateService.new(identity.user, # existing automations
parsed_hash.except(:extern_uid, :active) true
.merge(user: identity.user)).execute
result[:status] == :success
end end
end end
...@@ -91,12 +88,6 @@ module API ...@@ -91,12 +88,6 @@ module API
false false
end end
def email_taken?(email, identity)
return unless email
User.by_any_email(email.downcase).where.not(id: identity.user.id).exists?
end
def find_user_identity(group, extern_uid) def find_user_identity(group, extern_uid)
return unless group.saml_provider return unless group.saml_provider
......
...@@ -25,6 +25,23 @@ module EE ...@@ -25,6 +25,23 @@ module EE
super super
end end
override :check_project_accessibility!
def check_project_accessibility!
super
# Deploy keys and tokens are unique in that we don't check
# against the project policy, where IP restrictions normally are
# checked. The existence of a project's associated key or
# token is enough to authenticate read access. To ensure deploy keys
# and tokens honor the IP allow list, we need to force a check here. We
# don't want to do this for all Git access because GitLab admin users
# aren't subject to this IP restriction, but deploy keys and tokens don't
# necessarily have an associated user.
return unless deploy_key? || deploy_token?
raise ::Gitlab::GitAccess::NotFoundError, not_found_message if ip_restricted?
end
def group? def group?
# Strict nil check, to avoid any surprises with Object#present? # Strict nil check, to avoid any surprises with Object#present?
# which can delegate to #empty? # which can delegate to #empty?
...@@ -46,6 +63,10 @@ module EE ...@@ -46,6 +63,10 @@ module EE
private private
def ip_restricted?
!::Gitlab::IpRestriction::Enforcer.new(project.group).allows_current_ip? if project.group
end
override :check_custom_action override :check_custom_action
def check_custom_action def check_custom_action
geo_custom_action || super geo_custom_action || super
......
...@@ -79,6 +79,43 @@ RSpec.describe 'Jira issues list', :js do ...@@ -79,6 +79,43 @@ RSpec.describe 'Jira issues list', :js do
end end
end end
context 'when title or description contains HTML characters' do
let(:html) { '<script>foobar</script>' }
let(:escaped_html) { ERB::Util.html_escape(html) }
let(:issue) { build_issue(1).deep_merge(fields: { summary: html }) }
before do
stub_licensed_features(jira_issues_integration: true)
end
it 'escapes the HTML on issues#index' do
stub_issues([issue])
visit project_integrations_jira_issues_path(project)
expect(page).to have_text(html)
expect(page).not_to have_css('script', text: 'foobar')
expect(page.source).to include(escaped_html)
end
it 'escapes the HTML on issues#show' do
issue.deep_merge!(
fields: { comment: { comments: [] } },
renderedFields: { description: html },
duedate: Time.zone.now.to_s
)
stub_request(:get, /\A#{public_url}/)
.to_return(headers: { 'Content-Type' => 'application/json' }, body: issue.to_json)
visit project_integrations_jira_issue_path(project, 1)
expect(page).to have_text(html)
expect(page).not_to have_css('script', text: 'foobar')
expect(page.source).to include(escaped_html)
end
end
private private
def all_pages def all_pages
......
...@@ -30,6 +30,180 @@ RSpec.describe Gitlab::GitAccess do ...@@ -30,6 +30,180 @@ RSpec.describe Gitlab::GitAccess do
end end
end end
describe '#check_project_accessibility!' do
let_it_be_with_reload(:group) { create(:group, :public) }
let_it_be(:project) { create(:project, :repository, group: group) }
let_it_be(:deploy_key) { create(:deploy_key, user: user) }
let_it_be(:admin) { create(:admin) }
let(:deploy_token) { create(:deploy_token, projects: [project]) }
let(:start_sha) { '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9' }
let(:end_sha) { '570e7b2abdd848b95f2f578043fc23bd6f6fd24d' }
let(:changes) { "#{start_sha} #{end_sha} refs/heads/master" }
let(:push_error_message) { Gitlab::GitAccess::ERROR_MESSAGES[:upload] }
before(:all) do
project.add_developer(user)
deploy_key.deploy_keys_projects.create!(project: project, can_push: true)
end
context 'with ip restriction' do
before do
allow(Gitlab::IpAddressState).to receive(:current).and_return('192.168.0.2')
stub_licensed_features(group_ip_restriction: true)
end
context 'group with restriction' do
before do
create(:ip_restriction, group: group, range: range)
end
context 'address is within the range' do
let(:range) { '192.168.0.0/24' }
context 'when actor is a DeployKey with access to project' do
let(:actor) { deploy_key }
it 'allows pull, push access' do
aggregate_failures do
expect { pull_changes }.not_to raise_error
expect { push_changes }.not_to raise_error
end
end
end
context 'when actor is DeployToken with access to project' do
let(:actor) { deploy_token }
it 'allows pull access' do
aggregate_failures do
expect { pull_changes }.not_to raise_error
expect { push_changes }.to raise_forbidden(push_error_message)
end
end
end
context 'when actor is user with access to project' do
let(:actor) { user }
it 'allows push, pull access' do
aggregate_failures do
expect { pull_changes }.not_to raise_error
expect { push_changes }.not_to raise_error
end
end
end
context 'when actor is instance admin', :enable_admin_mode do
let(:actor) { admin }
it 'allows push, pull access' do
aggregate_failures do
expect { pull_changes }.not_to raise_error
expect { push_changes }.not_to raise_error
end
end
end
end
context 'address is outside the range' do
let(:range) { '10.0.0.0/8' }
context 'when actor is a DeployKey with access to project' do
let(:actor) { deploy_key }
it 'blocks pull, push with "not found"' do
aggregate_failures do
expect { pull_changes }.to raise_not_found
expect { push_changes }.to raise_not_found
end
end
end
context 'when actor is DeployToken with access to project' do
let(:actor) { deploy_token }
it 'blocks pull, push with "not found"' do
aggregate_failures do
expect { pull_changes }.to raise_not_found
expect { push_changes }.to raise_not_found
end
end
end
context 'when actor is user with access to project' do
let(:actor) { user }
it 'blocks pull, push with "not found"' do
aggregate_failures do
expect { pull_changes }.to raise_not_found
expect { push_changes }.to raise_not_found
end
end
end
context 'when actor is instance admin', :enable_admin_mode do
let(:actor) { admin }
it 'allows push, pull access' do
aggregate_failures do
expect { pull_changes }.not_to raise_error
expect { push_changes }.not_to raise_error
end
end
end
end
end
context 'group without restriction' do
context 'when actor is a DeployKey with access to project' do
let(:actor) { deploy_key }
it 'allows pull, push access' do
aggregate_failures do
expect { pull_changes }.not_to raise_error
expect { push_changes }.not_to raise_error
end
end
end
context 'when actor is DeployToken with access to project' do
let(:actor) { deploy_token }
it 'allows pull access' do
aggregate_failures do
expect { pull_changes }.not_to raise_error
expect { push_changes }.to raise_forbidden(push_error_message)
end
end
end
context 'when actor is user with access to project' do
let(:actor) { user }
it 'allows push, pull access' do
aggregate_failures do
expect { pull_changes }.not_to raise_error
expect { push_changes }.not_to raise_error
end
end
end
context 'when actor is instance admin', :enable_admin_mode do
let(:actor) { admin }
it 'allows push, pull access' do
aggregate_failures do
expect { pull_changes }.not_to raise_error
expect { push_changes }.not_to raise_error
end
end
end
end
end
end
context "when in a read-only GitLab instance" do context "when in a read-only GitLab instance" do
before do before do
create(:protected_branch, name: 'feature', project: project) create(:protected_branch, name: 'feature', project: project)
...@@ -1094,4 +1268,8 @@ RSpec.describe Gitlab::GitAccess do ...@@ -1094,4 +1268,8 @@ RSpec.describe Gitlab::GitAccess do
def raise_forbidden(message) def raise_forbidden(message)
raise_error(Gitlab::GitAccess::ForbiddenError, message) raise_error(Gitlab::GitAccess::ForbiddenError, message)
end end
def raise_not_found
raise_error(Gitlab::GitAccess::NotFoundError, Gitlab::GitAccess::ERROR_MESSAGES[:project_not_found])
end
end end
...@@ -2052,4 +2052,34 @@ RSpec.describe ProjectPolicy do ...@@ -2052,4 +2052,34 @@ RSpec.describe ProjectPolicy do
expect_disallowed(*owner_permissions) expect_disallowed(*owner_permissions)
end end
end end
context 'importing members from another project' do
let(:current_user) { owner }
context 'for a personal project' do
it { is_expected.to be_allowed(:import_project_members_from_another_project) }
end
context 'for a project in a group' do
let(:project) { create(:project, group: create(:group)) }
context 'when the project has locked their membership' do
context 'via the parent group' do
before do
project.group.update!(membership_lock: true)
end
it { is_expected.to be_disallowed(:import_project_members_from_another_project) }
end
context 'via LDAP' do
before do
stub_application_setting(lock_memberships_to_ldap: true)
end
it { is_expected.to be_disallowed(:import_project_members_from_another_project) }
end
end
end
end
end end
...@@ -1433,4 +1433,46 @@ RSpec.describe API::Projects do ...@@ -1433,4 +1433,46 @@ RSpec.describe API::Projects do
end end
end end
end end
describe 'POST /projects/:id/import_project_members/:project_id' do
let_it_be(:project) { create(:project) }
let_it_be(:target_project) { create(:project, group: create(:group)) }
before_all do
project.add_maintainer(another_user)
target_project.add_maintainer(another_user)
end
context 'when the target project has locked their membership' do
context 'via the parent group' do
before do
target_project.group.update!(membership_lock: true)
end
it 'returns 403' do
expect do
post api("/projects/#{target_project.id}/import_project_members/#{project.id}", another_user)
end.not_to change { target_project.members.count }
expect(response).to have_gitlab_http_status(:unprocessable_entity)
expect(json_response['message']).to eq('Import failed')
end
end
context 'via LDAP' do
before do
stub_application_setting(lock_memberships_to_ldap: true)
end
it 'returns 403' do
expect do
post api("/projects/#{target_project.id}/import_project_members/#{project.id}", another_user)
end.not_to change { target_project.members.count }
expect(response).to have_gitlab_http_status(:unprocessable_entity)
expect(json_response['message']).to eq('Import failed')
end
end
end
end
end end
...@@ -371,7 +371,6 @@ RSpec.describe API::Scim do ...@@ -371,7 +371,6 @@ RSpec.describe API::Scim do
it 'does not call reprovision service when identity is already active' do it 'does not call reprovision service when identity is already active' do
expect(::EE::Gitlab::Scim::ReprovisionService).not_to receive(:new) expect(::EE::Gitlab::Scim::ReprovisionService).not_to receive(:new)
expect(::Users::UpdateService).to receive(:new).and_call_original
call_patch_api(params) call_patch_api(params)
end end
...@@ -394,36 +393,36 @@ RSpec.describe API::Scim do ...@@ -394,36 +393,36 @@ RSpec.describe API::Scim do
end end
end end
context 'name' do context 'user attributes' do
before do context 'name' do
params = { Operations: [{ 'op': 'Replace', 'path': 'name.formatted', 'value': 'new_name' }] }.to_query before do
params = { Operations: [{ 'op': 'Replace', 'path': 'name.formatted', 'value': 'new_name' }] }.to_query
call_patch_api(params) call_patch_api(params)
end end
it 'responds with 204' do it 'responds with 204' do
expect(response).to have_gitlab_http_status(:no_content) expect(response).to have_gitlab_http_status(:no_content)
end end
it 'updates the name' do it 'does not update the name' do
expect(user.reload.name).to eq('new_name') expect(user.reload.name).not_to eq('new_name')
end end
it 'responds with an empty response' do it 'responds with an empty response' do
expect(response.body).to eq('') expect(response.body).to eq('')
end
end end
end
context 'email' do context 'email' do
context 'non existent email' do
before do before do
params = { Operations: [{ 'op': 'Replace', 'path': 'emails[type eq "work"].value', 'value': 'new@mail.com' }] }.to_query params = { Operations: [{ 'op': 'Replace', 'path': 'emails[type eq "work"].value', 'value': 'new@mail.com' }] }.to_query
call_patch_api(params) call_patch_api(params)
end end
it 'updates the email' do it 'does not update the email' do
expect(user.reload.unconfirmed_email).to eq('new@mail.com') expect(user.reload.unconfirmed_email).not_to eq('new@mail.com')
end end
it 'responds with 204' do it 'responds with 204' do
...@@ -431,21 +430,23 @@ RSpec.describe API::Scim do ...@@ -431,21 +430,23 @@ RSpec.describe API::Scim do
end end
end end
context 'existent email' do context 'userName' do
before do before do
create(:user, email: 'new@mail.com') params = { Operations: [{ 'op': 'Replace', 'path': 'userName', 'value': 'new_username' }] }.to_query
params = { Operations: [{ 'op': 'Replace', 'path': 'emails[type eq "work"].value', 'value': 'new@mail.com' }] }.to_query
call_patch_api(params) call_patch_api(params)
end end
it 'does not update a duplicated email' do it 'responds with 204' do
expect(user.reload.unconfirmed_email).not_to eq('new@mail.com') expect(response).to have_gitlab_http_status(:no_content)
end
it 'does not update the username' do
expect(user.reload.username).not_to eq('new_username')
end end
it 'responds with 209' do it 'responds with an empty response' do
expect(response).to have_gitlab_http_status(:conflict) expect(response.body).to eq('')
end end
end end
end end
......
...@@ -26,7 +26,7 @@ RSpec.describe Integrations::JiraSerializers::IssueEntity do ...@@ -26,7 +26,7 @@ RSpec.describe Integrations::JiraSerializers::IssueEntity do
let(:jira_issue) do let(:jira_issue) do
double( double(
summary: 'Title', summary: 'Title with <h1>HTML</h1>',
created: '2020-06-25T15:39:30.000+0000', created: '2020-06-25T15:39:30.000+0000',
updated: '2020-06-26T15:38:32.000+0000', updated: '2020-06-26T15:38:32.000+0000',
resolutiondate: '2020-06-27T13:23:51.000+0000', resolutiondate: '2020-06-27T13:23:51.000+0000',
...@@ -46,7 +46,8 @@ RSpec.describe Integrations::JiraSerializers::IssueEntity do ...@@ -46,7 +46,8 @@ RSpec.describe Integrations::JiraSerializers::IssueEntity do
it 'returns the Jira issues attributes' do it 'returns the Jira issues attributes' do
expect(subject).to include( expect(subject).to include(
project_id: project.id, project_id: project.id,
title: 'Title', title: 'Title with <h1>HTML</h1>',
title_html: 'Title with &lt;h1&gt;HTML&lt;/h1&gt;',
created_at: '2020-06-25T15:39:30.000+0000'.to_datetime.utc, created_at: '2020-06-25T15:39:30.000+0000'.to_datetime.utc,
updated_at: '2020-06-26T15:38:32.000+0000'.to_datetime.utc, updated_at: '2020-06-26T15:38:32.000+0000'.to_datetime.utc,
closed_at: '2020-06-27T13:23:51.000+0000'.to_datetime.utc, closed_at: '2020-06-27T13:23:51.000+0000'.to_datetime.utc,
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Ci::PipelineTriggerService do
let_it_be(:project) { create(:project, :repository) }
before do
stub_ci_pipeline_to_return_yaml_file
end
describe '#execute' do
let_it_be(:user) { create(:user) }
let(:result) { described_class.new(project, user, params).execute }
before do
project.add_developer(user)
end
shared_examples 'with ip restriction' do
let_it_be_with_reload(:group) { create(:group, :public) }
let_it_be_with_reload(:project) { create(:project, :repository, group: group) }
before do
allow(Gitlab::IpAddressState).to receive(:current).and_return('192.168.0.2')
stub_licensed_features(group_ip_restriction: true)
end
context 'group with restriction' do
before do
create(:ip_restriction, group: group, range: range)
end
context 'address is within the range' do
let(:range) { '192.168.0.0/24' }
it 'triggers a pipeline' do
expect { result }.to change { Ci::Pipeline.count }.by(1)
end
end
context 'address is outside the range' do
let(:range) { '10.0.0.0/8' }
it 'does nothing' do
expect { result }.not_to change { Ci::Pipeline.count }
end
end
end
context 'group without restriction' do
it 'triggers a pipeline' do
expect { result }.to change { Ci::Pipeline.count }.by(1)
end
end
end
context 'with a trigger token' do
let(:params) { { token: trigger.token, ref: 'master', variables: nil } }
let(:trigger) { create(:ci_trigger, project: project, owner: user) }
include_examples 'with ip restriction'
end
context 'with a job token' do
let!(:pipeline) { create(:ci_empty_pipeline, project: project) }
let(:job) { create(:ci_build, :running, pipeline: pipeline, user: user) }
let(:params) { { token: job.token, ref: 'master', variables: nil } }
include_examples 'with ip restriction'
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Members::ImportProjectTeamService do
describe '#execute' do
let_it_be(:source_project) { create(:project) }
let_it_be(:target_project) { create(:project, group: create(:group)) }
let_it_be(:user) { create(:user) }
let(:source_project_id) { source_project.id }
let(:target_project_id) { target_project.id }
subject { described_class.new(user, { id: target_project_id, project_id: source_project_id }) }
before_all do
source_project.add_guest(user)
target_project.add_maintainer(user)
end
context 'when the project team import fails' do
context 'when the target project has locked their membership' do
context 'via the parent group' do
before do
target_project.group.update!(membership_lock: true)
end
it 'returns false' do
expect(subject.execute).to be(false)
end
end
context 'via LDAP' do
before do
stub_application_setting(lock_memberships_to_ldap: true)
end
it 'returns false' do
expect(subject.execute).to be(false)
end
end
end
end
end
end
...@@ -15,6 +15,10 @@ module API ...@@ -15,6 +15,10 @@ module API
public_send("find_#{source_type}!", id) # rubocop:disable GitlabSecurity/PublicSend public_send("find_#{source_type}!", id) # rubocop:disable GitlabSecurity/PublicSend
end end
def authorize_read_source_member!(source_type, source)
authorize! :"read_#{source_type}_member", source
end
def authorize_admin_source!(source_type, source) def authorize_admin_source!(source_type, source)
authorize! :"admin_#{source_type}", source authorize! :"admin_#{source_type}", source
end end
......
...@@ -30,6 +30,8 @@ module API ...@@ -30,6 +30,8 @@ module API
get ":id/members" do get ":id/members" do
source = find_source(source_type, params[:id]) source = find_source(source_type, params[:id])
authorize_read_source_member!(source_type, source)
members = paginate(retrieve_members(source, params: params)) members = paginate(retrieve_members(source, params: params))
present_members members present_members members
...@@ -49,6 +51,8 @@ module API ...@@ -49,6 +51,8 @@ module API
get ":id/members/all" do get ":id/members/all" do
source = find_source(source_type, params[:id]) source = find_source(source_type, params[:id])
authorize_read_source_member!(source_type, source)
members = paginate(retrieve_members(source, params: params, deep: true)) members = paginate(retrieve_members(source, params: params, deep: true))
present_members members present_members members
...@@ -64,6 +68,8 @@ module API ...@@ -64,6 +68,8 @@ module API
get ":id/members/:user_id" do get ":id/members/:user_id" do
source = find_source(source_type, params[:id]) source = find_source(source_type, params[:id])
authorize_read_source_member!(source_type, source)
members = source_members(source) members = source_members(source)
member = members.find_by!(user_id: params[:user_id]) member = members.find_by!(user_id: params[:user_id])
...@@ -81,6 +87,8 @@ module API ...@@ -81,6 +87,8 @@ module API
get ":id/members/all/:user_id" do get ":id/members/all/:user_id" do
source = find_source(source_type, params[:id]) source = find_source(source_type, params[:id])
authorize_read_source_member!(source_type, source)
members = find_all_members(source) members = find_all_members(source)
member = members.find_by!(user_id: params[:user_id]) member = members.find_by!(user_id: params[:user_id])
......
...@@ -183,7 +183,7 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state do ...@@ -183,7 +183,7 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state do
end end
context 'with web terminal' do context 'with web terminal' do
let(:job) { create(:ci_build, :running, :with_runner_session, pipeline: pipeline) } let(:job) { create(:ci_build, :running, :with_runner_session, pipeline: pipeline, user: user) }
it 'exposes the terminal path' do it 'exposes the terminal path' do
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
...@@ -1284,7 +1284,7 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state do ...@@ -1284,7 +1284,7 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state do
context 'when job exists' do context 'when job exists' do
context 'and it has a terminal' do context 'and it has a terminal' do
let!(:job) { create(:ci_build, :running, :with_runner_session, pipeline: pipeline) } let!(:job) { create(:ci_build, :running, :with_runner_session, pipeline: pipeline, user: user) }
it 'has a job' do it 'has a job' do
get_terminal(id: job.id) get_terminal(id: job.id)
...@@ -1295,7 +1295,7 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state do ...@@ -1295,7 +1295,7 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state do
end end
context 'and does not have a terminal' do context 'and does not have a terminal' do
let!(:job) { create(:ci_build, :running, pipeline: pipeline) } let!(:job) { create(:ci_build, :running, pipeline: pipeline, user: user) }
it 'returns not_found' do it 'returns not_found' do
get_terminal(id: job.id) get_terminal(id: job.id)
...@@ -1324,7 +1324,7 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state do ...@@ -1324,7 +1324,7 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state do
end end
describe 'GET #terminal_websocket_authorize' do describe 'GET #terminal_websocket_authorize' do
let!(:job) { create(:ci_build, :running, :with_runner_session, pipeline: pipeline) } let!(:job) { create(:ci_build, :running, :with_runner_session, pipeline: pipeline, user: user) }
before do before do
project.add_developer(user) project.add_developer(user)
......
...@@ -97,7 +97,7 @@ RSpec.describe 'Private Group access' do ...@@ -97,7 +97,7 @@ RSpec.describe 'Private Group access' do
it { is_expected.to be_allowed_for(:developer).of(group) } it { is_expected.to be_allowed_for(:developer).of(group) }
it { is_expected.to be_allowed_for(:reporter).of(group) } it { is_expected.to be_allowed_for(:reporter).of(group) }
it { is_expected.to be_allowed_for(:guest).of(group) } it { is_expected.to be_allowed_for(:guest).of(group) }
it { is_expected.to be_allowed_for(project_guest) } it { is_expected.to be_denied_for(project_guest) }
it { is_expected.to be_denied_for(:user) } it { is_expected.to be_denied_for(:user) }
it { is_expected.to be_denied_for(:external) } it { is_expected.to be_denied_for(:external) }
it { is_expected.to be_denied_for(:visitor) } it { is_expected.to be_denied_for(:visitor) }
......
...@@ -405,4 +405,52 @@ RSpec.describe Ci::BuildPolicy do ...@@ -405,4 +405,52 @@ RSpec.describe Ci::BuildPolicy do
end end
end end
end end
describe 'ability :create_build_terminal' do
let(:project) { create(:project, :private) }
subject { described_class.new(user, build) }
context 'when user can update_build' do
before do
project.add_maintainer(user)
end
context 'when job has terminal' do
before do
allow(build).to receive(:has_terminal?).and_return(true)
end
context 'when current user is the job owner' do
before do
build.update!(user: user)
end
it { expect_allowed(:create_build_terminal) }
end
context 'when current user is not the job owner' do
it { expect_disallowed(:create_build_terminal) }
end
end
context 'when job does not have terminal' do
before do
allow(build).to receive(:has_terminal?).and_return(false)
build.update!(user: user)
end
it { expect_disallowed(:create_build_terminal) }
end
end
context 'when user cannot update build' do
before do
project.add_guest(user)
allow(build).to receive(:has_terminal?).and_return(true)
end
it { expect_disallowed(:create_build_terminal) }
end
end
end end
...@@ -396,6 +396,36 @@ RSpec.describe ProjectPolicy do ...@@ -396,6 +396,36 @@ RSpec.describe ProjectPolicy do
end end
end end
context 'importing members from another project' do
%w(maintainer owner).each do |role|
context "with #{role}" do
let(:current_user) { send(role) }
it { is_expected.to be_allowed(:import_project_members_from_another_project) }
end
end
%w(guest reporter developer anonymous).each do |role|
context "with #{role}" do
let(:current_user) { send(role) }
it { is_expected.to be_disallowed(:import_project_members_from_another_project) }
end
end
context 'with an admin' do
let(:current_user) { admin }
context 'when admin mode is enabled', :enable_admin_mode do
it { expect_allowed(:import_project_members_from_another_project) }
end
context 'when admin mode is disabled' do
it { expect_disallowed(:import_project_members_from_another_project) }
end
end
end
context 'reading usage quotas' do context 'reading usage quotas' do
%w(maintainer owner).each do |role| %w(maintainer owner).each do |role|
context "with #{role}" do context "with #{role}" do
......
...@@ -184,6 +184,21 @@ RSpec.describe API::Members do ...@@ -184,6 +184,21 @@ RSpec.describe API::Members do
expect(json_response).to be_an Array expect(json_response).to be_an Array
expect(json_response.map { |u| u['id'] }).to match_array [maintainer.id, developer.id, nested_user.id] expect(json_response.map { |u| u['id'] }).to match_array [maintainer.id, developer.id, nested_user.id]
end end
context 'with a subgroup' do
let(:group) { create(:group, :private)}
let(:subgroup) { create(:group, :private, parent: group)}
let(:project) { create(:project, group: subgroup) }
before do
subgroup.add_developer(developer)
end
it 'subgroup member cannot get parent group members list' do
get api("/groups/#{group.id}/members/all", developer)
expect(response).to have_gitlab_http_status(:forbidden)
end
end
end end
shared_examples 'GET /:source_type/:id/members/(all/):user_id' do |source_type, all| shared_examples 'GET /:source_type/:id/members/(all/):user_id' do |source_type, all|
......
...@@ -56,6 +56,15 @@ RSpec.describe Ci::PipelineTriggerService do ...@@ -56,6 +56,15 @@ RSpec.describe Ci::PipelineTriggerService do
end end
end end
context 'when trigger owner does not have a permission to read a project' do
let(:params) { { token: trigger.token, ref: 'master', variables: nil } }
let(:trigger) { create(:ci_trigger, project: project, owner: create(:user)) }
it 'does nothing' do
expect { result }.not_to change { Ci::Pipeline.count }
end
end
context 'when params have an existing trigger token' do context 'when params have an existing trigger token' do
context 'when params have an existing ref' do context 'when params have an existing ref' do
let(:params) { { token: trigger.token, ref: 'master', variables: nil } } let(:params) { { token: trigger.token, ref: 'master', variables: nil } }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment