diff options
author | Zeger-Jan van de Weg <git@zjvandeweg.nl> | 2017-09-04 09:28:46 +0200 |
---|---|---|
committer | Zeger-Jan van de Weg <git@zjvandeweg.nl> | 2017-09-04 09:28:46 +0200 |
commit | a315e6025c702985b2f6390b29508de39383f52d (patch) | |
tree | f0d07d955092e4a218346c41f2942131dfcef91a /lib | |
parent | 78dad4cf321eb84aa5decdea34704145adca0c3e (diff) | |
parent | fd54a4678f23c9e18ce46b3803e5e57ffa1199a3 (diff) | |
download | gitlab-ce-a315e6025c702985b2f6390b29508de39383f52d.tar.gz |
Merge branch 'master' into zj-auto-devops-table
Diffstat (limited to 'lib')
49 files changed, 578 insertions, 96 deletions
diff --git a/lib/api/access_requests.rb b/lib/api/access_requests.rb index 4fa9b2b2494..374b611f55e 100644 --- a/lib/api/access_requests.rb +++ b/lib/api/access_requests.rb @@ -10,7 +10,7 @@ module API params do requires :id, type: String, desc: "The #{source_type} ID" end - resource source_type.pluralize, requirements: { id: %r{[^/]+} } do + resource source_type.pluralize, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do desc "Gets a list of access requests for a #{source_type}." do detail 'This feature was introduced in GitLab 8.11.' success Entities::AccessRequester diff --git a/lib/api/award_emoji.rb b/lib/api/award_emoji.rb index 8e3851640da..c3d93996816 100644 --- a/lib/api/award_emoji.rb +++ b/lib/api/award_emoji.rb @@ -12,7 +12,7 @@ module API params do requires :id, type: String, desc: 'The ID of a project' end - resource :projects, requirements: { id: %r{[^/]+} } do + resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do AWARDABLES.each do |awardable_params| awardable_string = awardable_params[:type].pluralize awardable_id_string = "#{awardable_params[:type]}_#{awardable_params[:find_by]}" diff --git a/lib/api/boards.rb b/lib/api/boards.rb index 0d11c5fc971..366b0dc9a6f 100644 --- a/lib/api/boards.rb +++ b/lib/api/boards.rb @@ -7,7 +7,7 @@ module API params do requires :id, type: String, desc: 'The ID of a project' end - resource :projects, requirements: { id: %r{[^/]+} } do + resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do desc 'Get all project boards' do detail 'This feature was introduced in 8.13' success Entities::Board diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb index 485b680cd5f..78e889a4c35 100644 --- a/lib/api/commit_statuses.rb +++ b/lib/api/commit_statuses.rb @@ -5,7 +5,7 @@ module API params do requires :id, type: String, desc: 'The ID of a project' end - resource :projects, requirements: { id: %r{[^/]+} } do + resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do include PaginationParams before { authenticate! } diff --git a/lib/api/deploy_keys.rb b/lib/api/deploy_keys.rb index f405c341398..281269b1190 100644 --- a/lib/api/deploy_keys.rb +++ b/lib/api/deploy_keys.rb @@ -17,7 +17,7 @@ module API params do requires :id, type: String, desc: 'The ID of the project' end - resource :projects, requirements: { id: %r{[^/]+} } do + resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do before { authorize_admin_project } desc "Get a specific project's deploy keys" do diff --git a/lib/api/deployments.rb b/lib/api/deployments.rb index 46b936897f6..1efee9a1324 100644 --- a/lib/api/deployments.rb +++ b/lib/api/deployments.rb @@ -8,7 +8,7 @@ module API params do requires :id, type: String, desc: 'The project ID' end - resource :projects, requirements: { id: %r{[^/]+} } do + resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do desc 'Get all deployments of the project' do detail 'This feature was introduced in GitLab 8.11.' success Entities::Deployment diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 803b48dd88a..9df9a515990 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -1,11 +1,11 @@ module API module Entities class UserSafe < Grape::Entity - expose :name, :username + expose :id, :name, :username end class UserBasic < UserSafe - expose :id, :state + expose :state expose :avatar_url do |user, options| user.avatar_url(only_path: false) end diff --git a/lib/api/environments.rb b/lib/api/environments.rb index e33269f9483..5c63ec028d9 100644 --- a/lib/api/environments.rb +++ b/lib/api/environments.rb @@ -9,7 +9,7 @@ module API params do requires :id, type: String, desc: 'The project ID' end - resource :projects, requirements: { id: %r{[^/]+} } do + resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do desc 'Get all environments of the project' do detail 'This feature was introduced in GitLab 8.11.' success Entities::Environment diff --git a/lib/api/events.rb b/lib/api/events.rb index dabdf579119..b0713ff1d54 100644 --- a/lib/api/events.rb +++ b/lib/api/events.rb @@ -67,7 +67,7 @@ module API params do requires :id, type: String, desc: 'The ID of a project' end - resource :projects, requirements: { id: %r{[^/]+} } do + resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do desc "List a Project's visible events" do success Entities::Event end diff --git a/lib/api/group_milestones.rb b/lib/api/group_milestones.rb index b85eb59dc0a..93fa0b95857 100644 --- a/lib/api/group_milestones.rb +++ b/lib/api/group_milestones.rb @@ -10,7 +10,7 @@ module API params do requires :id, type: String, desc: 'The ID of a group' end - resource :groups, requirements: { id: %r{[^/]+} } do + resource :groups, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do desc 'Get a list of group milestones' do success Entities::Milestone end diff --git a/lib/api/group_variables.rb b/lib/api/group_variables.rb index 25152f30998..92800ce6450 100644 --- a/lib/api/group_variables.rb +++ b/lib/api/group_variables.rb @@ -9,7 +9,7 @@ module API requires :id, type: String, desc: 'The ID of a group' end - resource :groups, requirements: { id: %r{[^/]+} } do + resource :groups, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do desc 'Get group-level variables' do success Entities::Variable end diff --git a/lib/api/groups.rb b/lib/api/groups.rb index 8c494a54329..31a918eda60 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -89,7 +89,7 @@ module API params do requires :id, type: String, desc: 'The ID of a group' end - resource :groups, requirements: { id: %r{[^/]+} } do + resource :groups, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do desc 'Update a group. Available only for users who can administrate groups.' do success Entities::Group end diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb index ecb79317093..f57ff0f2632 100644 --- a/lib/api/helpers/internal_helpers.rb +++ b/lib/api/helpers/internal_helpers.rb @@ -42,6 +42,10 @@ module API ::Users::ActivityService.new(actor, 'Git SSH').execute if commands.include?(params[:action]) end + def merge_request_urls + ::MergeRequests::GetUrlsService.new(project).execute(params[:changes]) + end + private def set_project diff --git a/lib/api/internal.rb b/lib/api/internal.rb index 8b007869dc3..622bd9650e4 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -68,7 +68,7 @@ module API end get "/merge_request_urls" do - ::MergeRequests::GetUrlsService.new(project).execute(params[:changes]) + merge_request_urls end # @@ -155,6 +155,21 @@ module API # render_api_error!(e, 500) # end end + + post '/post_receive' do + status 200 + + PostReceive.perform_async(params[:gl_repository], params[:identifier], + params[:changes]) + broadcast_message = BroadcastMessage.current&.last&.message + reference_counter_decreased = Gitlab::ReferenceCounter.new(params[:gl_repository]).decrease + + { + merge_request_urls: merge_request_urls, + broadcast_message: broadcast_message, + reference_counter_decreased: reference_counter_decreased + } + end end end end diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 0297023226f..e4c2c390853 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -81,7 +81,7 @@ module API params do requires :id, type: String, desc: 'The ID of a group' end - resource :groups, requirements: { id: %r{[^/]+} } do + resource :groups, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do desc 'Get a list of group issues' do success Entities::IssueBasic end @@ -108,7 +108,7 @@ module API params do requires :id, type: String, desc: 'The ID of a project' end - resource :projects, requirements: { id: %r{[^/]+} } do + resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do include TimeTrackingEndpoints desc 'Get a list of project issues' do diff --git a/lib/api/jobs.rb b/lib/api/jobs.rb index a40018b214e..5bab96398fd 100644 --- a/lib/api/jobs.rb +++ b/lib/api/jobs.rb @@ -7,7 +7,7 @@ module API params do requires :id, type: String, desc: 'The ID of a project' end - resource :projects, requirements: { id: %r{[^/]+} } do + resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do helpers do params :optional_scope do optional :scope, types: [String, Array[String]], desc: 'The scope of builds to show', diff --git a/lib/api/labels.rb b/lib/api/labels.rb index c0cf618ee8d..e41a1720ac1 100644 --- a/lib/api/labels.rb +++ b/lib/api/labels.rb @@ -7,7 +7,7 @@ module API params do requires :id, type: String, desc: 'The ID of a project' end - resource :projects, requirements: { id: %r{[^/]+} } do + resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do desc 'Get all labels of the project' do success Entities::Label end diff --git a/lib/api/members.rb b/lib/api/members.rb index a5d3d7f25a0..22e4bdead41 100644 --- a/lib/api/members.rb +++ b/lib/api/members.rb @@ -10,7 +10,7 @@ module API params do requires :id, type: String, desc: "The #{source_type} ID" end - resource source_type.pluralize, requirements: { id: %r{[^/]+} } do + resource source_type.pluralize, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do desc 'Gets a list of group or project members viewable by the authenticated user.' do success Entities::Member end diff --git a/lib/api/merge_request_diffs.rb b/lib/api/merge_request_diffs.rb index 4b79eac2b8b..c3affcc6c6b 100644 --- a/lib/api/merge_request_diffs.rb +++ b/lib/api/merge_request_diffs.rb @@ -8,7 +8,7 @@ module API params do requires :id, type: String, desc: 'The ID of a project' end - resource :projects, requirements: { id: %r{[^/]+} } do + resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do desc 'Get a list of merge request diff versions' do detail 'This feature was introduced in GitLab 8.12.' success Entities::MergeRequestDiff diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index eec8d9357aa..7bcbf9f20ff 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -72,7 +72,7 @@ module API params do requires :id, type: String, desc: 'The ID of a project' end - resource :projects, requirements: { id: %r{[^/]+} } do + resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do include TimeTrackingEndpoints helpers do diff --git a/lib/api/notes.rb b/lib/api/notes.rb index e116448c15b..d6e7203adaf 100644 --- a/lib/api/notes.rb +++ b/lib/api/notes.rb @@ -9,7 +9,7 @@ module API params do requires :id, type: String, desc: 'The ID of a project' end - resource :projects, requirements: { id: %r{[^/]+} } do + resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do NOTEABLE_TYPES.each do |noteable_type| noteables_str = noteable_type.to_s.underscore.pluralize diff --git a/lib/api/notification_settings.rb b/lib/api/notification_settings.rb index 5d113c94b22..bcc0833aa5c 100644 --- a/lib/api/notification_settings.rb +++ b/lib/api/notification_settings.rb @@ -54,7 +54,7 @@ module API params do requires :id, type: String, desc: "The #{source_type} ID" end - resource source_type.pluralize, requirements: { id: %r{[^/]+} } do + resource source_type.pluralize, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do desc "Get #{source_type} level notification level settings, defaults to Global" do detail 'This feature was introduced in GitLab 8.12' success Entities::NotificationSetting diff --git a/lib/api/pipeline_schedules.rb b/lib/api/pipeline_schedules.rb index e3123ef4e2d..ef01cbc7875 100644 --- a/lib/api/pipeline_schedules.rb +++ b/lib/api/pipeline_schedules.rb @@ -7,7 +7,7 @@ module API params do requires :id, type: String, desc: 'The ID of a project' end - resource :projects, requirements: { id: %r{[^/]+} } do + resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do desc 'Get all pipeline schedules' do success Entities::PipelineSchedule end diff --git a/lib/api/pipelines.rb b/lib/api/pipelines.rb index e505cae3992..74b3376a1f3 100644 --- a/lib/api/pipelines.rb +++ b/lib/api/pipelines.rb @@ -7,7 +7,7 @@ module API params do requires :id, type: String, desc: 'The project ID' end - resource :projects, requirements: { id: %r{[^/]+} } do + resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do desc 'Get all Pipelines of the project' do detail 'This feature was introduced in GitLab 8.11.' success Entities::PipelineBasic diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb index 5b457bbe639..86066e2b58f 100644 --- a/lib/api/project_hooks.rb +++ b/lib/api/project_hooks.rb @@ -24,7 +24,7 @@ module API params do requires :id, type: String, desc: 'The ID of a project' end - resource :projects, requirements: { id: %r{[^/]+} } do + resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do desc 'Get project hooks' do success Entities::ProjectHook end diff --git a/lib/api/project_milestones.rb b/lib/api/project_milestones.rb index 451998c726a..0cb209a02d0 100644 --- a/lib/api/project_milestones.rb +++ b/lib/api/project_milestones.rb @@ -10,7 +10,7 @@ module API params do requires :id, type: String, desc: 'The ID of a project' end - resource :projects, requirements: { id: %r{[^/]+} } do + resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do desc 'Get a list of project milestones' do success Entities::Milestone end diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb index 704e8c6718d..2ccda1c1aa1 100644 --- a/lib/api/project_snippets.rb +++ b/lib/api/project_snippets.rb @@ -7,7 +7,7 @@ module API params do requires :id, type: String, desc: 'The ID of a project' end - resource :projects, requirements: { id: %r{[^/]+} } do + resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do helpers do def handle_project_member_errors(errors) if errors[:project_access].any? diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 78d900984ac..4845242a173 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -95,7 +95,7 @@ module API end end - resource :users, requirements: { user_id: %r{[^/]+} } do + resource :users, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do desc 'Get a user projects' do success Entities::BasicProjectDetails end @@ -183,7 +183,7 @@ module API params do requires :id, type: String, desc: 'The ID of a project' end - resource :projects, requirements: { id: %r{[^/]+} } do + resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do desc 'Get a single project' do success Entities::ProjectWithAccess end diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb index 14d2bff9cb5..2255fb1b70d 100644 --- a/lib/api/repositories.rb +++ b/lib/api/repositories.rb @@ -9,7 +9,7 @@ module API params do requires :id, type: String, desc: 'The ID of a project' end - resource :projects, requirements: { id: %r{[^/]+} } do + resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do helpers do def handle_project_member_errors(errors) if errors[:project_access].any? diff --git a/lib/api/runners.rb b/lib/api/runners.rb index 68c2120cc15..1ea9a7918d7 100644 --- a/lib/api/runners.rb +++ b/lib/api/runners.rb @@ -87,7 +87,7 @@ module API params do requires :id, type: String, desc: 'The ID of a project' end - resource :projects, requirements: { id: %r{[^/]+} } do + resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do before { authorize_admin_project } desc 'Get runners available for project' do diff --git a/lib/api/services.rb b/lib/api/services.rb index ff9ddd44439..2cbd0517dc3 100644 --- a/lib/api/services.rb +++ b/lib/api/services.rb @@ -601,7 +601,7 @@ module API params do requires :id, type: String, desc: 'The ID of a project' end - resource :projects, requirements: { id: %r{[^/]+} } do + resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do before { authenticate! } before { authorize_admin_project } @@ -691,7 +691,7 @@ module API params do requires :id, type: String, desc: 'The ID of a project' end - resource :projects, requirements: { id: %r{[^/]+} } do + resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do desc "Trigger a slash command for #{service_slug}" do detail 'Added in GitLab 8.13' end diff --git a/lib/api/settings.rb b/lib/api/settings.rb index 667ba468ce6..851b226e9e5 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -122,6 +122,13 @@ module API optional :terminal_max_session_time, type: Integer, desc: 'Maximum time for web terminal websocket connection (in seconds). Set to 0 for unlimited time.' optional :polling_interval_multiplier, type: BigDecimal, desc: 'Interval multiplier used by endpoints that perform polling. Set to 0 to disable polling.' + ApplicationSetting::SUPPORTED_KEY_TYPES.each do |type| + optional :"#{type}_key_restriction", + type: Integer, + values: KeyRestrictionValidator.supported_key_restrictions(type), + desc: "Restrictions on the complexity of uploaded #{type.upcase} keys. A value of #{ApplicationSetting::FORBIDDEN_KEY_VALUE} disables all #{type.upcase} keys." + end + optional(*::ApplicationSettingsHelper.visible_attributes) at_least_one_of(*::ApplicationSettingsHelper.visible_attributes) end diff --git a/lib/api/subscriptions.rb b/lib/api/subscriptions.rb index 91567909998..b3e1e23031a 100644 --- a/lib/api/subscriptions.rb +++ b/lib/api/subscriptions.rb @@ -12,7 +12,7 @@ module API requires :id, type: String, desc: 'The ID of a project' requires :subscribable_id, type: String, desc: 'The ID of a resource' end - resource :projects, requirements: { id: %r{[^/]+} } do + resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do subscribable_types.each do |type, finder| type_singularized = type.singularize entity_class = Entities.const_get(type_singularized.camelcase) diff --git a/lib/api/todos.rb b/lib/api/todos.rb index 55191169dd4..ffccfebe752 100644 --- a/lib/api/todos.rb +++ b/lib/api/todos.rb @@ -12,7 +12,7 @@ module API params do requires :id, type: String, desc: 'The ID of a project' end - resource :projects, requirements: { id: %r{[^/]+} } do + resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do ISSUABLE_TYPES.each do |type, finder| type_id_str = "#{type.singularize}_iid".to_sym diff --git a/lib/api/triggers.rb b/lib/api/triggers.rb index c9fee7e5193..dd6801664b1 100644 --- a/lib/api/triggers.rb +++ b/lib/api/triggers.rb @@ -5,7 +5,7 @@ module API params do requires :id, type: String, desc: 'The ID of a project' end - resource :projects, requirements: { id: %r{[^/]+} } do + resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do desc 'Trigger a GitLab project pipeline' do success Entities::Pipeline end diff --git a/lib/api/variables.rb b/lib/api/variables.rb index da71787abab..d08876ae1b9 100644 --- a/lib/api/variables.rb +++ b/lib/api/variables.rb @@ -9,7 +9,7 @@ module API requires :id, type: String, desc: 'The ID of a project' end - resource :projects, requirements: { id: %r{[^/]+} } do + resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do desc 'Get project variables' do success Entities::Variable end diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index 1790f380c33..3fd81759d25 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -50,10 +50,6 @@ module Gitlab # Avoid resource intensive login checks if password is not provided return unless password.present? - # Nothing to do here if internal auth is disabled and LDAP is - # not configured - return unless current_application_settings.password_authentication_enabled? || Gitlab::LDAP::Config.enabled? - Gitlab::Auth::UniqueIpsLimiter.limit_user! do user = User.by_login(login) diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index 554e40dc8a6..8709f82bcc4 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -250,11 +250,17 @@ module Gitlab branch_names + tag_names end + def delete_all_refs_except(prefixes) + delete_refs(*all_ref_names_except(prefixes)) + end + # Returns an Array of all ref names, except when it's matching pattern # # regexp - The pattern for ref names we don't want - def all_ref_names_except(regexp) - rugged.references.reject { |ref| ref.name =~ regexp }.map(&:name) + def all_ref_names_except(prefixes) + rugged.references.reject do |ref| + prefixes.any? { |p| ref.name.start_with?(p) } + end.map(&:name) end # Discovers the default branch based on the repository's available branches diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index 3e8b83c0f90..62d1ecae676 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -35,6 +35,7 @@ module Gitlab def check(cmd, changes) check_protocol! + check_valid_actor! check_active_user! check_project_accessibility! check_project_moved! @@ -70,6 +71,14 @@ module Gitlab private + def check_valid_actor! + return unless actor.is_a?(Key) + + unless actor.valid? + raise UnauthorizedError, "Your SSH key #{actor.errors[:key].first}." + end + end + def check_protocol! unless protocol_allowed? raise UnauthorizedError, "Git access over #{protocol.upcase} is not allowed" diff --git a/lib/gitlab/i18n/metadata_entry.rb b/lib/gitlab/i18n/metadata_entry.rb new file mode 100644 index 00000000000..35d57459a3d --- /dev/null +++ b/lib/gitlab/i18n/metadata_entry.rb @@ -0,0 +1,27 @@ +module Gitlab + module I18n + class MetadataEntry + attr_reader :entry_data + + def initialize(entry_data) + @entry_data = entry_data + end + + def expected_plurals + return nil unless plural_information + + plural_information['nplurals'].to_i + end + + private + + def plural_information + return @plural_information if defined?(@plural_information) + + if plural_line = entry_data[:msgstr].detect { |metadata_line| metadata_line.starts_with?('Plural-Forms: ') } + @plural_information = Hash[plural_line.scan(/(\w+)=([^;\n]+)/)] + end + end + end + end +end diff --git a/lib/gitlab/i18n/po_linter.rb b/lib/gitlab/i18n/po_linter.rb new file mode 100644 index 00000000000..2e02787a4f4 --- /dev/null +++ b/lib/gitlab/i18n/po_linter.rb @@ -0,0 +1,216 @@ +require 'simple_po_parser' + +module Gitlab + module I18n + class PoLinter + attr_reader :po_path, :translation_entries, :metadata_entry, :locale + + VARIABLE_REGEX = /%{\w*}|%[a-z]/.freeze + + def initialize(po_path, locale = I18n.locale.to_s) + @po_path = po_path + @locale = locale + end + + def errors + @errors ||= validate_po + end + + def validate_po + if parse_error = parse_po + return 'PO-syntax errors' => [parse_error] + end + + validate_entries + end + + def parse_po + entries = SimplePoParser.parse(po_path) + + # The first entry is the metadata entry if there is one. + # This is an entry when empty `msgid` + if entries.first[:msgid].empty? + @metadata_entry = Gitlab::I18n::MetadataEntry.new(entries.shift) + else + return 'Missing metadata entry.' + end + + @translation_entries = entries.map do |entry_data| + Gitlab::I18n::TranslationEntry.new(entry_data, metadata_entry.expected_plurals) + end + + nil + rescue SimplePoParser::ParserError => e + @translation_entries = [] + e.message + end + + def validate_entries + errors = {} + + translation_entries.each do |entry| + errors_for_entry = validate_entry(entry) + errors[join_message(entry.msgid)] = errors_for_entry if errors_for_entry.any? + end + + errors + end + + def validate_entry(entry) + errors = [] + + validate_flags(errors, entry) + validate_variables(errors, entry) + validate_newlines(errors, entry) + validate_number_of_plurals(errors, entry) + validate_unescaped_chars(errors, entry) + + errors + end + + def validate_unescaped_chars(errors, entry) + if entry.msgid_contains_unescaped_chars? + errors << 'contains unescaped `%`, escape it using `%%`' + end + + if entry.plural_id_contains_unescaped_chars? + errors << 'plural id contains unescaped `%`, escape it using `%%`' + end + + if entry.translations_contain_unescaped_chars? + errors << 'translation contains unescaped `%`, escape it using `%%`' + end + end + + def validate_number_of_plurals(errors, entry) + return unless metadata_entry&.expected_plurals + return unless entry.translated? + + if entry.has_plural? && entry.all_translations.size != metadata_entry.expected_plurals + errors << "should have #{metadata_entry.expected_plurals} "\ + "#{'translations'.pluralize(metadata_entry.expected_plurals)}" + end + end + + def validate_newlines(errors, entry) + if entry.msgid_contains_newlines? + errors << 'is defined over multiple lines, this breaks some tooling.' + end + + if entry.plural_id_contains_newlines? + errors << 'plural is defined over multiple lines, this breaks some tooling.' + end + + if entry.translations_contain_newlines? + errors << 'has translations defined over multiple lines, this breaks some tooling.' + end + end + + def validate_variables(errors, entry) + if entry.has_singular_translation? + validate_variables_in_message(errors, entry.msgid, entry.singular_translation) + end + + if entry.has_plural? + entry.plural_translations.each do |translation| + validate_variables_in_message(errors, entry.plural_id, translation) + end + end + end + + def validate_variables_in_message(errors, message_id, message_translation) + message_id = join_message(message_id) + required_variables = message_id.scan(VARIABLE_REGEX) + + validate_unnamed_variables(errors, required_variables) + validate_translation(errors, message_id, required_variables) + validate_variable_usage(errors, message_translation, required_variables) + end + + def validate_translation(errors, message_id, used_variables) + variables = fill_in_variables(used_variables) + + begin + Gitlab::I18n.with_locale(locale) do + translated = if message_id.include?('|') + FastGettext::Translation.s_(message_id) + else + FastGettext::Translation._(message_id) + end + + translated % variables + end + + # `sprintf` could raise an `ArgumentError` when invalid passing something + # other than a Hash when using named variables + # + # `sprintf` could raise `TypeError` when passing a wrong type when using + # unnamed variables + # + # FastGettext::Translation could raise `RuntimeError` (raised as a string), + # or as subclassess `NoTextDomainConfigured` & `InvalidFormat` + # + # `FastGettext::Translation` could raise `ArgumentError` as subclassess + # `InvalidEncoding`, `IllegalSequence` & `InvalidCharacter` + rescue ArgumentError, TypeError, RuntimeError => e + errors << "Failure translating to #{locale} with #{variables}: #{e.message}" + end + end + + def fill_in_variables(variables) + if variables.empty? + [] + elsif variables.any? { |variable| unnamed_variable?(variable) } + variables.map do |variable| + variable == '%d' ? Random.rand(1000) : Gitlab::Utils.random_string + end + else + variables.inject({}) do |hash, variable| + variable_name = variable[/\w+/] + hash[variable_name] = Gitlab::Utils.random_string + hash + end + end + end + + def validate_unnamed_variables(errors, variables) + if variables.size > 1 && variables.any? { |variable_name| unnamed_variable?(variable_name) } + errors << 'is combining multiple unnamed variables' + end + end + + def validate_variable_usage(errors, translation, required_variables) + translation = join_message(translation) + + # We don't need to validate when the message is empty. + # In this case we fall back to the default, which has all the the + # required variables. + return if translation.empty? + + found_variables = translation.scan(VARIABLE_REGEX) + + missing_variables = required_variables - found_variables + if missing_variables.any? + errors << "<#{translation}> is missing: [#{missing_variables.to_sentence}]" + end + + unknown_variables = found_variables - required_variables + if unknown_variables.any? + errors << "<#{translation}> is using unknown variables: [#{unknown_variables.to_sentence}]" + end + end + + def unnamed_variable?(variable_name) + !variable_name.start_with?('%{') + end + + def validate_flags(errors, entry) + errors << "is marked #{entry.flag}" if entry.flag + end + + def join_message(message) + Array(message).join + end + end + end +end diff --git a/lib/gitlab/i18n/translation_entry.rb b/lib/gitlab/i18n/translation_entry.rb new file mode 100644 index 00000000000..e6c95afca7e --- /dev/null +++ b/lib/gitlab/i18n/translation_entry.rb @@ -0,0 +1,92 @@ +module Gitlab + module I18n + class TranslationEntry + PERCENT_REGEX = /(?:^|[^%])%(?!{\w*}|[a-z%])/.freeze + + attr_reader :nplurals, :entry_data + + def initialize(entry_data, nplurals) + @entry_data = entry_data + @nplurals = nplurals + end + + def msgid + entry_data[:msgid] + end + + def plural_id + entry_data[:msgid_plural] + end + + def has_plural? + plural_id.present? + end + + def singular_translation + all_translations.first if has_singular_translation? + end + + def all_translations + @all_translations ||= entry_data.fetch_values(*translation_keys) + .reject(&:empty?) + end + + def translated? + all_translations.any? + end + + def plural_translations + return [] unless has_plural? + return [] unless translated? + + @plural_translations ||= if has_singular_translation? + all_translations.drop(1) + else + all_translations + end + end + + def flag + entry_data[:flag] + end + + def has_singular_translation? + nplurals > 1 || !has_plural? + end + + def msgid_contains_newlines? + msgid.is_a?(Array) + end + + def plural_id_contains_newlines? + plural_id.is_a?(Array) + end + + def translations_contain_newlines? + all_translations.any? { |translation| translation.is_a?(Array) } + end + + def msgid_contains_unescaped_chars? + contains_unescaped_chars?(msgid) + end + + def plural_id_contains_unescaped_chars? + contains_unescaped_chars?(plural_id) + end + + def translations_contain_unescaped_chars? + all_translations.any? { |translation| contains_unescaped_chars?(translation) } + end + + def contains_unescaped_chars?(string) + string =~ PERCENT_REGEX + end + + private + + def translation_keys + @translation_keys ||= entry_data.keys.select { |key| key.to_s =~ /\Amsgstr(\[\d+\])?\z/ } + end + end + end +end diff --git a/lib/gitlab/key_fingerprint.rb b/lib/gitlab/key_fingerprint.rb deleted file mode 100644 index d9a79f7c291..00000000000 --- a/lib/gitlab/key_fingerprint.rb +++ /dev/null @@ -1,48 +0,0 @@ -module Gitlab - class KeyFingerprint - attr_reader :key, :ssh_key - - # Unqualified MD5 fingerprint for compatibility - delegate :fingerprint, to: :ssh_key, allow_nil: true - - def initialize(key) - @key = key - - @ssh_key = - begin - Net::SSH::KeyFactory.load_data_public_key(key) - rescue Net::SSH::Exception, NotImplementedError - end - end - - def valid? - ssh_key.present? - end - - def type - return unless valid? - - parts = ssh_key.ssh_type.split('-') - parts.shift if parts[0] == 'ssh' - - parts[0].upcase - end - - def bits - return unless valid? - - case type - when 'RSA' - ssh_key.n.num_bits - when 'DSS', 'DSA' - ssh_key.p.num_bits - when 'ECDSA' - ssh_key.group.order.num_bits - when 'ED25519' - 256 - else - raise "Unsupported key type: #{type}" - end - end - end -end diff --git a/lib/gitlab/reference_counter.rb b/lib/gitlab/reference_counter.rb new file mode 100644 index 00000000000..bb26f1b610a --- /dev/null +++ b/lib/gitlab/reference_counter.rb @@ -0,0 +1,44 @@ +module Gitlab + class ReferenceCounter + REFERENCE_EXPIRE_TIME = 600 + + attr_reader :gl_repository, :key + + def initialize(gl_repository) + @gl_repository = gl_repository + @key = "git-receive-pack-reference-counter:#{gl_repository}" + end + + def value + Gitlab::Redis::SharedState.with { |redis| (redis.get(key) || 0).to_i } + end + + def increase + redis_cmd do |redis| + redis.incr(key) + redis.expire(key, REFERENCE_EXPIRE_TIME) + end + end + + def decrease + redis_cmd do |redis| + current_value = redis.decr(key) + if current_value < 0 + Rails.logger.warn("Reference counter for #{gl_repository} decreased" \ + " when its value was less than 1. Reseting the counter.") + redis.del(key) + end + end + end + + private + + def redis_cmd + Gitlab::Redis::SharedState.with { |redis| yield(redis) } + true + rescue => e + Rails.logger.warn("GitLab: An unexpected error occurred in writing to Redis: #{e}") + false + end + end +end diff --git a/lib/gitlab/sentry.rb b/lib/gitlab/sentry.rb index f6bdd6cf0fe..159d0e7952e 100644 --- a/lib/gitlab/sentry.rb +++ b/lib/gitlab/sentry.rb @@ -9,6 +9,8 @@ module Gitlab def self.context(current_user = nil) return unless self.enabled? + Raven.tags_context(locale: I18n.locale) + if current_user Raven.user_context( id: current_user.id, diff --git a/lib/gitlab/ssh_public_key.rb b/lib/gitlab/ssh_public_key.rb new file mode 100644 index 00000000000..89ca1298120 --- /dev/null +++ b/lib/gitlab/ssh_public_key.rb @@ -0,0 +1,71 @@ +module Gitlab + class SSHPublicKey + Technology = Struct.new(:name, :key_class, :supported_sizes) + + Technologies = [ + Technology.new(:rsa, OpenSSL::PKey::RSA, [1024, 2048, 3072, 4096]), + Technology.new(:dsa, OpenSSL::PKey::DSA, [1024, 2048, 3072]), + Technology.new(:ecdsa, OpenSSL::PKey::EC, [256, 384, 521]), + Technology.new(:ed25519, Net::SSH::Authentication::ED25519::PubKey, [256]) + ].freeze + + def self.technology(name) + Technologies.find { |tech| tech.name.to_s == name.to_s } + end + + def self.technology_for_key(key) + Technologies.find { |tech| key.is_a?(tech.key_class) } + end + + def self.supported_sizes(name) + technology(name)&.supported_sizes + end + + attr_reader :key_text, :key + + # Unqualified MD5 fingerprint for compatibility + delegate :fingerprint, to: :key, allow_nil: true + + def initialize(key_text) + @key_text = key_text + + @key = + begin + Net::SSH::KeyFactory.load_data_public_key(key_text) + rescue StandardError, NotImplementedError + end + end + + def valid? + key.present? + end + + def type + technology.name if valid? + end + + def bits + return unless valid? + + case type + when :rsa + key.n.num_bits + when :dsa + key.p.num_bits + when :ecdsa + key.group.order.num_bits + when :ed25519 + 256 + else + raise "Unsupported key type: #{type}" + end + end + + private + + def technology + @technology ||= + self.class.technology_for_key(key) || raise("Unsupported key type: #{key.class}") + end + end +end diff --git a/lib/gitlab/utils.rb b/lib/gitlab/utils.rb index 9670c93759e..abb3d3a02c3 100644 --- a/lib/gitlab/utils.rb +++ b/lib/gitlab/utils.rb @@ -42,5 +42,9 @@ module Gitlab 'No' end end + + def random_string + Random.rand(Float::MAX.to_i).to_s(36) + end end end diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb index a362a3a0bc6..e5ad9b5a40c 100644 --- a/lib/gitlab/workhorse.rb +++ b/lib/gitlab/workhorse.rb @@ -35,10 +35,7 @@ module Gitlab when 'git_receive_pack' Gitlab::GitalyClient.feature_enabled?(:post_receive_pack) when 'git_upload_pack' - Gitlab::GitalyClient.feature_enabled?( - :post_upload_pack, - status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT - ) + true when 'info_refs' true else diff --git a/lib/tasks/gettext.rake b/lib/tasks/gettext.rake index b48e4dce445..e1491f29b5e 100644 --- a/lib/tasks/gettext.rake +++ b/lib/tasks/gettext.rake @@ -19,4 +19,44 @@ namespace :gettext do Rake::Task['gettext:pack'].invoke Rake::Task['gettext:po_to_json'].invoke end + + desc 'Lint all po files in `locale/' + task lint: :environment do + FastGettext.silence_errors + files = Dir.glob(Rails.root.join('locale/*/gitlab.po')) + + linters = files.map do |file| + locale = File.basename(File.dirname(file)) + + Gitlab::I18n::PoLinter.new(file, locale) + end + + pot_file = Rails.root.join('locale/gitlab.pot') + linters.unshift(Gitlab::I18n::PoLinter.new(pot_file)) + + failed_linters = linters.select { |linter| linter.errors.any? } + + if failed_linters.empty? + puts 'All PO files are valid.' + else + failed_linters.each do |linter| + report_errors_for_file(linter.po_path, linter.errors) + end + + raise "Not all PO-files are valid: #{failed_linters.map(&:po_path).to_sentence}" + end + end + + def report_errors_for_file(file, errors_for_file) + puts "Errors in `#{file}`:" + + errors_for_file.each do |message_id, errors| + puts " #{message_id}" + errors.each do |error| + spaces = ' ' * 4 + error = error.lines.join("#{spaces}") + puts "#{spaces}#{error}" + end + end + end end |