Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-12-13 01:59:38 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-12-13 01:59:38 +0300
commit734bfe3a2e8b86c3e049f6f13d380b3d30e4e359 (patch)
treea0599c2a6efd4466ba7f48471def5791d2682e53 /app
parent27e1dab1ed98c46c91b85e8c5dd1cefd62c0cb96 (diff)
Add latest changes from gitlab-org/security/gitlab@16-6-stable-ee
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/repository/components/table/row.vue4
-rw-r--r--app/graphql/types/issue_type.rb2
-rw-r--r--app/graphql/types/merge_request_type.rb2
-rw-r--r--app/models/concerns/time_trackable.rb8
-rw-r--r--app/models/timelog.rb12
-rw-r--r--app/services/ci/pipeline_schedules/variables_base_save_service.rb52
-rw-r--r--app/services/ci/pipeline_schedules/variables_create_service.rb24
-rw-r--r--app/services/ci/pipeline_schedules/variables_update_service.rb24
-rw-r--r--app/services/personal_access_tokens/rotate_service.rb40
-rw-r--r--app/services/project_access_tokens/rotate_service.rb58
-rw-r--r--app/views/projects/merge_requests/_nav_btns.html.haml2
11 files changed, 210 insertions, 18 deletions
diff --git a/app/assets/javascripts/repository/components/table/row.vue b/app/assets/javascripts/repository/components/table/row.vue
index 6a81f11eb51..bb2b8ae54b6 100644
--- a/app/assets/javascripts/repository/components/table/row.vue
+++ b/app/assets/javascripts/repository/components/table/row.vue
@@ -120,13 +120,13 @@ export default {
routerLinkTo() {
if (this.isBlob) {
return buildURLwithRefType({
- path: joinPaths('/-/blob', this.escapedRef, this.path),
+ path: joinPaths('/-/blob', this.escapedRef, encodeURI(this.path)),
refType: this.refType,
});
}
if (this.isFolder) {
return buildURLwithRefType({
- path: joinPaths('/-/tree', this.escapedRef, this.path),
+ path: joinPaths('/-/tree', this.escapedRef, encodeURI(this.path)),
refType: this.refType,
});
}
diff --git a/app/graphql/types/issue_type.rb b/app/graphql/types/issue_type.rb
index 1c8a654a841..7c7d559e05d 100644
--- a/app/graphql/types/issue_type.rb
+++ b/app/graphql/types/issue_type.rb
@@ -111,7 +111,7 @@ module Types
field :time_estimate, GraphQL::Types::Int, null: false,
description: 'Time estimate of the issue.'
field :total_time_spent, GraphQL::Types::Int, null: false,
- description: 'Total time reported as spent on the issue.'
+ description: 'Total time (in seconds) reported as spent on the issue.'
field :closed_at, Types::TimeType, null: true,
description: 'Timestamp of when the issue was closed.'
diff --git a/app/graphql/types/merge_request_type.rb b/app/graphql/types/merge_request_type.rb
index 9dca82f1750..d7c3b313f84 100644
--- a/app/graphql/types/merge_request_type.rb
+++ b/app/graphql/types/merge_request_type.rb
@@ -199,7 +199,7 @@ module Types
field :time_estimate, GraphQL::Types::Int, null: false,
description: 'Time estimate of the merge request.'
field :total_time_spent, GraphQL::Types::Int, null: false,
- description: 'Total time reported as spent on the merge request.'
+ description: 'Total time (in seconds) reported as spent on the merge request.'
field :approved, GraphQL::Types::Boolean,
method: :approved?,
diff --git a/app/models/concerns/time_trackable.rb b/app/models/concerns/time_trackable.rb
index 0f361e70a91..70bc45b382a 100644
--- a/app/models/concerns/time_trackable.rb
+++ b/app/models/concerns/time_trackable.rb
@@ -45,7 +45,13 @@ module TimeTrackable
# rubocop:enable Gitlab/ModuleWithInstanceVariables
def total_time_spent
- timelogs.sum(:time_spent)
+ sum = timelogs.sum(:time_spent)
+
+ # A new restriction has been introduced to limit total time spent to -
+ # Timelog::MAX_TOTAL_TIME_SPENT or 3.154e+7 seconds (approximately a year, a generous limit)
+ # Since there could be existing records that breach the limit, check and return the maximum/minimum allowed value.
+ # (some issuable might have total time spent that's negative because a validation was missing.)
+ sum.clamp(-Timelog::MAX_TOTAL_TIME_SPENT, Timelog::MAX_TOTAL_TIME_SPENT)
end
def human_total_time_spent
diff --git a/app/models/timelog.rb b/app/models/timelog.rb
index b6b4decc64b..eb088b1f582 100644
--- a/app/models/timelog.rb
+++ b/app/models/timelog.rb
@@ -1,6 +1,10 @@
# frozen_string_literal: true
class Timelog < ApplicationRecord
+ # Gitlab::TimeTrackingFormatter.parse("1y") == 31557600 seconds
+ # 31557600 slightly deviates from (365 days * 24 hours/day * 60 minutes/hour * 60 seconds/minute)
+ MAX_TOTAL_TIME_SPENT = 31557600.seconds.to_i # a year
+
include Importable
include IgnorableColumns
include Sortable
@@ -12,6 +16,7 @@ class Timelog < ApplicationRecord
validates :time_spent, :user, presence: true
validates :summary, length: { maximum: 255 }
validate :issuable_id_is_present, unless: :importing?
+ validate :check_total_time_spent_is_within_range, on: :create, unless: :importing?, if: :time_spent
belongs_to :issue, touch: true
belongs_to :merge_request, touch: true
@@ -58,6 +63,13 @@ class Timelog < ApplicationRecord
private
+ def check_total_time_spent_is_within_range
+ total_time_spent = issuable.timelogs.sum(:time_spent) + time_spent
+
+ errors.add(:base, _("Total time spent cannot be negative.")) if total_time_spent < 0
+ errors.add(:base, _("Total time spent cannot exceed a year.")) if total_time_spent > MAX_TOTAL_TIME_SPENT
+ end
+
def issuable_id_is_present
if issue_id && merge_request_id
errors.add(:base, _('Only Issue ID or merge request ID is required'))
diff --git a/app/services/ci/pipeline_schedules/variables_base_save_service.rb b/app/services/ci/pipeline_schedules/variables_base_save_service.rb
new file mode 100644
index 00000000000..fb3ba52b36e
--- /dev/null
+++ b/app/services/ci/pipeline_schedules/variables_base_save_service.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+module Ci
+ module PipelineSchedules
+ class VariablesBaseSaveService
+ include Gitlab::Utils::StrongMemoize
+
+ def execute
+ variable.assign_attributes(params)
+
+ return forbidden_to_update_pipeline_schedule unless allowed_to_update_pipeline_schedule?
+ return forbidden_to_save_variables unless allowed_to_save_variables?
+
+ if variable.save
+ ServiceResponse.success(payload: variable)
+ else
+ ServiceResponse.error(payload: variable, message: variable.errors.full_messages)
+ end
+ end
+
+ private
+
+ attr_reader :project, :user, :params, :variable, :pipeline_schedule
+
+ def allowed_to_update_pipeline_schedule?
+ user.can?(:update_pipeline_schedule, pipeline_schedule)
+ end
+
+ def forbidden_to_update_pipeline_schedule
+ # We add the error to the base object too
+ # because model errors are used in the API responses and the `form_errors` helper.
+ pipeline_schedule.errors.add(:base, authorize_message)
+
+ ServiceResponse.error(payload: pipeline_schedule, message: [authorize_message], reason: :forbidden)
+ end
+
+ def allowed_to_save_variables?
+ user.can?(:set_pipeline_variables, project)
+ end
+
+ def forbidden_to_save_variables
+ message = _('The current user is not authorized to set pipeline schedule variables')
+
+ # We add the error to the base object too
+ # because model errors are used in the API responses and the `form_errors` helper.
+ pipeline_schedule.errors.add(:base, message)
+
+ ServiceResponse.error(payload: pipeline_schedule, message: [message], reason: :forbidden)
+ end
+ end
+ end
+end
diff --git a/app/services/ci/pipeline_schedules/variables_create_service.rb b/app/services/ci/pipeline_schedules/variables_create_service.rb
new file mode 100644
index 00000000000..7d81b0771dc
--- /dev/null
+++ b/app/services/ci/pipeline_schedules/variables_create_service.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Ci
+ module PipelineSchedules
+ class VariablesCreateService < VariablesBaseSaveService
+ AUTHORIZE = :update_pipeline_schedule
+
+ def initialize(pipeline_schedule, user, params)
+ @variable = pipeline_schedule.variables.new
+ @user = user
+ @pipeline_schedule = pipeline_schedule
+ @project = pipeline_schedule.project
+ @params = params
+ end
+
+ private
+
+ def authorize_message
+ _('The current user is not authorized to create the pipeline schedule variables')
+ end
+ strong_memoize_attr :authorize_message
+ end
+ end
+end
diff --git a/app/services/ci/pipeline_schedules/variables_update_service.rb b/app/services/ci/pipeline_schedules/variables_update_service.rb
new file mode 100644
index 00000000000..f0e3a03c37a
--- /dev/null
+++ b/app/services/ci/pipeline_schedules/variables_update_service.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Ci
+ module PipelineSchedules
+ class VariablesUpdateService < VariablesBaseSaveService
+ AUTHORIZE = :update_pipeline_schedule
+
+ def initialize(pipeline_schedule_variable, user, params)
+ @variable = pipeline_schedule_variable
+ @user = user
+ @pipeline_schedule = pipeline_schedule_variable.pipeline_schedule
+ @project = pipeline_schedule.project
+ @params = params
+ end
+
+ private
+
+ def authorize_message
+ _('The current user is not authorized to update the pipeline schedule variables')
+ end
+ strong_memoize_attr :authorize_message
+ end
+ end
+end
diff --git a/app/services/personal_access_tokens/rotate_service.rb b/app/services/personal_access_tokens/rotate_service.rb
index 32710629caf..13144a04c11 100644
--- a/app/services/personal_access_tokens/rotate_service.rb
+++ b/app/services/personal_access_tokens/rotate_service.rb
@@ -10,26 +10,18 @@ module PersonalAccessTokens
end
def execute(params = {})
- return ServiceResponse.error(message: _('token already revoked')) if token.revoked?
+ return error_response(_('token already revoked')) if token.revoked?
response = ServiceResponse.success
PersonalAccessToken.transaction do
unless token.revoke!
- response = ServiceResponse.error(message: _('failed to revoke token'))
+ response = error_response(_('failed to revoke token'))
raise ActiveRecord::Rollback
end
- target_user = token.user
- new_token = target_user.personal_access_tokens.create(create_token_params(token, params))
-
- if new_token.persisted?
- response = ServiceResponse.success(payload: { personal_access_token: new_token })
- else
- response = ServiceResponse.error(message: new_token.errors.full_messages.to_sentence)
-
- raise ActiveRecord::Rollback
- end
+ response = create_access_token(params)
+ raise ActiveRecord::Rollback unless response.success?
end
response
@@ -47,5 +39,29 @@ module PersonalAccessTokens
scopes: token.scopes,
expires_at: expires_at }
end
+
+ def create_access_token(params)
+ target_user = token.user
+
+ new_token = target_user.personal_access_tokens.create(create_token_params(token, params))
+
+ return success_response(new_token) if new_token.persisted?
+
+ error_response(new_token.errors.full_messages.to_sentence)
+ end
+
+ def expires_at(params)
+ return params[:expires_at] if params[:expires_at]
+
+ params[:expires_at] || EXPIRATION_PERIOD.from_now.to_date
+ end
+
+ def success_response(new_token)
+ ServiceResponse.success(payload: { personal_access_token: new_token })
+ end
+
+ def error_response(message)
+ ServiceResponse.error(message: message)
+ end
end
end
diff --git a/app/services/project_access_tokens/rotate_service.rb b/app/services/project_access_tokens/rotate_service.rb
new file mode 100644
index 00000000000..63d8d2a82cc
--- /dev/null
+++ b/app/services/project_access_tokens/rotate_service.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+module ProjectAccessTokens
+ class RotateService < ::PersonalAccessTokens::RotateService
+ extend ::Gitlab::Utils::Override
+
+ def initialize(current_user, token, resource = nil)
+ @current_user = current_user
+ @token = token
+ @project = resource
+ end
+
+ def execute(params = {})
+ super
+ end
+
+ attr_reader :project
+
+ private
+
+ override :create_access_token
+ def create_access_token(params)
+ target_user = token.user
+
+ unless valid_access_level?
+ return error_response(
+ _("Not eligible to rotate token with access level higher than the user")
+ )
+ end
+
+ new_token = target_user.personal_access_tokens.create(create_token_params(token, params))
+
+ if new_token.persisted?
+ update_bot_membership(target_user, new_token.expires_at)
+
+ return success_response(new_token)
+ end
+
+ error_response(new_token.errors.full_messages.to_sentence)
+ end
+
+ def update_bot_membership(target_user, expires_at)
+ target_user.members.update(expires_at: expires_at)
+ end
+
+ def valid_access_level?
+ return true if current_user.can_admin_all_resources?
+ return false unless current_user.can?(:manage_resource_access_tokens, project)
+
+ token_access_level = project.team.max_member_access(token.user.id).to_i
+ current_user_access_level = project.team.max_member_access(current_user.id).to_i
+
+ return true if token_access_level.to_i <= current_user_access_level
+
+ false
+ end
+ end
+end
diff --git a/app/views/projects/merge_requests/_nav_btns.html.haml b/app/views/projects/merge_requests/_nav_btns.html.haml
index 2c0a8d831e4..4607aebf121 100644
--- a/app/views/projects/merge_requests/_nav_btns.html.haml
+++ b/app/views/projects/merge_requests/_nav_btns.html.haml
@@ -4,7 +4,7 @@
- if @can_bulk_update
= render Pajamas::ButtonComponent.new(type: :submit, button_options: { class: 'gl-mr-3 js-bulk-update-toggle' }) do
= _("Bulk edit")
-- if merge_project
+- if merge_project && can?(@current_user, :create_merge_request_in, @project)
= render Pajamas::ButtonComponent.new(href: new_merge_request_path, variant: :confirm) do
= _("New merge request")