diff options
35 files changed, 681 insertions, 175 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f566dfd76e9..2d33bad5886 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,9 +1,5 @@ image: "ruby:2.1" -services: - - mysql:latest - - redis:alpine - cache: key: "ruby21" paths: @@ -34,7 +30,6 @@ stages: - post-test # Prepare and merge knapsack tests - .knapsack-state: &knapsack-state services: [] variables: @@ -68,8 +63,14 @@ update-knapsack: # Execute all testing suites +.use-db: &use-db + services: + - mysql:latest + - redis:alpine + .rspec-knapsack: &rspec-knapsack stage: test + <<: *use-db script: - bundle exec rake assets:precompile 2>/dev/null - JOB_NAME=( $CI_BUILD_NAME ) @@ -85,6 +86,7 @@ update-knapsack: .spinach-knapsack: &spinach-knapsack stage: test + <<: *use-db script: - bundle exec rake assets:precompile 2>/dev/null - JOB_NAME=( $CI_BUILD_NAME ) @@ -133,6 +135,7 @@ spinach 9 10: *spinach-knapsack # Execute all testing suites against Ruby 2.3 .ruby-23: &ruby-23 image: "ruby:2.3" + <<: *use-db only: - master cache: @@ -183,23 +186,41 @@ spinach 9 10 ruby23: *spinach-knapsack-ruby23 # Other generic tests +.static-analyses-variables: &static-analyses-variables + variables: + SIMPLECOV: "false" + USE_DB: "false" + USE_BUNDLE_INSTALL: "true" + .exec: &exec + <<: *static-analyses-variables stage: test script: - bundle exec $CI_BUILD_NAME -teaspoon: *exec rubocop: *exec rake scss_lint: *exec rake brakeman: *exec rake flog: *exec rake flay: *exec -rake db:migrate:reset: *exec license_finder: *exec rake downtime_check: *exec +rake db:migrate:reset: + stage: test + <<: *use-db + script: + - rake db:migrate:reset + +teaspoon: + stage: test + <<: *use-db + script: + - teaspoon + bundler:audit: stage: test + <<: *static-analyses-variables only: - master script: diff --git a/CHANGELOG b/CHANGELOG index 2a9cfa84ad4..389f88f27b1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -27,9 +27,13 @@ v 8.10.0 (unreleased) - Store when and yaml variables in builds table - Display last commit of deleted branch in push events !4699 (winniehell) - Escape file extension when parsing search results !5141 (winniehell) + - Add "passing with warnings" to the merge request pipeline possible statuses, this happens when builds that allow failures have failed. !5004 - Apply the trusted_proxies config to the rack request object for use with rack_attack + - Added the ability to block sign ups using a domain blacklist !5259 - Upgrade to Rails 4.2.7. !5236 - Extend exposed environment variables for CI builds + - Deprecate APIs "projects/:id/keys/...". Use "projects/:id/deploy_keys/..." instead + - Add API "deploy_keys" for admins to get all deploy keys - Allow to pull code with deploy key from public projects - Use limit parameter rather than hardcoded value in `ldap:check` rake task (Mike Ricketts) - Add Sidekiq queue duration to transaction metrics. @@ -131,6 +135,7 @@ v 8.10.0 (unreleased) - Optimistic locking for Issues and Merge Requests (Title and description overriding prevention) - Redesign Builds and Pipelines pages - Change status color and icon for running builds + - Fix commenting issue in side by side diff view for unchanged lines - Fix markdown rendering for: consecutive labels references, label references that begin with a digit or contains `.` - Project export filename now includes the project and namespace path - Fix last update timestamp on issues not preserved on gitlab.com and project imports diff --git a/app/assets/javascripts/admin.js.coffee b/app/assets/javascripts/admin.js.coffee index b2b8e1b7ffb..90c09619f8c 100644 --- a/app/assets/javascripts/admin.js.coffee +++ b/app/assets/javascripts/admin.js.coffee @@ -38,3 +38,14 @@ class @Admin $('li.group_member').bind 'ajax:success', -> Turbolinks.visit(location.href) + + showBlacklistType = -> + if $("input[name='blacklist_type']:checked").val() == 'file' + $('.blacklist-file').show() + $('.blacklist-raw').hide() + else + $('.blacklist-file').hide() + $('.blacklist-raw').show() + + $("input[name='blacklist_type']").click showBlacklistType + showBlacklistType() diff --git a/app/assets/javascripts/files_comment_button.js.coffee b/app/assets/javascripts/files_comment_button.js.coffee index db0bf7082a9..5ab82c39fcd 100644 --- a/app/assets/javascripts/files_comment_button.js.coffee +++ b/app/assets/javascripts/files_comment_button.js.coffee @@ -7,7 +7,6 @@ class @FilesCommentButton UNFOLDABLE_LINE_CLASS = 'js-unfold' EMPTY_CELL_CLASS = 'empty-cell' OLD_LINE_CLASS = 'old_line' - NEW_CLASS = 'new' LINE_COLUMN_CLASSES = ".#{LINE_NUMBER_CLASS}, .line_content" TEXT_FILE_SELECTOR = '.text-file' DEBOUNCE_TIMEOUT_DURATION = 100 @@ -18,6 +17,8 @@ class @FilesCommentButton debounce = _.debounce @render, DEBOUNCE_TIMEOUT_DURATION $(document) + .off 'mouseover', LINE_COLUMN_CLASSES + .off 'mouseleave', LINE_COLUMN_CLASSES .on 'mouseover', LINE_COLUMN_CLASSES, debounce .on 'mouseleave', LINE_COLUMN_CLASSES, @destroy @@ -64,20 +65,20 @@ class @FilesCommentButton getLineContent: (hoveredElement) -> return hoveredElement if hoveredElement.hasClass LINE_CONTENT_CLASS - $(".#{LINE_CONTENT_CLASS + @diffTypeClass hoveredElement}", hoveredElement.parent()) + if @VIEW_TYPE is 'inline' + return $(hoveredElement).closest(LINE_HOLDER_CLASS).find ".#{LINE_CONTENT_CLASS}" + else + return $(hoveredElement).next ".#{LINE_CONTENT_CLASS}" getButtonParent: (hoveredElement) -> if @VIEW_TYPE is 'inline' return hoveredElement if hoveredElement.hasClass OLD_LINE_CLASS - $(".#{OLD_LINE_CLASS}", hoveredElement.parent()) + hoveredElement.parent().find ".#{OLD_LINE_CLASS}" else return hoveredElement if hoveredElement.hasClass LINE_NUMBER_CLASS - $(".#{LINE_NUMBER_CLASS + @diffTypeClass hoveredElement}", hoveredElement.parent()) - - diffTypeClass: (hoveredElement) -> - if hoveredElement.hasClass(NEW_CLASS) then '.new' else '.old' + $(hoveredElement).prev ".#{LINE_NUMBER_CLASS}" isMovingToSameType: (e) -> newButtonParent = @getButtonParent $(e.toElement) diff --git a/app/assets/javascripts/merge_request_widget.js.coffee b/app/assets/javascripts/merge_request_widget.js.coffee index 779f536d9f0..963a0550c35 100644 --- a/app/assets/javascripts/merge_request_widget.js.coffee +++ b/app/assets/javascripts/merge_request_widget.js.coffee @@ -55,10 +55,13 @@ class @MergeRequestWidget $('.mr-state-widget').replaceWith(data) ciLabelForStatus: (status) -> - if status is 'success' - 'passed' - else - status + switch status + when 'success' + 'passed' + when 'success_with_warnings' + 'passed with warnings' + else + status pollCIStatus: -> @fetchBuildStatusInterval = setInterval ( => @@ -116,7 +119,7 @@ class @MergeRequestWidget showCIStatus: (state) -> return if not state? $('.ci_widget').hide() - allowed_states = ["failed", "canceled", "running", "pending", "success", "skipped", "not_found"] + allowed_states = ["failed", "canceled", "running", "pending", "success", "success_with_warnings", "skipped", "not_found"] if state in allowed_states $('.ci_widget.ci-' + state).show() switch state @@ -124,7 +127,7 @@ class @MergeRequestWidget @setMergeButtonClass('btn-danger') when "running" @setMergeButtonClass('btn-warning') - when "success" + when "success", "success_with_warnings" @setMergeButtonClass('btn-create') else $('.ci_widget.ci-error').show() diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 5254faf723d..4e806e60e7e 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -70,6 +70,14 @@ color: $gl-success; } + &.ci-success_with_warnings { + color: $gl-success; + + i { + color: $gl-warning; + } + } + &.ci-skipped { background-color: #eee; color: #888; diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss index a22d4b6f6be..99f64b5e4cc 100644 --- a/app/assets/stylesheets/pages/status.scss +++ b/app/assets/stylesheets/pages/status.scss @@ -15,7 +15,8 @@ border-color: $gl-danger; } - &.ci-success { + &.ci-success, + &.ci-success_with_warnings { color: $gl-success; border-color: $gl-success; } @@ -57,9 +58,12 @@ .ci-status-icon-failed { color: $gl-danger; } - .ci-status-icon-pending { + + .ci-status-icon-pending, + .ci-status-icon-success_with_warning { color: $gl-warning; } + .ci-status-icon-running { color: $blue-normal; } diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index 23ba83aba0e..9e1dc15de84 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -64,6 +64,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController params[:application_setting][:disabled_oauth_sign_in_sources] = AuthHelper.button_based_providers.map(&:to_s) - Array(enabled_oauth_sign_in_sources) + params.delete(:domain_blacklist_raw) if params[:domain_blacklist_file] params.require(:application_setting).permit( :default_projects_limit, @@ -83,7 +84,10 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController :default_project_visibility, :default_snippet_visibility, :default_group_visibility, - :restricted_signup_domains_raw, + :domain_whitelist_raw, + :domain_blacklist_enabled, + :domain_blacklist_raw, + :domain_blacklist_file, :version_check_enabled, :admin_notification_email, :user_oauth_applications, diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index df659bb8c3b..7beeb7d97d0 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -286,6 +286,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController status = pipeline.status coverage = pipeline.try(:coverage) + status = "success_with_warnings" if pipeline.success? && pipeline.has_warnings? + status ||= "preparing" else ci_service = @merge_request.source_project.ci_service diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index 59a8365d60b..ffac28f78a0 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -15,8 +15,11 @@ module CiStatusHelper end def ci_label_for_status(status) - if status == 'success' + case status + when 'success' 'passed' + when 'success_with_warnings' + 'passed with warnings' else status end diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index c6f77cc055f..8c19d9dc9c8 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -4,12 +4,20 @@ class ApplicationSetting < ActiveRecord::Base add_authentication_token_field :health_check_access_token CACHE_KEY = 'application_setting.last' + DOMAIN_LIST_SEPARATOR = %r{\s*[,;]\s* # comma or semicolon, optionally surrounded by whitespace + | # or + \s # any whitespace character + | # or + [\r\n] # any number of newline characters + }x serialize :restricted_visibility_levels serialize :import_sources serialize :disabled_oauth_sign_in_sources, Array - serialize :restricted_signup_domains, Array - attr_accessor :restricted_signup_domains_raw + serialize :domain_whitelist, Array + serialize :domain_blacklist, Array + + attr_accessor :domain_whitelist_raw, :domain_blacklist_raw validates :session_expire_delay, presence: true, @@ -62,6 +70,10 @@ class ApplicationSetting < ActiveRecord::Base validates :enabled_git_access_protocol, inclusion: { in: %w(ssh http), allow_blank: true, allow_nil: true } + validates :domain_blacklist, + presence: { message: 'Domain blacklist cannot be empty if Blacklist is enabled.' }, + if: :domain_blacklist_enabled? + validates_each :restricted_visibility_levels do |record, attr, value| unless value.nil? value.each do |level| @@ -129,7 +141,7 @@ class ApplicationSetting < ActiveRecord::Base session_expire_delay: Settings.gitlab['session_expire_delay'], default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'], default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'], - restricted_signup_domains: Settings.gitlab['restricted_signup_domains'], + domain_whitelist: Settings.gitlab['domain_whitelist'], import_sources: %w[github bitbucket gitlab gitorious google_code fogbugz git gitlab_project], shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'], max_artifacts_size: Settings.artifacts['max_size'], @@ -150,20 +162,30 @@ class ApplicationSetting < ActiveRecord::Base ActiveRecord::Base.connection.column_exists?(:application_settings, :home_page_url) end - def restricted_signup_domains_raw - self.restricted_signup_domains.join("\n") unless self.restricted_signup_domains.nil? + def domain_whitelist_raw + self.domain_whitelist.join("\n") unless self.domain_whitelist.nil? + end + + def domain_blacklist_raw + self.domain_blacklist.join("\n") unless self.domain_blacklist.nil? + end + + def domain_whitelist_raw=(values) + self.domain_whitelist = [] + self.domain_whitelist = values.split(DOMAIN_LIST_SEPARATOR) + self.domain_whitelist.reject! { |d| d.empty? } + self.domain_whitelist + end + + def domain_blacklist_raw=(values) + self.domain_blacklist = [] + self.domain_blacklist = values.split(DOMAIN_LIST_SEPARATOR) + self.domain_blacklist.reject! { |d| d.empty? } + self.domain_blacklist end - def restricted_signup_domains_raw=(values) - self.restricted_signup_domains = [] - self.restricted_signup_domains = values.split( - /\s*[,;]\s* # comma or semicolon, optionally surrounded by whitespace - | # or - \s # any whitespace character - | # or - [\r\n] # any number of newline characters - /x) - self.restricted_signup_domains.reject! { |d| d.empty? } + def domain_blacklist_file=(file) + self.domain_blacklist_raw = file.read end def runners_registration_token diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 88fa01c896d..6d3bbb03484 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -146,6 +146,10 @@ module Ci end end + def has_warnings? + builds.latest.ignored.any? + end + def config_processor return nil unless ci_yaml_file return @config_processor if defined?(@config_processor) diff --git a/app/models/user.rb b/app/models/user.rb index 8dc10becd69..db747434959 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -111,7 +111,7 @@ class User < ActiveRecord::Base validates :avatar, file_size: { maximum: 200.kilobytes.to_i } before_validation :generate_password, on: :create - before_validation :restricted_signup_domains, on: :create + before_validation :signup_domain_valid?, on: :create before_validation :sanitize_attrs before_validation :set_notification_email, if: ->(user) { user.email_changed? } before_validation :set_public_email, if: ->(user) { user.public_email_changed? } @@ -762,29 +762,6 @@ class User < ActiveRecord::Base Project.where(id: events) end - def restricted_signup_domains - email_domains = current_application_settings.restricted_signup_domains - - unless email_domains.blank? - match_found = email_domains.any? do |domain| - escaped = Regexp.escape(domain).gsub('\*', '.*?') - regexp = Regexp.new "^#{escaped}$", Regexp::IGNORECASE - email_domain = Mail::Address.new(self.email).domain - email_domain =~ regexp - end - - unless match_found - self.errors.add :email, - 'is not whitelisted. ' + - 'Email domains valid for registration are: ' + - email_domains.join(', ') - return false - end - end - - true - end - def can_be_removed? !solo_owned_groups.present? end @@ -883,4 +860,40 @@ class User < ActiveRecord::Base self.can_create_group = false self.projects_limit = 0 end + + def signup_domain_valid? + valid = true + error = nil + + if current_application_settings.domain_blacklist_enabled? + blocked_domains = current_application_settings.domain_blacklist + if domain_matches?(blocked_domains, self.email) + error = 'is not from an allowed domain.' + valid = false + end + end + + allowed_domains = current_application_settings.domain_whitelist + unless allowed_domains.blank? + if domain_matches?(allowed_domains, self.email) + valid = true + else + error = "is not whitelisted. Email domains valid for registration are: #{allowed_domains.join(', ')}" + valid = false + end + end + + self.errors.add(:email, error) unless valid + + valid + end + + def domain_matches?(email_domains, email) + signup_domain = Mail::Address.new(email).domain + email_domains.any? do |domain| + escaped = Regexp.escape(domain).gsub('\*', '.*?') + regexp = Regexp.new "^#{escaped}$", Regexp::IGNORECASE + signup_domain =~ regexp + end + end end diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index 538d8176ce7..23b52d08df7 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -109,7 +109,7 @@ Newly registered users will by default be external %fieldset - %legend Sign-in Restrictions + %legend Sign-up Restrictions .form-group .col-sm-offset-2.col-sm-10 .checkbox @@ -123,6 +123,49 @@ = f.check_box :send_user_confirmation_email Send confirmation email on sign-up .form-group + = f.label :domain_whitelist, 'Whitelisted domains for sign-ups', class: 'control-label col-sm-2' + .col-sm-10 + = f.text_area :domain_whitelist_raw, placeholder: 'domain.com', class: 'form-control', rows: 8 + .help-block ONLY users with e-mail addresses that match these domain(s) will be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com + .form-group + = f.label :domain_blacklist_enabled, 'Domain Blacklist', class: 'control-label col-sm-2' + .col-sm-10 + .checkbox + = f.label :domain_blacklist_enabled do + = f.check_box :domain_blacklist_enabled + Enable domain blacklist for sign ups + .form-group + .col-sm-offset-2.col-sm-10 + .radio + = label_tag :blacklist_type_file do + = radio_button_tag :blacklist_type, :file + .option-title + Upload blacklist file + .radio + = label_tag :blacklist_type_raw do + = radio_button_tag :blacklist_type, :raw, @application_setting.domain_blacklist.present? || @application_setting.domain_blacklist.blank? + .option-title + Enter blacklist manually + .form-group.blacklist-file + = f.label :domain_blacklist_file, 'Blacklist file', class: 'control-label col-sm-2' + .col-sm-10 + = f.file_field :domain_blacklist_file, class: 'form-control', accept: '.txt,.conf' + .help-block Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines or commas for multiple entries. + .form-group.blacklist-raw + = f.label :domain_blacklist, 'Blacklisted domains for sign-ups', class: 'control-label col-sm-2' + .col-sm-10 + = f.text_area :domain_blacklist_raw, placeholder: 'domain.com', class: 'form-control', rows: 8 + .help-block Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com + + .form-group + = f.label :after_sign_up_text, class: 'control-label col-sm-2' + .col-sm-10 + = f.text_area :after_sign_up_text, class: 'form-control', rows: 4 + .help-block Markdown enabled + + %fieldset + %legend Sign-in Restrictions + .form-group .col-sm-offset-2.col-sm-10 .checkbox = f.label :signin_enabled do @@ -148,11 +191,6 @@ = f.number_field :two_factor_grace_period, min: 0, class: 'form-control', placeholder: '0' .help-block Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication .form-group - = f.label :restricted_signup_domains, 'Restricted domains for sign-ups', class: 'control-label col-sm-2' - .col-sm-10 - = f.text_area :restricted_signup_domains_raw, placeholder: 'domain.com', class: 'form-control' - .help-block Only users with e-mail addresses that match these domain(s) will be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com - .form-group = f.label :home_page_url, 'Home page URL', class: 'control-label col-sm-2' .col-sm-10 = f.text_field :home_page_url, class: 'form-control', placeholder: 'http://company.example.com', :'aria-describedby' => 'home_help_block' @@ -168,11 +206,6 @@ = f.text_area :sign_in_text, class: 'form-control', rows: 4 .help-block Markdown enabled .form-group - = f.label :after_sign_up_text, class: 'control-label col-sm-2' - .col-sm-10 - = f.text_area :after_sign_up_text, class: 'form-control', rows: 4 - .help-block Markdown enabled - .form-group = f.label :help_page_text, class: 'control-label col-sm-2' .col-sm-10 = f.text_area :help_page_text, class: 'form-control', rows: 4 @@ -352,4 +385,4 @@ .form-actions - = f.submit 'Save', class: 'btn btn-save' + = f.submit 'Save', class: 'btn btn-save'
\ No newline at end of file diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml index 489c632ae22..6ef640bb654 100644 --- a/app/views/projects/merge_requests/widget/_heading.html.haml +++ b/app/views/projects/merge_requests/widget/_heading.html.haml @@ -1,6 +1,6 @@ - if @pipeline .mr-widget-heading - - %w[success skipped canceled failed running pending].each do |status| + - %w[success success_with_warnings skipped canceled failed running pending].each do |status| .ci_widget{ class: "ci-#{status}", style: ("display:none" unless @pipeline.status == status) } = ci_icon_for_status(status) %span diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 51d93e8cde0..693507e0bec 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -212,7 +212,7 @@ Settings.gitlab.default_projects_features['builds'] = true if Settin Settings.gitlab.default_projects_features['container_registry'] = true if Settings.gitlab.default_projects_features['container_registry'].nil? Settings.gitlab.default_projects_features['visibility_level'] = Settings.send(:verify_constant, Gitlab::VisibilityLevel, Settings.gitlab.default_projects_features['visibility_level'], Gitlab::VisibilityLevel::PRIVATE) Settings.gitlab['repository_downloads_path'] = File.join(Settings.shared['path'], 'cache/archive') if Settings.gitlab['repository_downloads_path'].nil? -Settings.gitlab['restricted_signup_domains'] ||= [] +Settings.gitlab['domain_whitelist'] ||= [] Settings.gitlab['import_sources'] ||= %w[github bitbucket gitlab gitorious google_code fogbugz git gitlab_project] Settings.gitlab['trusted_proxies'] ||= [] diff --git a/db/migrate/20160713205315_add_domain_blacklist_to_application_settings.rb b/db/migrate/20160713205315_add_domain_blacklist_to_application_settings.rb new file mode 100644 index 00000000000..ecdd1bd7e5e --- /dev/null +++ b/db/migrate/20160713205315_add_domain_blacklist_to_application_settings.rb @@ -0,0 +1,22 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddDomainBlacklistToApplicationSettings < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # When using the methods "add_concurrent_index" or "add_column_with_default" + # you must disable the use of transactions as these methods can not run in an + # existing transaction. When using "add_concurrent_index" make sure that this + # method is the _only_ method called in the migration, any other changes + # should go in a separate migration. This ensures that upon failure _only_ the + # index creation fails and can be retried or reverted easily. + # + # To disable transactions uncomment the following line and remove these + # comments: + # disable_ddl_transaction! + + def change + add_column :application_settings, :domain_blacklist_enabled, :boolean, default: false + add_column :application_settings, :domain_blacklist, :text + end +end diff --git a/db/migrate/20160715230841_rename_application_settings_restricted_signup_domains.rb b/db/migrate/20160715230841_rename_application_settings_restricted_signup_domains.rb new file mode 100644 index 00000000000..dd15704800a --- /dev/null +++ b/db/migrate/20160715230841_rename_application_settings_restricted_signup_domains.rb @@ -0,0 +1,21 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class RenameApplicationSettingsRestrictedSignupDomains < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # When using the methods "add_concurrent_index" or "add_column_with_default" + # you must disable the use of transactions as these methods can not run in an + # existing transaction. When using "add_concurrent_index" make sure that this + # method is the _only_ method called in the migration, any other changes + # should go in a separate migration. This ensures that upon failure _only_ the + # index creation fails and can be retried or reverted easily. + # + # To disable transactions uncomment the following line and remove these + # comments: + # disable_ddl_transaction! + + def change + rename_column :application_settings, :restricted_signup_domains, :domain_whitelist + end +end diff --git a/db/schema.rb b/db/schema.rb index 72780fb8d03..c7876426424 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -49,7 +49,7 @@ ActiveRecord::Schema.define(version: 20160718153603) do t.integer "max_attachment_size", default: 10, null: false t.integer "default_project_visibility" t.integer "default_snippet_visibility" - t.text "restricted_signup_domains" + t.text "domain_whitelist" t.boolean "user_oauth_applications", default: true t.string "after_sign_out_path" t.integer "session_expire_delay", default: 10080, null: false @@ -88,6 +88,8 @@ ActiveRecord::Schema.define(version: 20160718153603) do t.text "after_sign_up_text" t.string "repository_storage", default: "default" t.string "enabled_git_access_protocol" + t.boolean "domain_blacklist_enabled", default: false + t.text "domain_blacklist" end create_table "audit_events", force: :cascade do |t| diff --git a/doc/administration/access_restrictions.md b/doc/administration/access_restrictions.md index 51d7996effd..eb08cf139d4 100644 --- a/doc/administration/access_restrictions.md +++ b/doc/administration/access_restrictions.md @@ -1,6 +1,6 @@ # Access Restrictions -> **Note:** This feature is only available on versions 8.10 and above. +> **Note:** These features are only available on versions 8.10 and above. With GitLab's Access restrictions you can choose which Git access protocols you want your users to use to communicate with GitLab. This feature can be enabled @@ -35,4 +35,22 @@ not selected. > **Note:** Please keep in mind that disabling an access protocol does not actually block access to the server itself. The ports used for the protocol, be it SSH or HTTP, will still be accessible. What GitLab does is restrict access on the - application level.
\ No newline at end of file + application level. + +## Blacklist email domains + +With this feature enabled, you can block email addresses of a specific domain +from creating an account on your GitLab server. This is particularly useful to +prevent spam. Disposable email addresses are usually used by malicious users to +create dummy accounts and spam issues. + +This feature can be activated via the `Application Settings` in the Admin area, +and you have the option of entering the list manually, or uploading a file with +the list. + +The blacklist accepts wildcards, so you can use `*.test.com` to block every +`test.com` subdomain, or `*.io` to block all domains ending in `.io`. Domains +should be separated by a whitespace, semicolon, comma, or a new line. + + + diff --git a/doc/administration/img/domain_blacklist.png b/doc/administration/img/domain_blacklist.png Binary files differnew file mode 100644 index 00000000000..a7894e5f08d --- /dev/null +++ b/doc/administration/img/domain_blacklist.png diff --git a/doc/api/deploy_key_multiple_projects.md b/doc/api/deploy_key_multiple_projects.md index 3ad836f51b5..9280f0d68b6 100644 --- a/doc/api/deploy_key_multiple_projects.md +++ b/doc/api/deploy_key_multiple_projects.md @@ -24,6 +24,6 @@ With those IDs, add the same deploy key to all: ``` for project_id in 321 456 987; do curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" -H "Content-Type: application/json" \ - --data '{"title": "my key", "key": "ssh-rsa AAAA..."}' https://gitlab.example.com/api/v3/projects/${project_id}/keys + --data '{"title": "my key", "key": "ssh-rsa AAAA..."}' https://gitlab.example.com/api/v3/projects/${project_id}/deploy_keys done ``` diff --git a/doc/api/deploy_keys.md b/doc/api/deploy_keys.md index 9da1fe22e61..4e620ccc81a 100644 --- a/doc/api/deploy_keys.md +++ b/doc/api/deploy_keys.md @@ -1,11 +1,42 @@ # Deploy Keys -## List deploy keys +## List all deploy keys + +Get a list of all deploy keys across all projects. + +``` +GET /deploy_keys +``` + +```bash +curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/deploy_keys" +``` + +Example response: + +```json +[ + { + "id": 1, + "title": "Public key", + "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=", + "created_at": "2013-10-02T10:12:29Z" + }, + { + "id": 3, + "title": "Another Public key", + "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=", + "created_at": "2013-10-02T11:12:29Z" + } +] +``` + +## List project deploy keys Get a list of a project's deploy keys. ``` -GET /projects/:id/keys +GET /projects/:id/deploy_keys ``` | Attribute | Type | Required | Description | @@ -13,7 +44,7 @@ GET /projects/:id/keys | `id` | integer | yes | The ID of the project | ```bash -curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/keys" +curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/deploy_keys" ``` Example response: @@ -40,7 +71,7 @@ Example response: Get a single key. ``` -GET /projects/:id/keys/:key_id +GET /projects/:id/deploy_keys/:key_id ``` Parameters: @@ -51,7 +82,7 @@ Parameters: | `key_id` | integer | yes | The ID of the deploy key | ```bash -curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/keys/11" +curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/deploy_keys/11" ``` Example response: @@ -73,7 +104,7 @@ If the deploy key already exists in another project, it will be joined to curren project only if original one was is accessible by the same user. ``` -POST /projects/:id/keys +POST /projects/:id/deploy_keys ``` | Attribute | Type | Required | Description | @@ -83,7 +114,7 @@ POST /projects/:id/keys | `key` | string | yes | New deploy key | ```bash -curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" -H "Content-Type: application/json" --data '{"title": "My deploy key", "key": "ssh-rsa AAAA..."}' "https://gitlab.example.com/api/v3/projects/5/keys/" +curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" -H "Content-Type: application/json" --data '{"title": "My deploy key", "key": "ssh-rsa AAAA..."}' "https://gitlab.example.com/api/v3/projects/5/deploy_keys/" ``` Example response: @@ -102,7 +133,7 @@ Example response: Delete a deploy key from a project ``` -DELETE /projects/:id/keys/:key_id +DELETE /projects/:id/deploy_keys/:key_id ``` | Attribute | Type | Required | Description | @@ -111,7 +142,7 @@ DELETE /projects/:id/keys/:key_id | `key_id` | integer | yes | The ID of the deploy key | ```bash -curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/keys/13" +curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/deploy_keys/13" ``` Example response: diff --git a/doc/api/settings.md b/doc/api/settings.md index d9b68eaeadf..ea39b32561c 100644 --- a/doc/api/settings.md +++ b/doc/api/settings.md @@ -33,7 +33,9 @@ Example response: "session_expire_delay" : 10080, "home_page_url" : null, "default_snippet_visibility" : 0, - "restricted_signup_domains" : [], + "domain_whitelist" : [], + "domain_blacklist_enabled" : false, + "domain_blacklist" : [], "created_at" : "2016-01-04T15:44:55.176Z", "default_project_visibility" : 0, "gravatar_enabled" : true, @@ -63,7 +65,9 @@ PUT /application/settings | `session_expire_delay` | integer | no | Session duration in minutes. GitLab restart is required to apply changes | | `default_project_visibility` | integer | no | What visibility level new projects receive. Can take `0` _(Private)_, `1` _(Internal)_ and `2` _(Public)_ as a parameter. Default is `0`.| | `default_snippet_visibility` | integer | no | What visibility level new snippets receive. Can take `0` _(Private)_, `1` _(Internal)_ and `2` _(Public)_ as a parameter. Default is `0`.| -| `restricted_signup_domains` | array of strings | no | Force people to use only corporate emails for sign-up. Default is null, meaning there is no restriction. | +| `domain_whitelist` | array of strings | no | Force people to use only corporate emails for sign-up. Default is null, meaning there is no restriction. | +| `domain_blacklist_enabled` | boolean | no | Enable/disable the `domain_blacklist` | +| `domain_blacklist` | array of strings | yes (if `domain_whitelist_enabled` is `true` | People trying to sign-up with emails from this domain will not be allowed to do so. | | `user_oauth_applications` | boolean | no | Allow users to register any application to use GitLab as an OAuth provider | | `after_sign_out_path` | string | no | Where to redirect users after logout | | `container_registry_token_expire_delay` | integer | no | Container Registry token duration in minutes | @@ -93,7 +97,9 @@ Example response: "session_expire_delay": 10080, "default_project_visibility": 1, "default_snippet_visibility": 0, - "restricted_signup_domains": [], + "domain_whitelist": [], + "domain_blacklist_enabled" : false, + "domain_blacklist" : [], "user_oauth_applications": true, "after_sign_out_path": "", "container_registry_token_expire_delay": 5, diff --git a/doc/development/doc_styleguide.md b/doc/development/doc_styleguide.md index fac35ec964d..6ee7b3cfeeb 100644 --- a/doc/development/doc_styleguide.md +++ b/doc/development/doc_styleguide.md @@ -359,7 +359,7 @@ restrict the sign-up e-mail domains of a GitLab instance to `*.example.com` and `example.net`, you would do something like this: ```bash -curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" -d "restricted_signup_domains[]=*.example.com" -d "restricted_signup_domains[]=example.net" https://gitlab.example.com/api/v3/application/settings +curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" -d "domain_whitelist[]=*.example.com" -d "domain_whitelist[]=example.net" https://gitlab.example.com/api/v3/application/settings ``` [cURL]: http://curl.haxx.se/ "cURL website" diff --git a/lib/api/deploy_keys.rb b/lib/api/deploy_keys.rb index 06eb7756841..5c570b5e5ca 100644 --- a/lib/api/deploy_keys.rb +++ b/lib/api/deploy_keys.rb @@ -2,74 +2,87 @@ module API # Projects API class DeployKeys < Grape::API before { authenticate! } - before { authorize_admin_project } + + get "deploy_keys" do + authenticated_as_admin! + + keys = DeployKey.all + present keys, with: Entities::SSHKey + end resource :projects do - # Get a specific project's keys - # - # Example Request: - # GET /projects/:id/keys - get ":id/keys" do - present user_project.deploy_keys, with: Entities::SSHKey - end + before { authorize_admin_project } - # Get single key owned by currently authenticated user + # Routing "projects/:id/keys/..." is DEPRECATED and WILL BE REMOVED in version 9.0 + # Use "projects/:id/deploy_keys/..." instead. # - # Example Request: - # GET /projects/:id/keys/:id - get ":id/keys/:key_id" do - key = user_project.deploy_keys.find params[:key_id] - present key, with: Entities::SSHKey - end + %w(keys deploy_keys).each do |path| + # Get a specific project's deploy keys + # + # Example Request: + # GET /projects/:id/deploy_keys + get ":id/#{path}" do + present user_project.deploy_keys, with: Entities::SSHKey + end - # Add new ssh key to currently authenticated user - # If deploy key already exists - it will be joined to project - # but only if original one was is accessible by same user - # - # Parameters: - # key (required) - New SSH Key - # title (required) - New SSH Key's title - # Example Request: - # POST /projects/:id/keys - post ":id/keys" do - attrs = attributes_for_keys [:title, :key] + # Get single deploy key owned by currently authenticated user + # + # Example Request: + # GET /projects/:id/deploy_keys/:key_id + get ":id/#{path}/:key_id" do + key = user_project.deploy_keys.find params[:key_id] + present key, with: Entities::SSHKey + end - if attrs[:key].present? - attrs[:key].strip! + # Add new deploy key to currently authenticated user + # If deploy key already exists - it will be joined to project + # but only if original one was accessible by same user + # + # Parameters: + # key (required) - New deploy Key + # title (required) - New deploy Key's title + # Example Request: + # POST /projects/:id/deploy_keys + post ":id/#{path}" do + attrs = attributes_for_keys [:title, :key] - # check if key already exist in project - key = user_project.deploy_keys.find_by(key: attrs[:key]) - if key - present key, with: Entities::SSHKey - return + if attrs[:key].present? + attrs[:key].strip! + + # check if key already exist in project + key = user_project.deploy_keys.find_by(key: attrs[:key]) + if key + present key, with: Entities::SSHKey + next + end + + # Check for available deploy keys in other projects + key = current_user.accessible_deploy_keys.find_by(key: attrs[:key]) + if key + user_project.deploy_keys << key + present key, with: Entities::SSHKey + next + end end - # Check for available deploy keys in other projects - key = current_user.accessible_deploy_keys.find_by(key: attrs[:key]) - if key - user_project.deploy_keys << key + key = DeployKey.new attrs + + if key.valid? && user_project.deploy_keys << key present key, with: Entities::SSHKey - return + else + render_validation_error!(key) end end - key = DeployKey.new attrs - - if key.valid? && user_project.deploy_keys << key - present key, with: Entities::SSHKey - else - render_validation_error!(key) + # Delete existing deploy key of currently authenticated user + # + # Example Request: + # DELETE /projects/:id/deploy_keys/:key_id + delete ":id/#{path}/:key_id" do + key = user_project.deploy_keys.find params[:key_id] + key.destroy end end - - # Delete existed ssh key of currently authenticated user - # - # Example Request: - # DELETE /projects/:id/keys/:id - delete ":id/keys/:key_id" do - key = user_project.deploy_keys.find params[:key_id] - key.destroy - end end end end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index d7e74582459..fbf0d74663f 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -414,7 +414,9 @@ module API expose :default_project_visibility expose :default_snippet_visibility expose :default_group_visibility - expose :restricted_signup_domains + expose :domain_whitelist + expose :domain_blacklist_enabled + expose :domain_blacklist expose :user_oauth_applications expose :after_sign_out_path expose :container_registry_token_expire_delay diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb index ffc1814b29d..735331df66c 100644 --- a/lib/gitlab/current_settings.rb +++ b/lib/gitlab/current_settings.rb @@ -39,7 +39,7 @@ module Gitlab session_expire_delay: Settings.gitlab['session_expire_delay'], default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'], default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'], - restricted_signup_domains: Settings.gitlab['restricted_signup_domains'], + domain_whitelist: Settings.gitlab['domain_whitelist'], import_sources: %w[github bitbucket gitlab gitorious google_code fogbugz git gitlab_project], shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'], max_artifacts_size: Settings.artifacts['max_size'], diff --git a/spec/features/merge_requests/diffs_spec.rb b/spec/features/merge_requests/diffs_spec.rb index c9a0059645d..35f5bfe46be 100644 --- a/spec/features/merge_requests/diffs_spec.rb +++ b/spec/features/merge_requests/diffs_spec.rb @@ -22,4 +22,98 @@ feature 'Diffs URL', js: true, feature: true do expect(page).to have_css('.diffs.tab-pane.active') end end + + context 'when hovering over the parallel view diff file' do + let(:comment_button_class) { '.add-diff-note' } + + before(:each) do + visit diffs_namespace_project_merge_request_path @project.namespace, @project, @merge_request + click_link 'Side-by-side' + @old_line_number = first '.diff-line-num.old_line:not(.empty-cell)' + @new_line_number = first '.diff-line-num.new_line:not(.empty-cell)' + @old_line = first '.line_content[data-line-type="old"]' + @new_line = first '.line_content[data-line-type="new"]' + end + + it 'shows a comment button on the old side when hovering over an old line number' do + @old_line_number.hover + expect(@old_line_number).to have_css comment_button_class + expect(@new_line_number).not_to have_css comment_button_class + end + + it 'shows a comment button on the old side when hovering over an old line' do + @old_line.hover + expect(@old_line_number).to have_css comment_button_class + expect(@new_line_number).not_to have_css comment_button_class + end + + it 'shows a comment button on the new side when hovering over a new line number' do + @new_line_number.hover + expect(@new_line_number).to have_css comment_button_class + expect(@old_line_number).not_to have_css comment_button_class + end + + it 'shows a comment button on the new side when hovering over a new line' do + @new_line.hover + expect(@new_line_number).to have_css comment_button_class + expect(@old_line_number).not_to have_css comment_button_class + end + end + + context 'when hovering over the inline view diff file' do + let(:comment_button_class) { '.add-diff-note' } + + before(:each) do + visit diffs_namespace_project_merge_request_path @project.namespace, @project, @merge_request + click_link 'Inline' + @old_line_number = first '.diff-line-num.old_line:not(.unfold)' + @new_line_number = first '.diff-line-num.new_line:not(.unfold)' + @new_line = first '.line_content:not(.match)' + end + + it 'shows a comment button on the old side when hovering over an old line number' do + @old_line_number.hover + expect(@old_line_number).to have_css comment_button_class + expect(@new_line_number).not_to have_css comment_button_class + end + + it 'shows a comment button on the new side when hovering over a new line number' do + @new_line_number.hover + expect(@old_line_number).to have_css comment_button_class + expect(@new_line_number).not_to have_css comment_button_class + end + + it 'shows a comment button on the new side when hovering over a new line' do + @new_line.hover + expect(@old_line_number).to have_css comment_button_class + expect(@new_line_number).not_to have_css comment_button_class + end + end + + context 'when clicking a comment button' do + let(:test_note_comment) { 'this is a test note!' } + let(:note_class) { '.new-note' } + + before(:each) do + visit diffs_namespace_project_merge_request_path @project.namespace, @project, @merge_request + click_link 'Inline' + first('.diff-line-num.old_line:not(.unfold)').hover + find('.add-diff-note').click + end + + it 'shows a note form' do + expect(page).to have_css note_class + end + + it 'can be submitted and viewed' do + fill_in 'note[note]', with: :test_note_comment + click_button 'Comment' + expect(page).to have_content :test_note_comment + end + + it 'can be closed' do + find('.note-form-actions .btn-cancel').click + expect(page).not_to have_css note_class + end + end end diff --git a/spec/fixtures/domain_blacklist.txt b/spec/fixtures/domain_blacklist.txt new file mode 100644 index 00000000000..baeb11eda9a --- /dev/null +++ b/spec/fixtures/domain_blacklist.txt @@ -0,0 +1,3 @@ +example.com +test.com +foo.bar
\ No newline at end of file diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index 2ea1320267c..fb040ba82bc 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -54,23 +54,60 @@ describe ApplicationSetting, models: true do context 'restricted signup domains' do it 'set single domain' do - setting.restricted_signup_domains_raw = 'example.com' - expect(setting.restricted_signup_domains).to eq(['example.com']) + setting.domain_whitelist_raw = 'example.com' + expect(setting.domain_whitelist).to eq(['example.com']) end it 'set multiple domains with spaces' do - setting.restricted_signup_domains_raw = 'example.com *.example.com' - expect(setting.restricted_signup_domains).to eq(['example.com', '*.example.com']) + setting.domain_whitelist_raw = 'example.com *.example.com' + expect(setting.domain_whitelist).to eq(['example.com', '*.example.com']) end it 'set multiple domains with newlines and a space' do - setting.restricted_signup_domains_raw = "example.com\n *.example.com" - expect(setting.restricted_signup_domains).to eq(['example.com', '*.example.com']) + setting.domain_whitelist_raw = "example.com\n *.example.com" + expect(setting.domain_whitelist).to eq(['example.com', '*.example.com']) end it 'set multiple domains with commas' do - setting.restricted_signup_domains_raw = "example.com, *.example.com" - expect(setting.restricted_signup_domains).to eq(['example.com', '*.example.com']) + setting.domain_whitelist_raw = "example.com, *.example.com" + expect(setting.domain_whitelist).to eq(['example.com', '*.example.com']) + end + end + + context 'blacklisted signup domains' do + it 'set single domain' do + setting.domain_blacklist_raw = 'example.com' + expect(setting.domain_blacklist).to contain_exactly('example.com') + end + + it 'set multiple domains with spaces' do + setting.domain_blacklist_raw = 'example.com *.example.com' + expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com') + end + + it 'set multiple domains with newlines and a space' do + setting.domain_blacklist_raw = "example.com\n *.example.com" + expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com') + end + + it 'set multiple domains with commas' do + setting.domain_blacklist_raw = "example.com, *.example.com" + expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com') + end + + it 'set multiple domains with semicolon' do + setting.domain_blacklist_raw = "example.com; *.example.com" + expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com') + end + + it 'set multiple domains with mixture of everything' do + setting.domain_blacklist_raw = "example.com; *.example.com\n test.com\sblock.com yes.com" + expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com', 'test.com', 'block.com', 'yes.com') + end + + it 'set multiple domain with file' do + setting.domain_blacklist_file = File.open(Rails.root.join('spec/fixtures/', 'domain_blacklist.txt')) + expect(setting.domain_blacklist).to contain_exactly('example.com', 'test.com', 'foo.bar') end end end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index a3bd8fdf30b..0d4c86955ce 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -504,4 +504,42 @@ describe Ci::Pipeline, models: true do end end end + + describe '#has_warnings?' do + subject { pipeline.has_warnings? } + + context 'build which is allowed to fail fails' do + before do + create :ci_build, :success, pipeline: pipeline, name: 'rspec' + create :ci_build, :allowed_to_fail, :failed, pipeline: pipeline, name: 'rubocop' + end + + it 'returns true' do + is_expected.to be_truthy + end + end + + context 'build which is allowed to fail succeeds' do + before do + create :ci_build, :success, pipeline: pipeline, name: 'rspec' + create :ci_build, :allowed_to_fail, :success, pipeline: pipeline, name: 'rubocop' + end + + it 'returns false' do + is_expected.to be_falsey + end + end + + context 'build is retried and succeeds' do + before do + create :ci_build, :success, pipeline: pipeline, name: 'rubocop' + create :ci_build, :failed, pipeline: pipeline, name: 'rspec' + create :ci_build, :success, pipeline: pipeline, name: 'rspec' + end + + it 'returns false' do + is_expected.to be_falsey + end + end + end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 3bf82cf2668..2a5a7fb2fc6 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -89,9 +89,9 @@ describe User, models: true do end describe 'email' do - context 'when no signup domains listed' do + context 'when no signup domains whitelisted' do before do - allow_any_instance_of(ApplicationSetting).to receive(:restricted_signup_domains).and_return([]) + allow_any_instance_of(ApplicationSetting).to receive(:domain_whitelist).and_return([]) end it 'accepts any email' do @@ -100,9 +100,9 @@ describe User, models: true do end end - context 'when a signup domain is listed and subdomains are allowed' do + context 'when a signup domain is whitelisted and subdomains are allowed' do before do - allow_any_instance_of(ApplicationSetting).to receive(:restricted_signup_domains).and_return(['example.com', '*.example.com']) + allow_any_instance_of(ApplicationSetting).to receive(:domain_whitelist).and_return(['example.com', '*.example.com']) end it 'accepts info@example.com' do @@ -121,9 +121,9 @@ describe User, models: true do end end - context 'when a signup domain is listed and subdomains are not allowed' do + context 'when a signup domain is whitelisted and subdomains are not allowed' do before do - allow_any_instance_of(ApplicationSetting).to receive(:restricted_signup_domains).and_return(['example.com']) + allow_any_instance_of(ApplicationSetting).to receive(:domain_whitelist).and_return(['example.com']) end it 'accepts info@example.com' do @@ -142,6 +142,53 @@ describe User, models: true do end end + context 'domain blacklist' do + before do + allow_any_instance_of(ApplicationSetting).to receive(:domain_blacklist_enabled?).and_return(true) + allow_any_instance_of(ApplicationSetting).to receive(:domain_blacklist).and_return(['example.com']) + end + + context 'when a signup domain is blacklisted' do + it 'accepts info@test.com' do + user = build(:user, email: 'info@test.com') + expect(user).to be_valid + end + + it 'rejects info@example.com' do + user = build(:user, email: 'info@example.com') + expect(user).not_to be_valid + end + end + + context 'when a signup domain is blacklisted but a wildcard subdomain is allowed' do + before do + allow_any_instance_of(ApplicationSetting).to receive(:domain_blacklist).and_return(['test.example.com']) + allow_any_instance_of(ApplicationSetting).to receive(:domain_whitelist).and_return(['*.example.com']) + end + + it 'should give priority to whitelist and allow info@test.example.com' do + user = build(:user, email: 'info@test.example.com') + expect(user).to be_valid + end + end + + context 'with both lists containing a domain' do + before do + allow_any_instance_of(ApplicationSetting).to receive(:domain_whitelist).and_return(['test.com']) + end + + it 'accepts info@test.com' do + user = build(:user, email: 'info@test.com') + expect(user).to be_valid + end + + it 'rejects info@example.com' do + user = build(:user, email: 'info@example.com') + expect(user).not_to be_valid + end + end + end + context 'owns_notification_email' do it 'accepts temp_oauth_email emails' do user = build(:user, email: "temp-email-for-oauth@example.com") diff --git a/spec/requests/api/deploy_keys.rb b/spec/requests/api/deploy_keys.rb new file mode 100644 index 00000000000..ac42288bc34 --- /dev/null +++ b/spec/requests/api/deploy_keys.rb @@ -0,0 +1,38 @@ +require 'spec_helper' + +describe API::API, api: true do + include ApiHelpers + + let(:user) { create(:user) } + let(:project) { create(:project, creator_id: user.id) } + let!(:deploy_keys_project) { create(:deploy_keys_project, project: project) } + let(:admin) { create(:admin) } + + describe 'GET /deploy_keys' do + before { admin } + + context 'when unauthenticated' do + it 'should return authentication error' do + get api('/deploy_keys') + expect(response.status).to eq(401) + end + end + + context 'when authenticated as non-admin user' do + it 'should return a 403 error' do + get api('/deploy_keys', user) + expect(response.status).to eq(403) + end + end + + context 'when authenticated as admin' do + it 'should return all deploy keys' do + get api('/deploy_keys', admin) + expect(response.status).to eq(200) + + expect(json_response).to be_an Array + expect(json_response.first['id']).to eq(deploy_keys_project.deploy_key.id) + end + end + end +end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 152cd802839..afa0599807f 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -647,33 +647,33 @@ describe API::API, api: true do let(:deploy_keys_project) { create(:deploy_keys_project, project: project) } let(:deploy_key) { deploy_keys_project.deploy_key } - describe 'GET /projects/:id/keys' do + describe 'GET /projects/:id/deploy_keys' do before { deploy_key } it 'should return array of ssh keys' do - get api("/projects/#{project.id}/keys", user) + get api("/projects/#{project.id}/deploy_keys", user) expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['title']).to eq(deploy_key.title) end end - describe 'GET /projects/:id/keys/:key_id' do + describe 'GET /projects/:id/deploy_keys/:key_id' do it 'should return a single key' do - get api("/projects/#{project.id}/keys/#{deploy_key.id}", user) + get api("/projects/#{project.id}/deploy_keys/#{deploy_key.id}", user) expect(response).to have_http_status(200) expect(json_response['title']).to eq(deploy_key.title) end it 'should return 404 Not Found with invalid ID' do - get api("/projects/#{project.id}/keys/404", user) + get api("/projects/#{project.id}/deploy_keys/404", user) expect(response).to have_http_status(404) end end - describe 'POST /projects/:id/keys' do + describe 'POST /projects/:id/deploy_keys' do it 'should not create an invalid ssh key' do - post api("/projects/#{project.id}/keys", user), { title: 'invalid key' } + post api("/projects/#{project.id}/deploy_keys", user), { title: 'invalid key' } expect(response).to have_http_status(400) expect(json_response['message']['key']).to eq([ 'can\'t be blank', @@ -683,7 +683,7 @@ describe API::API, api: true do end it 'should not create a key without title' do - post api("/projects/#{project.id}/keys", user), key: 'some key' + post api("/projects/#{project.id}/deploy_keys", user), key: 'some key' expect(response).to have_http_status(400) expect(json_response['message']['title']).to eq([ 'can\'t be blank', @@ -694,22 +694,22 @@ describe API::API, api: true do it 'should create new ssh key' do key_attrs = attributes_for :key expect do - post api("/projects/#{project.id}/keys", user), key_attrs + post api("/projects/#{project.id}/deploy_keys", user), key_attrs end.to change{ project.deploy_keys.count }.by(1) end end - describe 'DELETE /projects/:id/keys/:key_id' do + describe 'DELETE /projects/:id/deploy_keys/:key_id' do before { deploy_key } it 'should delete existing key' do expect do - delete api("/projects/#{project.id}/keys/#{deploy_key.id}", user) + delete api("/projects/#{project.id}/deploy_keys/#{deploy_key.id}", user) end.to change{ project.deploy_keys.count }.by(-1) end it 'should return 404 Not Found with invalid ID' do - delete api("/projects/#{project.id}/keys/404", user) + delete api("/projects/#{project.id}/deploy_keys/404", user) expect(response).to have_http_status(404) end end |
