diff options
58 files changed, 840 insertions, 142 deletions
diff --git a/CHANGELOG-EE.md b/CHANGELOG-EE.md index eb43ae6a328..6e5a6e26804 100644 --- a/CHANGELOG-EE.md +++ b/CHANGELOG-EE.md @@ -203,6 +203,13 @@ Please view this file on the master branch, on stable branches it's out of date. - Fixes style-lint errors and warnings for EE builds.scss file. +## 12.2.8 + +### Fixed (1 change) + +- Geo: LFS not being synced. !17633 + + ## 12.2.7 ### Security (1 change) @@ -471,6 +478,13 @@ Please view this file on the master branch, on stable branches it's out of date. - Fix alignment of activity dropdown in epic tabs; add counter to discussion tab. +## 12.1.14 + +### Fixed (1 change) + +- Geo: LFS not being synced. !17633 + + ## 12.1.12 ### Security (4 changes) diff --git a/CHANGELOG.md b/CHANGELOG.md index da0e41c6d0e..279c6ede932 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -307,6 +307,13 @@ entry. - Updates tooltip of 'detached' label/state. +## 12.2.8 + +### Security (1 change) + +- Limit search for IID to a type to avoid leaking records with the same IID that the user does not have access to. + + ## 12.2.7 ### Security (1 change) @@ -649,6 +656,13 @@ entry. - Update Packer.gitlab-ci.yml to use latest image. (Kelly Hair) +## 12.1.14 + +### Security (1 change) + +- Limit search for IID to a type to avoid leaking records with the same IID that the user does not have access to. + + ## 12.1.12 ### Security (12 changes) diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js index cb70837f9e9..37b0215f6f9 100644 --- a/app/assets/javascripts/lib/utils/datetime_utility.js +++ b/app/assets/javascripts/lib/utils/datetime_utility.js @@ -561,3 +561,6 @@ export const getDateInPast = (date, daysInPast) => { dateClone.setTime(dateClone.getTime() - daysInPast * 24 * 60 * 60 * 1000), ).toISOString(); }; + +export const beginOfDayTime = 'T00:00:00Z'; +export const endOfDayTime = 'T23:59:59Z'; diff --git a/app/assets/javascripts/pages/registrations/new/index.js b/app/assets/javascripts/pages/registrations/new/index.js new file mode 100644 index 00000000000..a33d11f3613 --- /dev/null +++ b/app/assets/javascripts/pages/registrations/new/index.js @@ -0,0 +1,9 @@ +import LengthValidator from '~/pages/sessions/new/length_validator'; +import UsernameValidator from '~/pages/sessions/new/username_validator'; +import NoEmojiValidator from '~/emoji/no_emoji_validator'; + +document.addEventListener('DOMContentLoaded', () => { + new UsernameValidator(); // eslint-disable-line no-new + new LengthValidator(); // eslint-disable-line no-new + new NoEmojiValidator(); // eslint-disable-line no-new +}); diff --git a/app/assets/stylesheets/pages/experimental_separate_sign_up.scss b/app/assets/stylesheets/pages/experimental_separate_sign_up.scss new file mode 100644 index 00000000000..8b1ec1ced35 --- /dev/null +++ b/app/assets/stylesheets/pages/experimental_separate_sign_up.scss @@ -0,0 +1,51 @@ +.signup-page { + .page-wrap { + background-color: $gray-light; + } + + .gitlab-logo { + width: 80px; + height: 80px; + } + + .signup-box-container { + max-width: 900px; + + &.navless-container { + // overriding .devise-layout-html.navless-container to support the sticky footer + // without having a header on size xs + @include media-breakpoint-down(xs) { + padding: 65px $gl-padding; // height of footer + padding-top: $gl-padding; + } + } + } + + .signup-heading h2 { + font-weight: $gl-font-weight-bold; + + @include media-breakpoint-down(md) { + font-size: $gl-font-size-large; + } + } + + .signup-box { + background-color: $white-light; + box-shadow: 0 0 0 1px $border-color; + border-radius: $border-radius; + } + + .form-control { + &:active, + &:focus { + background-color: $white-light; + } + } + + .devise-errors { + h2 { + font-size: $gl-font-size; + color: $red-700; + } + } +} diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index d8aabecc036..7488a5b16a2 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -215,6 +215,12 @@ body { // offset height of fixed header + 1 to avoid scroll height: calc(100% - 51px); + + // offset without the header + &.navless { + height: calc(100% - 11px); + } + margin: 0; padding: 0; diff --git a/app/controllers/health_controller.rb b/app/controllers/health_controller.rb index 99840e40af1..d88ec06a18b 100644 --- a/app/controllers/health_controller.rb +++ b/app/controllers/health_controller.rb @@ -5,23 +5,21 @@ class HealthController < ActionController::Base include RequiresWhitelistedMonitoringClient def readiness - results = checks.flat_map(&:readiness) - success = results.all?(&:success) - - # disable static error pages at the gitlab-workhorse level, we want to see this error response even in production - headers["X-GitLab-Custom-Error"] = 1 unless success - - response = results.map { |result| [result.name, result.payload] }.to_h - render json: response, status: success ? :ok : :service_unavailable + render_probe(::Gitlab::HealthChecks::Probes::Readiness) end def liveness - render json: { status: 'ok' }, status: :ok + render_probe(::Gitlab::HealthChecks::Probes::Liveness) end private - def checks - ::Gitlab::HealthChecks::CHECKS + def render_probe(probe_class) + result = probe_class.new.execute + + # disable static error pages at the gitlab-workhorse level, we want to see this error response even in production + headers["X-GitLab-Custom-Error"] = 1 unless result.success? + + render json: result.json, status: result.http_status end end diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index 2e4c6a801b0..3a2975e92d6 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -6,13 +6,19 @@ class RegistrationsController < Devise::RegistrationsController include RecaptchaExperimentHelper include InvisibleCaptcha + layout :choose_layout + prepend_before_action :check_captcha, only: :create before_action :whitelist_query_limiting, only: [:destroy] before_action :ensure_terms_accepted, if: -> { action_name == 'create' && Gitlab::CurrentSettings.current_application_settings.enforce_terms? } def new - redirect_to(new_user_session_path) + if helpers.use_experimental_separate_sign_up_flow? + @resource = build_resource + else + redirect_to new_user_session_path(anchor: 'register-pane') + end end def create @@ -144,6 +150,16 @@ class RegistrationsController < Devise::RegistrationsController def stored_location_or_dashboard(user) stored_location_for(user) || dashboard_projects_path end + + # Part of an experiment to build a new sign up flow. Will be resolved + # with https://gitlab.com/gitlab-org/growth/engineering/issues/64 + def choose_layout + if helpers.use_experimental_separate_sign_up_flow? + 'devise_experimental_separate_sign_up_flow' + else + 'devise' + end + end end RegistrationsController.prepend_if_ee('EE::RegistrationsController') diff --git a/app/helpers/sessions_helper.rb b/app/helpers/sessions_helper.rb index af98a611b8b..2a5a3b9eac6 100644 --- a/app/helpers/sessions_helper.rb +++ b/app/helpers/sessions_helper.rb @@ -4,4 +4,8 @@ module SessionsHelper def unconfirmed_email? flash[:alert] == t(:unconfirmed, scope: [:devise, :failure]) end + + def use_experimental_separate_sign_up_flow? + ::Gitlab.dev_env_or_com? && Feature.enabled?(:experimental_separate_sign_up_flow) + end end diff --git a/app/models/concerns/group_api_compatibility.rb b/app/models/concerns/group_api_compatibility.rb new file mode 100644 index 00000000000..f02aa2035e5 --- /dev/null +++ b/app/models/concerns/group_api_compatibility.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +# Add methods used by the groups API +module GroupAPICompatibility + extend ActiveSupport::Concern + + def project_creation_level_str + ::Gitlab::Access.project_creation_string_options.key(project_creation_level) + end + + def project_creation_level_str=(value) + write_attribute(:project_creation_level, ::Gitlab::Access.project_creation_string_options.fetch(value)) + end + + def subgroup_creation_level_str + ::Gitlab::Access.subgroup_creation_string_options.key(subgroup_creation_level) + end + + def subgroup_creation_level_str=(value) + write_attribute(:subgroup_creation_level, ::Gitlab::Access.subgroup_creation_string_options.fetch(value)) + end +end diff --git a/app/models/group.rb b/app/models/group.rb index 1b62db04ab7..5df9d97dcb6 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -14,6 +14,7 @@ class Group < Namespace include TokenAuthenticatable include WithUploads include Gitlab::Utils::StrongMemoize + include GroupAPICompatibility ACCESS_REQUEST_APPROVERS_TO_BE_NOTIFIED_LIMIT = 10 diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 0e6059f8715..7c0220a705a 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -256,7 +256,7 @@ class Namespace < ApplicationRecord end def has_parent? - parent.present? + parent_id.present? || parent.present? end def root_ancestor diff --git a/app/models/project_services/data_fields.rb b/app/models/project_services/data_fields.rb index 46136556ade..cffb493d569 100644 --- a/app/models/project_services/data_fields.rb +++ b/app/models/project_services/data_fields.rb @@ -5,7 +5,7 @@ module DataFields class_methods do # Provide convenient accessor methods for data fields. - # TODO: Simplify as part of https://gitlab.com/gitlab-org/gitlab-foss/issues/63084 + # TODO: Simplify as part of https://gitlab.com/gitlab-org/gitlab/issues/29404 def data_field(*args) args.each do |arg| self.class_eval <<-RUBY, __FILE__, __LINE__ + 1 diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb index 278677edcdf..9e1393196ff 100644 --- a/app/models/project_services/issue_tracker_service.rb +++ b/app/models/project_services/issue_tracker_service.rb @@ -4,7 +4,7 @@ class IssueTrackerService < Service validate :one_issue_tracker, if: :activated?, on: :manual_change # TODO: we can probably just delegate as part of - # https://gitlab.com/gitlab-org/gitlab-foss/issues/63084 + # https://gitlab.com/gitlab-org/gitlab/issues/29404 data_field :project_url, :issues_url, :new_issue_url default_value_for :category, 'issue_tracker' @@ -25,7 +25,7 @@ class IssueTrackerService < Service end end - # this will be removed as part of https://gitlab.com/gitlab-org/gitlab-foss/issues/63084 + # this will be removed as part of https://gitlab.com/gitlab-org/gitlab/issues/29404 def title if title_attribute = read_attribute(:title) title_attribute @@ -36,7 +36,7 @@ class IssueTrackerService < Service end end - # this will be removed as part of https://gitlab.com/gitlab-org/gitlab-foss/issues/63084 + # this will be removed as part of https://gitlab.com/gitlab-org/gitlab/issues/29404 def description if description_attribute = read_attribute(:description) description_attribute @@ -49,7 +49,7 @@ class IssueTrackerService < Service def handle_properties # this has been moved from initialize_properties and should be improved - # as part of https://gitlab.com/gitlab-org/gitlab-foss/issues/63084 + # as part of https://gitlab.com/gitlab-org/gitlab/issues/29404 return unless properties @legacy_properties_data = properties.dup diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index cfdf55b5155..6ebec17e703 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -19,7 +19,7 @@ class JiraService < IssueTrackerService # for more information check: https://gitlab.com/gitlab-org/gitlab-foss/issues/49936. # TODO: we can probably just delegate as part of - # https://gitlab.com/gitlab-org/gitlab-foss/issues/63084 + # https://gitlab.com/gitlab-org/gitlab/issues/29404 data_field :username, :password, :url, :api_url, :jira_issue_transition_id before_update :reset_password diff --git a/app/views/admin/application_settings/_influx.html.haml b/app/views/admin/application_settings/_influx.html.haml index 98c7a9659c3..300b01c6777 100644 --- a/app/views/admin/application_settings/_influx.html.haml +++ b/app/views/admin/application_settings/_influx.html.haml @@ -7,7 +7,7 @@ in running SQL queries. These settings require a = link_to 'restart', help_page_path('administration/restart_gitlab') to take effect. - = link_to icon('question-circle'), help_page_path('administration/monitoring/performance/introduction') + = link_to icon('question-circle'), help_page_path('administration/monitoring/performance/index') .form-group .form-check = f.check_box :metrics_enabled, class: 'form-check-input' diff --git a/app/views/devise/registrations/new.html.haml b/app/views/devise/registrations/new.html.haml index 42cfbbf84f2..dfdf7429dc5 100644 --- a/app/views/devise/registrations/new.html.haml +++ b/app/views/devise/registrations/new.html.haml @@ -1,4 +1,7 @@ - page_title "Sign up" -= render 'devise/shared/signup_box' +- if use_experimental_separate_sign_up_flow? + = render 'devise/shared/experimental_separate_sign_up_flow_box' +- else + = render 'devise/shared/signup_box' = render 'devise/shared/sign_in_link' diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml index 30ed7ed6b29..17e5da501f5 100644 --- a/app/views/devise/sessions/new.html.haml +++ b/app/views/devise/sessions/new.html.haml @@ -4,7 +4,8 @@ - if form_based_providers.any? = render 'devise/shared/tabs_ldap' - else - = render 'devise/shared/tabs_normal' + - unless use_experimental_separate_sign_up_flow? + = render 'devise/shared/tabs_normal' .tab-content - if password_authentication_enabled_for_web? || ldap_enabled? || crowd_enabled? = render 'devise/shared/signin_box' diff --git a/app/views/devise/shared/_experimental_separate_sign_up_flow_box.html.haml b/app/views/devise/shared/_experimental_separate_sign_up_flow_box.html.haml new file mode 100644 index 00000000000..f92c29da5e6 --- /dev/null +++ b/app/views/devise/shared/_experimental_separate_sign_up_flow_box.html.haml @@ -0,0 +1,34 @@ +- max_name_length = 128 +- max_username_length = 255 +.signup-box.p-3.mb-2 + .signup-body + = form_for(resource, as: "new_#{resource_name}", url: registration_path(resource_name), html: { class: "new_new_user gl-show-field-errors", "aria-live" => "assertive" }) do |f| + .devise-errors.mt-0 + = render "devise/shared/error_messages", resource: resource + = invisible_captcha + .name.form-group + = f.label :name, _('Full name'), class: 'label-bold' + = f.text_field :name, class: "form-control top js-block-emoji js-validate-length", :data => { :max_length => max_name_length, :max_length_message => s_("SignUp|Name is too long (maximum is %{max_length} characters).") % { max_length: max_name_length }, :qa_selector => 'new_user_name_field' }, required: true, title: _("This field is required.") + .username.form-group + = f.label :username, class: 'label-bold' + = f.text_field :username, class: "form-control middle js-block-emoji js-validate-length js-validate-username", :data => { :max_length => max_username_length, :max_length_message => s_("SignUp|Username is too long (maximum is %{max_length} characters).") % { max_length: max_username_length }, :qa_selector => 'new_user_username_field' }, pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, required: true, title: _("Please create a username with only alphanumeric characters.") + %p.validation-error.gl-field-error-ignore.field-validation.mt-1.hide.cred= _('Username is already taken.') + %p.validation-success.gl-field-error-ignore.field-validation.mt-1.hide.cgreen= _('Username is available.') + %p.validation-pending.gl-field-error-ignore.field-validation.mt-1.hide= _('Checking username availability...') + .form-group + = f.label :email, class: 'label-bold' + = f.email_field :email, class: "form-control middle", data: { qa_selector: 'new_user_email_field' }, required: true, title: _("Please provide a valid email address.") + .form-group.append-bottom-20#password-strength + = f.label :password, class: 'label-bold' + = f.password_field :password, class: "form-control bottom", data: { qa_selector: 'new_user_password_field' }, required: true, pattern: ".{#{@minimum_password_length},}", title: _("Minimum length is %{minimum_password_length} characters.") % { minimum_password_length: @minimum_password_length } + %p.gl-field-hint.text-secondary= _('Minimum length is %{minimum_password_length} characters') % { minimum_password_length: @minimum_password_length } + - if Gitlab::CurrentSettings.current_application_settings.enforce_terms? + .form-group + = check_box_tag :terms_opt_in, '1', false, required: true, data: { qa_selector: 'new_user_accept_terms_checkbox' } + = label_tag :terms_opt_in do + - terms_link = link_to s_("I accept the|Terms of Service and Privacy Policy"), terms_path, target: "_blank" + - accept_terms_label = _("I accept the %{terms_link}") % { terms_link: terms_link } + = accept_terms_label.html_safe + = render_if_exists 'devise/shared/email_opted_in', f: f + .submit-container.mt-3 + = f.submit _("Register"), class: "btn-register btn btn-block btn-success mb-0 p-2", data: { qa_selector: 'new_user_register_button' } diff --git a/app/views/devise/shared/_sign_in_link.html.haml b/app/views/devise/shared/_sign_in_link.html.haml index 77ef103cc47..9a7d8a0a160 100644 --- a/app/views/devise/shared/_sign_in_link.html.haml +++ b/app/views/devise/shared/_sign_in_link.html.haml @@ -1,4 +1,4 @@ -%p +%p.text-center %span.light Already have login and password? = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes') diff --git a/app/views/devise/shared/_signin_box.html.haml b/app/views/devise/shared/_signin_box.html.haml index f8f36a8bfff..deaceccbfc7 100644 --- a/app/views/devise/shared/_signin_box.html.haml +++ b/app/views/devise/shared/_signin_box.html.haml @@ -22,3 +22,8 @@ .login-box.tab-pane.active{ id: 'login-pane', role: 'tabpanel' } .login-body = render 'devise/sessions/new_base' + +- if use_experimental_separate_sign_up_flow? + %p.light.mt-2 + = _("Don't have an account yet?") + = link_to _("Register now"), new_registration_path(:user) diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index 68abfd3f61f..1efd8647a67 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -1,5 +1,17 @@ - page_description brand_title unless page_description +-# Needs a redirect on the client side since it's using an anchor to distuingish +-# between sign in and registration. We need to inline the JS to not render +-# anything from this page beforehand. +-# Part of an experiment to build a new sign up flow. Will be removed again with +-# https://gitlab.com/gitlab-org/growth/engineering/issues/64 +- if use_experimental_separate_sign_up_flow? && current_path?("sessions#new") + = javascript_tag nonce: true do + :plain + if (window.location.hash === '#register-pane') { + window.location.replace("/users/sign_up") + } + - site_name = "GitLab" %head{ prefix: "og: http://ogp.me/ns#" } %meta{ charset: "utf-8" } diff --git a/app/views/layouts/devise_experimental_separate_sign_up_flow.html.haml b/app/views/layouts/devise_experimental_separate_sign_up_flow.html.haml new file mode 100644 index 00000000000..b10145b62af --- /dev/null +++ b/app/views/layouts/devise_experimental_separate_sign_up_flow.html.haml @@ -0,0 +1,25 @@ +!!! 5 +%html.devise-layout-html.navless{ class: system_message_class } + = render "layouts/head" + %body.ui-indigo.signup-page.application.navless{ class: "#{client_class_list}", data: { page: body_data_page, qa_selector: 'signup_page' } } + = header_message + = render "layouts/init_client_detection_flags" + .page-wrap + .container.signup-box-container.navless-container.mt-0 + = render "layouts/broadcast" + .content + = render "layouts/flash" + .row.mb-3 + .col-sm-8.offset-sm-2.col-md-6.offset-md-3.new-session-forms-container + = render_if_exists 'layouts/devise_help_text' + .text-center.signup-heading.mt-3.mb-3 + = image_tag(image_url('logo.svg'), class: 'gitlab-logo', alt: 'GitLab Logo') + %h2= _('Register for GitLab.com') + = yield + %hr.footer-fixed + .footer-container + .container + .footer-links + = link_to _("Help"), help_path + = link_to _("About GitLab"), "https://about.gitlab.com/" + = footer_message diff --git a/changelogs/unreleased/16482-split-sign-in-and-sign-up.yml b/changelogs/unreleased/16482-split-sign-in-and-sign-up.yml new file mode 100644 index 00000000000..bb3c01385d8 --- /dev/null +++ b/changelogs/unreleased/16482-split-sign-in-and-sign-up.yml @@ -0,0 +1,5 @@ +--- +title: Experimental separate sign up flow +merge_request: 16482 +author: +type: other diff --git a/changelogs/unreleased/add-health-checks-exporter.yml b/changelogs/unreleased/add-health-checks-exporter.yml new file mode 100644 index 00000000000..7ffce51b82c --- /dev/null +++ b/changelogs/unreleased/add-health-checks-exporter.yml @@ -0,0 +1,5 @@ +--- +title: Export liveness and readiness probes +merge_request: +author: +type: changed diff --git a/changelogs/unreleased/fix-moved-help-doc-administration-monitoring-performance.yml b/changelogs/unreleased/fix-moved-help-doc-administration-monitoring-performance.yml new file mode 100644 index 00000000000..2fe34855ee8 --- /dev/null +++ b/changelogs/unreleased/fix-moved-help-doc-administration-monitoring-performance.yml @@ -0,0 +1,5 @@ +--- +title: Fix moved help URL for monitoring performance +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/groups_api.yml b/changelogs/unreleased/groups_api.yml new file mode 100644 index 00000000000..09e85c45811 --- /dev/null +++ b/changelogs/unreleased/groups_api.yml @@ -0,0 +1,5 @@ +--- +title: 'API: Add missing group parameters' +merge_request: 17220 +author: Mathieu Parent +type: added diff --git a/changelogs/unreleased/security-search-by-iid-leaks-data.yml b/changelogs/unreleased/security-search-by-iid-leaks-data.yml new file mode 100644 index 00000000000..cf68fe504de --- /dev/null +++ b/changelogs/unreleased/security-search-by-iid-leaks-data.yml @@ -0,0 +1,6 @@ +--- +title: Limit search for IID to a type to avoid leaking records with the same IID that + the user does not have access to +merge_request: +author: +type: security diff --git a/doc/api/groups.md b/doc/api/groups.md index b0d60d58049..312bd04e24c 100644 --- a/doc/api/groups.md +++ b/doc/api/groups.md @@ -31,6 +31,13 @@ GET /groups "path": "foo-bar", "description": "An interesting group", "visibility": "public", + "share_with_group_lock": false, + "require_two_factor_authentication": false, + "two_factor_grace_period": 48, + "project_creation_level": "developer", + "auto_devops_enabled": null, + "subgroup_creation_level": "owner", + "emails_disabled": null, "lfs_enabled": true, "avatar_url": "http://localhost:3000/uploads/group/avatar/1/foo.jpg", "web_url": "http://localhost:3000/groups/foo-bar", @@ -57,6 +64,13 @@ GET /groups?statistics=true "path": "foo-bar", "description": "An interesting group", "visibility": "public", + "share_with_group_lock": false, + "require_two_factor_authentication": false, + "two_factor_grace_period": 48, + "project_creation_level": "developer", + "auto_devops_enabled": null, + "subgroup_creation_level": "owner", + "emails_disabled": null, "lfs_enabled": true, "avatar_url": "http://localhost:3000/uploads/group/avatar/1/foo.jpg", "web_url": "http://localhost:3000/groups/foo-bar", @@ -119,6 +133,13 @@ GET /groups/:id/subgroups "path": "foo-bar", "description": "An interesting group", "visibility": "public", + "share_with_group_lock": false, + "require_two_factor_authentication": false, + "two_factor_grace_period": 48, + "project_creation_level": "developer", + "auto_devops_enabled": null, + "subgroup_creation_level": "owner", + "emails_disabled": null, "lfs_enabled": true, "avatar_url": "http://gitlab.example.com/uploads/group/avatar/1/foo.jpg", "web_url": "http://gitlab.example.com/groups/foo-bar", @@ -434,6 +455,13 @@ Parameters: | `path` | string | yes | The path of the group. | | `description` | string | no | The group's description. | | `visibility` | string | no | The group's visibility. Can be `private`, `internal`, or `public`. | +| `share_with_group_lock` | boolean | no | Prevent sharing a project with another group within this group. | +| `require_two_factor_authentication` | boolean | no | Require all users in this group to setup Two-factor authentication. | +| `two_factor_grace_period` | integer | no | Time before Two-factor authentication is enforced (in hours). | +| `project_creation_level` | string | no | Determine if developers can create projects in the group. Can be `noone` (No one), `maintainer` (Maintainers), or `developer` (Developers + Maintainers). | +| `auto_devops_enabled` | boolean | no | Default to Auto DevOps pipeline for all projects within this group. | +| `subgroup_creation_level` | integer | no | Allowed to create subgroups. Can be `owner` (Owners), or `maintainer` (Maintainers). | +| `emails_disabled` | boolean | no | Disable email notifications | | `lfs_enabled` | boolean | no | Enable/disable Large File Storage (LFS) for the projects in this group. | | `request_access_enabled` | boolean | no | Allow users to request member access. | | `parent_id` | integer | no | The parent group ID for creating nested group. | @@ -472,6 +500,13 @@ PUT /groups/:id | `membership_lock` | boolean | no | **(STARTER)** Prevent adding new members to project membership within this group. | | `share_with_group_lock` | boolean | no | Prevent sharing a project with another group within this group. | | `visibility` | string | no | The visibility level of the group. Can be `private`, `internal`, or `public`. | +| `share_with_group_lock` | boolean | no | Prevent sharing a project with another group within this group. | +| `require_two_factor_authentication` | boolean | no | Require all users in this group to setup Two-factor authentication. | +| `two_factor_grace_period` | integer | no | Time before Two-factor authentication is enforced (in hours). | +| `project_creation_level` | string | no | Determine if developers can create projects in the group. Can be `noone` (No one), `maintainer` (Maintainers), or `developer` (Developers + Maintainers). | +| `auto_devops_enabled` | boolean | no | Default to Auto DevOps pipeline for all projects within this group. | +| `subgroup_creation_level` | integer | no | Allowed to create subgroups. Can be `owner` (Owners), or `maintainer` (Maintainers). | +| `emails_disabled` | boolean | no | Disable email notifications | | `lfs_enabled` (optional) | boolean | no | Enable/disable Large File Storage (LFS) for the projects in this group. | | `request_access_enabled` | boolean | no | Allow users to request member access. | | `file_template_project_id` | integer | no | **(PREMIUM)** The ID of a project to load custom file templates from. | diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 16cc20e95c5..6dd2e171d77 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -378,6 +378,13 @@ module API class Group < BasicGroupDetails expose :path, :description, :visibility + expose :share_with_group_lock + expose :require_two_factor_authentication + expose :two_factor_grace_period + expose :project_creation_level_str, as: :project_creation_level + expose :auto_devops_enabled + expose :subgroup_creation_level_str, as: :subgroup_creation_level + expose :emails_disabled expose :lfs_enabled?, as: :lfs_enabled expose :avatar_url do |group, options| group.avatar_url(only_path: false) diff --git a/lib/api/helpers/groups_helpers.rb b/lib/api/helpers/groups_helpers.rb index abe9d457a5b..2cc18acb7ec 100644 --- a/lib/api/helpers/groups_helpers.rb +++ b/lib/api/helpers/groups_helpers.rb @@ -11,9 +11,15 @@ module API optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The visibility of the group' + optional :share_with_group_lock, type: Boolean, desc: 'Prevent sharing a project with another group within this group' + optional :require_two_factor_authentication, type: Boolean, desc: 'Require all users in this group to setup Two-factor authentication' + optional :two_factor_grace_period, type: Integer, desc: 'Time before Two-factor authentication is enforced' + optional :project_creation_level, type: String, values: ::Gitlab::Access.project_creation_string_values, desc: 'Determine if developers can create projects in the group', as: :project_creation_level_str + optional :auto_devops_enabled, type: Boolean, desc: 'Default to Auto DevOps pipeline for all projects within this group' + optional :subgroup_creation_level, type: String, values: ::Gitlab::Access.subgroup_creation_string_values, desc: 'Allowed to create subgroups', as: :subgroup_creation_level_str + optional :emails_disabled, type: Boolean, desc: 'Disable email notifications' optional :lfs_enabled, type: Boolean, desc: 'Enable/disable LFS for the projects in this group' optional :request_access_enabled, type: Boolean, desc: 'Allow users to request member access' - optional :share_with_group_lock, type: Boolean, desc: 'Prevent sharing a project with another group within this group' end params :optional_params_ee do diff --git a/lib/gitlab/access.rb b/lib/gitlab/access.rb index ed5816482a9..6492ccc286a 100644 --- a/lib/gitlab/access.rb +++ b/lib/gitlab/access.rb @@ -103,10 +103,22 @@ module Gitlab } end + def project_creation_string_options + { + 'noone' => NO_ONE_PROJECT_ACCESS, + 'maintainer' => MAINTAINER_PROJECT_ACCESS, + 'developer' => DEVELOPER_MAINTAINER_PROJECT_ACCESS + } + end + def project_creation_values project_creation_options.values end + def project_creation_string_values + project_creation_string_options.keys + end + def project_creation_level_name(name) project_creation_options.key(name) end @@ -117,6 +129,21 @@ module Gitlab s_('SubgroupCreationlevel|Maintainers') => MAINTAINER_SUBGROUP_ACCESS } end + + def subgroup_creation_string_options + { + 'owner' => OWNER_SUBGROUP_ACCESS, + 'maintainer' => MAINTAINER_SUBGROUP_ACCESS + } + end + + def subgroup_creation_values + subgroup_creation_options.values + end + + def subgroup_creation_string_values + subgroup_creation_string_options.keys + end end def human_access diff --git a/lib/gitlab/health_checks/probes/liveness.rb b/lib/gitlab/health_checks/probes/liveness.rb new file mode 100644 index 00000000000..b4d346e945e --- /dev/null +++ b/lib/gitlab/health_checks/probes/liveness.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Gitlab + module HealthChecks + module Probes + class Liveness + def execute + Probes::Status.new(200, status: 'ok') + end + end + end + end +end diff --git a/lib/gitlab/health_checks/probes/readiness.rb b/lib/gitlab/health_checks/probes/readiness.rb new file mode 100644 index 00000000000..b789cbe1ae6 --- /dev/null +++ b/lib/gitlab/health_checks/probes/readiness.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +module Gitlab + module HealthChecks + module Probes + class Readiness + attr_reader :checks + + # This accepts an array of Proc + # that returns `::Gitlab::HealthChecks::Result` + def initialize(*additional_checks) + @checks = ::Gitlab::HealthChecks::CHECKS.map { |check| check.method(:readiness) } + @checks += additional_checks + end + + def execute + readiness = probe_readiness + success = all_succeeded?(readiness) + + Probes::Status.new( + success ? 200 : 503, + status(success).merge(payload(readiness)) + ) + end + + private + + def all_succeeded?(readiness) + readiness.all? do |name, probes| + probes.any?(&:success) + end + end + + def status(success) + { status: success ? 'ok' : 'failed' } + end + + def payload(readiness) + readiness.transform_values do |probes| + probes.map(&:payload) + end + end + + def probe_readiness + checks + .flat_map(&:call) + .compact + .group_by(&:name) + end + end + end + end +end diff --git a/lib/gitlab/health_checks/probes/status.rb b/lib/gitlab/health_checks/probes/status.rb new file mode 100644 index 00000000000..192e9366001 --- /dev/null +++ b/lib/gitlab/health_checks/probes/status.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module Gitlab + module HealthChecks + module Probes + Status = Struct.new(:http_status, :json) do + # We accept 2xx + def success? + http_status / 100 == 2 + end + end + end + end +end diff --git a/lib/gitlab/metrics/exporter/base_exporter.rb b/lib/gitlab/metrics/exporter/base_exporter.rb index de7870dfb8c..b56770e224b 100644 --- a/lib/gitlab/metrics/exporter/base_exporter.rb +++ b/lib/gitlab/metrics/exporter/base_exporter.rb @@ -31,7 +31,15 @@ module Gitlab @server = ::WEBrick::HTTPServer.new( Port: settings.port, BindAddress: settings.address, Logger: logger, AccessLog: access_log) - server.mount "/", Rack::Handler::WEBrick, rack_app + server.mount_proc '/readiness' do |req, res| + render_probe( + ::Gitlab::HealthChecks::Probes::Readiness.new, req, res) + end + server.mount_proc '/liveness' do |req, res| + render_probe( + ::Gitlab::HealthChecks::Probes::Liveness.new, req, res) + end + server.mount '/', Rack::Handler::WEBrick, rack_app server.start end @@ -51,6 +59,14 @@ module Gitlab run -> (env) { [404, {}, ['']] } end end + + def render_probe(probe, req, res) + result = probe.execute + + res.status = result.http_status + res.content_type = 'application/json; charset=utf-8' + res.body = result.json.to_json + end end end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 28e95173006..e40a1c2a4ea 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -5472,6 +5472,9 @@ msgstr "" msgid "Domain verification is an essential security measure for public GitLab sites. Users are required to demonstrate they control a domain before it is enabled" msgstr "" +msgid "Don't have an account yet?" +msgstr "" + msgid "Don't paste the private part of the GPG key. Paste the public part which begins with '-----BEGIN PGP PUBLIC KEY BLOCK-----'." msgstr "" @@ -13081,6 +13084,12 @@ msgstr "" msgid "Register and see your runners for this project." msgstr "" +msgid "Register for GitLab.com" +msgstr "" + +msgid "Register now" +msgstr "" + msgid "Register with two-factor app" msgstr "" @@ -259,6 +259,7 @@ module QA module Milestone autoload :New, 'qa/page/project/milestone/new' autoload :Index, 'qa/page/project/milestone/index' + autoload :Show, 'qa/page/project/milestone/show' end module Operations @@ -449,6 +450,7 @@ module QA autoload :Logging, 'qa/support/page/logging' end autoload :Api, 'qa/support/api' + autoload :Dates, 'qa/support/dates' autoload :Waiter, 'qa/support/waiter' autoload :Retrier, 'qa/support/retrier' end diff --git a/qa/qa/page/project/milestone/index.rb b/qa/qa/page/project/milestone/index.rb index 8ad7689ce70..6895c44f72f 100644 --- a/qa/qa/page/project/milestone/index.rb +++ b/qa/qa/page/project/milestone/index.rb @@ -17,5 +17,3 @@ module QA end end end - -QA::Page::Project::Milestone::Index.prepend_if_ee('QA::EE::Page::Project::Milestone::Index') diff --git a/qa/qa/page/project/milestone/show.rb b/qa/qa/page/project/milestone/show.rb new file mode 100644 index 00000000000..a6ad76cb33b --- /dev/null +++ b/qa/qa/page/project/milestone/show.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module QA + module Page + module Project + module Milestone + class Show < Page::Base + end + end + end + end +end + +QA::Page::Project::Milestone::Show.prepend_if_ee('QA::EE::Page::Project::Milestone::Show') diff --git a/qa/qa/support/dates.rb b/qa/qa/support/dates.rb new file mode 100644 index 00000000000..47fc721afc1 --- /dev/null +++ b/qa/qa/support/dates.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module QA + module Support + module Dates + def current_date_yyyy_mm_dd + current_date.strftime("%Y/%m/%d") + end + + def next_month_yyyy_mm_dd + current_date.next_month.strftime("%Y/%m/%d") + end + + private + + def current_date + DateTime.now + end + end + end +end diff --git a/spec/controllers/health_controller_spec.rb b/spec/controllers/health_controller_spec.rb index dd6aac4b126..8a2291bccd7 100644 --- a/spec/controllers/health_controller_spec.rb +++ b/spec/controllers/health_controller_spec.rb @@ -24,11 +24,12 @@ describe HealthController do it 'responds with readiness checks data' do subject - expect(json_response['db_check']['status']).to eq('ok') - expect(json_response['cache_check']['status']).to eq('ok') - expect(json_response['queues_check']['status']).to eq('ok') - expect(json_response['shared_state_check']['status']).to eq('ok') - expect(json_response['gitaly_check']['status']).to eq('ok') + expect(json_response['db_check']).to contain_exactly({ 'status' => 'ok' }) + expect(json_response['cache_check']).to contain_exactly({ 'status' => 'ok' }) + expect(json_response['queues_check']).to contain_exactly({ 'status' => 'ok' }) + expect(json_response['shared_state_check']).to contain_exactly({ 'status' => 'ok' }) + expect(json_response['gitaly_check']).to contain_exactly( + { 'status' => 'ok', 'labels' => { 'shard' => 'default' } }) end it 'responds with readiness checks data when a failure happens' do @@ -37,9 +38,9 @@ describe HealthController do subject - expect(json_response['redis_check']['status']).to eq('failed') - expect(json_response['redis_check']['message']).to eq('check error') - expect(json_response['cache_check']['status']).to eq('ok') + expect(json_response['cache_check']).to contain_exactly({ 'status' => 'ok' }) + expect(json_response['redis_check']).to contain_exactly( + { 'status' => 'failed', 'message' => 'check error' }) expect(response.status).to eq(503) expect(response.headers['X-GitLab-Custom-Error']).to eq(1) @@ -90,7 +91,7 @@ describe HealthController do it 'responds with liveness checks data' do subject - expect(json_response['status']).to eq('ok') + expect(json_response).to eq('status' => 'ok') end end diff --git a/spec/features/users/signup_spec.rb b/spec/features/users/signup_spec.rb index fb927a9ca3b..0846ec8dfb4 100644 --- a/spec/features/users/signup_spec.rb +++ b/spec/features/users/signup_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -describe 'Signup' do +shared_examples 'Signup' do include TermsHelper before do @@ -13,8 +13,7 @@ describe 'Signup' do describe 'username validation', :js do before do - visit root_path - click_link 'Register' + visit new_user_registration_path end it 'does not show an error border if the username is available' do @@ -130,8 +129,7 @@ describe 'Signup' do describe 'user\'s full name validation', :js do before do - visit root_path - click_link 'Register' + visit new_user_registration_path end it 'does not show an error border if the user\'s fullname length is not longer than 128 characters' do @@ -177,13 +175,17 @@ describe 'Signup' do end it 'creates the user account and sends a confirmation email' do - visit root_path + visit new_user_registration_path - fill_in 'new_user_name', with: new_user.name - fill_in 'new_user_username', with: new_user.username - fill_in 'new_user_email', with: new_user.email - fill_in 'new_user_email_confirmation', with: new_user.email - fill_in 'new_user_password', with: new_user.password + fill_in 'new_user_name', with: new_user.name + fill_in 'new_user_username', with: new_user.username + fill_in 'new_user_email', with: new_user.email + + unless Feature.enabled?(:experimental_separate_sign_up_flow) + fill_in 'new_user_email_confirmation', with: new_user.email + end + + fill_in 'new_user_password', with: new_user.password expect { click_button 'Register' }.to change { User.count }.by(1) @@ -198,13 +200,17 @@ describe 'Signup' do end it 'creates the user account and sends a confirmation email' do - visit root_path + visit new_user_registration_path + + fill_in 'new_user_name', with: new_user.name + fill_in 'new_user_username', with: new_user.username + fill_in 'new_user_email', with: new_user.email + + unless Feature.enabled?(:experimental_separate_sign_up_flow) + fill_in 'new_user_email_confirmation', with: new_user.email + end - fill_in 'new_user_name', with: new_user.name - fill_in 'new_user_username', with: new_user.username - fill_in 'new_user_email', with: new_user.email - fill_in 'new_user_email_confirmation', with: new_user.email - fill_in 'new_user_password', with: new_user.password + fill_in 'new_user_password', with: new_user.password expect { click_button 'Register' }.to change { User.count }.by(1) @@ -216,13 +222,17 @@ describe 'Signup' do context "when sigining up with different cased emails" do it "creates the user successfully" do - visit root_path + visit new_user_registration_path - fill_in 'new_user_name', with: new_user.name - fill_in 'new_user_username', with: new_user.username - fill_in 'new_user_email', with: new_user.email - fill_in 'new_user_email_confirmation', with: new_user.email.capitalize - fill_in 'new_user_password', with: new_user.password + fill_in 'new_user_name', with: new_user.name + fill_in 'new_user_username', with: new_user.username + fill_in 'new_user_email', with: new_user.email + + unless Feature.enabled?(:experimental_separate_sign_up_flow) + fill_in 'new_user_email_confirmation', with: new_user.email.capitalize + end + + fill_in 'new_user_password', with: new_user.password click_button "Register" expect(current_path).to eq dashboard_projects_path @@ -236,13 +246,17 @@ describe 'Signup' do end it 'creates the user account and goes to dashboard' do - visit root_path + visit new_user_registration_path + + fill_in 'new_user_name', with: new_user.name + fill_in 'new_user_username', with: new_user.username + fill_in 'new_user_email', with: new_user.email - fill_in 'new_user_name', with: new_user.name - fill_in 'new_user_username', with: new_user.username - fill_in 'new_user_email', with: new_user.email - fill_in 'new_user_email_confirmation', with: new_user.email - fill_in 'new_user_password', with: new_user.password + unless Feature.enabled?(:experimental_separate_sign_up_flow) + fill_in 'new_user_email_confirmation', with: new_user.email + end + + fill_in 'new_user_password', with: new_user.password click_button "Register" expect(current_path).to eq dashboard_projects_path @@ -255,28 +269,34 @@ describe 'Signup' do it "displays the errors" do existing_user = create(:user) - visit root_path + visit new_user_registration_path - fill_in 'new_user_name', with: new_user.name + fill_in 'new_user_name', with: new_user.name fill_in 'new_user_username', with: new_user.username - fill_in 'new_user_email', with: existing_user.email + fill_in 'new_user_email', with: existing_user.email fill_in 'new_user_password', with: new_user.password click_button "Register" expect(current_path).to eq user_registration_path - expect(page).to have_content("errors prohibited this user from being saved") - expect(page).to have_content("Email has already been taken") - expect(page).to have_content("Email confirmation doesn't match") + + if Feature.enabled?(:experimental_separate_sign_up_flow) + expect(page).to have_content("error prohibited this user from being saved") + expect(page).to have_content("Email has already been taken") + else + expect(page).to have_content("errors prohibited this user from being saved") + expect(page).to have_content("Email has already been taken") + expect(page).to have_content("Email confirmation doesn't match") + end end it 'does not redisplay the password' do existing_user = create(:user) - visit root_path + visit new_user_registration_path - fill_in 'new_user_name', with: new_user.name + fill_in 'new_user_name', with: new_user.name fill_in 'new_user_username', with: new_user.username - fill_in 'new_user_email', with: existing_user.email + fill_in 'new_user_email', with: existing_user.email fill_in 'new_user_password', with: new_user.password click_button "Register" @@ -291,13 +311,17 @@ describe 'Signup' do end it 'requires the user to check the checkbox' do - visit root_path + visit new_user_registration_path + + fill_in 'new_user_name', with: new_user.name + fill_in 'new_user_username', with: new_user.username + fill_in 'new_user_email', with: new_user.email + + unless Feature.enabled?(:experimental_separate_sign_up_flow) + fill_in 'new_user_email_confirmation', with: new_user.email + end - fill_in 'new_user_name', with: new_user.name - fill_in 'new_user_username', with: new_user.username - fill_in 'new_user_email', with: new_user.email - fill_in 'new_user_email_confirmation', with: new_user.email - fill_in 'new_user_password', with: new_user.password + fill_in 'new_user_password', with: new_user.password click_button 'Register' @@ -306,13 +330,17 @@ describe 'Signup' do end it 'asks the user to accept terms before going to the dashboard' do - visit root_path + visit new_user_registration_path + + fill_in 'new_user_name', with: new_user.name + fill_in 'new_user_username', with: new_user.username + fill_in 'new_user_email', with: new_user.email + + unless Feature.enabled?(:experimental_separate_sign_up_flow) + fill_in 'new_user_email_confirmation', with: new_user.email + end - fill_in 'new_user_name', with: new_user.name - fill_in 'new_user_username', with: new_user.username - fill_in 'new_user_email', with: new_user.email - fill_in 'new_user_email_confirmation', with: new_user.email - fill_in 'new_user_password', with: new_user.password + fill_in 'new_user_password', with: new_user.password check :terms_opt_in click_button "Register" @@ -321,3 +349,20 @@ describe 'Signup' do end end end + +describe 'With original flow' do + it_behaves_like 'Signup' do + before do + stub_feature_flags(experimental_separate_sign_up_flow: false) + end + end +end + +describe 'With experimental flow on GitLab.com' do + it_behaves_like 'Signup' do + before do + expect(Gitlab).to receive(:com?).and_return(true).at_least(:once) + stub_feature_flags(experimental_separate_sign_up_flow: true) + end + end +end diff --git a/spec/lib/gitlab/health_checks/probes/liveness_spec.rb b/spec/lib/gitlab/health_checks/probes/liveness_spec.rb new file mode 100644 index 00000000000..91066cb8ba0 --- /dev/null +++ b/spec/lib/gitlab/health_checks/probes/liveness_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::HealthChecks::Probes::Liveness do + let(:liveness) { described_class.new } + + describe '#call' do + subject { liveness.execute } + + it 'responds with liveness checks data' do + expect(subject.http_status).to eq(200) + + expect(subject.json[:status]).to eq('ok') + end + end +end diff --git a/spec/lib/gitlab/health_checks/probes/readiness_spec.rb b/spec/lib/gitlab/health_checks/probes/readiness_spec.rb new file mode 100644 index 00000000000..d88ffd984c2 --- /dev/null +++ b/spec/lib/gitlab/health_checks/probes/readiness_spec.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::HealthChecks::Probes::Readiness do + let(:readiness) { described_class.new } + + describe '#call' do + subject { readiness.execute } + + it 'responds with readiness checks data' do + expect(subject.http_status).to eq(200) + + expect(subject.json[:status]).to eq('ok') + expect(subject.json['db_check']).to contain_exactly(status: 'ok') + expect(subject.json['cache_check']).to contain_exactly(status: 'ok') + expect(subject.json['queues_check']).to contain_exactly(status: 'ok') + expect(subject.json['shared_state_check']).to contain_exactly(status: 'ok') + expect(subject.json['gitaly_check']).to contain_exactly( + status: 'ok', labels: { shard: 'default' }) + end + + context 'when Redis fails' do + before do + allow(Gitlab::HealthChecks::Redis::RedisCheck).to receive(:readiness).and_return( + Gitlab::HealthChecks::Result.new('redis_check', false, "check error")) + end + + it 'responds with failure' do + expect(subject.http_status).to eq(503) + + expect(subject.json[:status]).to eq('failed') + expect(subject.json['cache_check']).to contain_exactly(status: 'ok') + expect(subject.json['redis_check']).to contain_exactly( + status: 'failed', message: 'check error') + end + end + end +end diff --git a/spec/lib/gitlab/metrics/exporter/base_exporter_spec.rb b/spec/lib/gitlab/metrics/exporter/base_exporter_spec.rb index 5fd7438d28a..bedf4fedcfa 100644 --- a/spec/lib/gitlab/metrics/exporter/base_exporter_spec.rb +++ b/spec/lib/gitlab/metrics/exporter/base_exporter_spec.rb @@ -4,35 +4,42 @@ require 'spec_helper' describe Gitlab::Metrics::Exporter::BaseExporter do let(:exporter) { described_class.new } - let(:server) { double('server') } - let(:socket) { double('socket') } let(:log_filename) { File.join(Rails.root, 'log', 'sidekiq_exporter.log') } let(:settings) { double('settings') } before do - allow(::WEBrick::HTTPServer).to receive(:new).and_return(server) - allow(server).to receive(:mount) - allow(server).to receive(:start) - allow(server).to receive(:shutdown) - allow(server).to receive(:listeners) { [socket] } - allow(socket).to receive(:close) allow_any_instance_of(described_class).to receive(:log_filename).and_return(log_filename) allow_any_instance_of(described_class).to receive(:settings).and_return(settings) end describe 'when exporter is enabled' do before do + allow(::WEBrick::HTTPServer).to receive(:new).with( + Port: anything, + BindAddress: anything, + Logger: anything, + AccessLog: anything + ).and_wrap_original do |m, *args| + m.call(DoNotListen: true, Logger: args.first[:Logger]) + end + + allow_any_instance_of(::WEBrick::HTTPServer).to receive(:start) + allow(settings).to receive(:enabled).and_return(true) - allow(settings).to receive(:port).and_return(3707) + allow(settings).to receive(:port).and_return(8082) allow(settings).to receive(:address).and_return('localhost') end + after do + exporter.stop + end + describe 'when exporter is stopped' do describe '#start' do it 'starts the exporter' do - expect { exporter.start.join }.to change { exporter.thread? }.from(false).to(true) + expect_any_instance_of(::WEBrick::HTTPServer).to receive(:start) - expect(server).to have_received(:start) + expect { exporter.start.join }.to change { exporter.thread? }.from(false).to(true) end describe 'with custom settings' do @@ -45,23 +52,25 @@ describe Gitlab::Metrics::Exporter::BaseExporter do end it 'starts server with port and address from settings' do - exporter.start.join - - expect(::WEBrick::HTTPServer).to have_received(:new).with( + expect(::WEBrick::HTTPServer).to receive(:new).with( Port: port, BindAddress: address, Logger: anything, AccessLog: anything - ) + ).and_wrap_original do |m, *args| + m.call(DoNotListen: true, Logger: args.first[:Logger]) + end + + exporter.start.join end end end describe '#stop' do it "doesn't shutdown stopped server" do - expect { exporter.stop }.not_to change { exporter.thread? } + expect_any_instance_of(::WEBrick::HTTPServer).not_to receive(:shutdown) - expect(server).not_to have_received(:shutdown) + expect { exporter.stop }.not_to change { exporter.thread? } end end end @@ -73,20 +82,66 @@ describe Gitlab::Metrics::Exporter::BaseExporter do describe '#start' do it "doesn't start running server" do - expect { exporter.start.join }.not_to change { exporter.thread? } + expect_any_instance_of(::WEBrick::HTTPServer).not_to receive(:start) - expect(server).to have_received(:start).once + expect { exporter.start.join }.not_to change { exporter.thread? } end end describe '#stop' do it 'shutdowns server' do + expect_any_instance_of(::WEBrick::HTTPServer).to receive(:shutdown) + expect { exporter.stop }.to change { exporter.thread? }.from(true).to(false) + end + end + end + end + + describe 'request handling' do + using RSpec::Parameterized::TableSyntax - expect(socket).to have_received(:close) - expect(server).to have_received(:shutdown) + where(:method_class, :path, :http_status) do + Net::HTTP::Get | '/metrics' | 200 + Net::HTTP::Get | '/liveness' | 200 + Net::HTTP::Get | '/readiness' | 200 + Net::HTTP::Get | '/' | 404 + end + + before do + allow(settings).to receive(:enabled).and_return(true) + allow(settings).to receive(:port).and_return(0) + allow(settings).to receive(:address).and_return('127.0.0.1') + + # We want to wrap original method + # and run handling of requests + # in separate thread + allow_any_instance_of(::WEBrick::HTTPServer) + .to receive(:start).and_wrap_original do |m, *args| + Thread.new do + m.call(*args) + rescue IOError + # is raised as we close listeners end end + + exporter.start.join + end + + after do + exporter.stop + end + + with_them do + let(:config) { exporter.server.config } + let(:request) { method_class.new(path) } + + it 'responds with proper http_status' do + http = Net::HTTP.new(config[:BindAddress], config[:Port]) + response = http.request(request) + + expect(response.code).to eq(http_status.to_s) + end end end @@ -97,18 +152,18 @@ describe Gitlab::Metrics::Exporter::BaseExporter do describe '#start' do it "doesn't start" do + expect_any_instance_of(::WEBrick::HTTPServer).not_to receive(:start) + expect(exporter.start).to be_nil expect { exporter.start }.not_to change { exporter.thread? } - - expect(server).not_to have_received(:start) end end describe '#stop' do it "doesn't shutdown" do - expect { exporter.stop }.not_to change { exporter.thread? } + expect_any_instance_of(::WEBrick::HTTPServer).not_to receive(:shutdown) - expect(server).not_to have_received(:shutdown) + expect { exporter.stop }.not_to change { exporter.thread? } end end end diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 796b6917fb2..3f149f9d7ee 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -880,22 +880,6 @@ describe Group do end end - describe '#has_parent?' do - context 'when the group has a parent' do - it 'is truthy' do - group = create(:group, :nested) - expect(group.has_parent?).to be_truthy - end - end - - context 'when the group has no parent' do - it 'is falsy' do - group = create(:group, parent: nil) - expect(group.has_parent?).to be_falsy - end - end - end - context 'with uploads' do it_behaves_like 'model with uploads', true do let(:model_object) { create(:group, :with_avatar) } diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index 797be0d1fe2..60f0dd49ff3 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -933,4 +933,25 @@ describe Namespace do end end end + + describe '#has_parent?' do + it 'returns true when the group has a parent' do + group = create(:group, :nested) + + expect(group.has_parent?).to be_truthy + end + + it 'returns true when the group has an unsaved parent' do + parent = build(:group) + group = build(:group, parent: parent) + + expect(group.has_parent?).to be_truthy + end + + it 'returns false when the group has no parent' do + group = create(:group, parent: nil) + + expect(group.has_parent?).to be_falsy + end + end end diff --git a/spec/models/project_services/bugzilla_service_spec.rb b/spec/models/project_services/bugzilla_service_spec.rb index 66481a461ca..d0ab5afc765 100644 --- a/spec/models/project_services/bugzilla_service_spec.rb +++ b/spec/models/project_services/bugzilla_service_spec.rb @@ -41,7 +41,7 @@ describe BugzillaService do { project_url: url, issues_url: url, new_issue_url: url } end - # this will be removed as part of https://gitlab.com/gitlab-org/gitlab-foss/issues/63084 + # this will be removed as part of https://gitlab.com/gitlab-org/gitlab/issues/29404 context 'when data are stored in properties' do let(:properties) { access_params.merge(title: title, description: description) } let(:service) do diff --git a/spec/models/project_services/custom_issue_tracker_service_spec.rb b/spec/models/project_services/custom_issue_tracker_service_spec.rb index 50bf15cfc8c..e749ea6eacc 100644 --- a/spec/models/project_services/custom_issue_tracker_service_spec.rb +++ b/spec/models/project_services/custom_issue_tracker_service_spec.rb @@ -55,7 +55,7 @@ describe CustomIssueTrackerService do { project_url: url, issues_url: url, new_issue_url: url } end - # this will be removed as part of https://gitlab.com/gitlab-org/gitlab-foss/issues/63084 + # this will be removed as part of https://gitlab.com/gitlab-org/gitlab/issues/29404 context 'when data are stored in properties' do let(:properties) { access_params.merge(title: title, description: description) } let(:service) do diff --git a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb index 2dc0b67239c..defebcee9c6 100644 --- a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb +++ b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb @@ -58,7 +58,7 @@ describe GitlabIssueTrackerService do { project_url: url, issues_url: url, new_issue_url: url } end - # this will be removed as part of https://gitlab.com/gitlab-org/gitlab-foss/issues/63084 + # this will be removed as part of https://gitlab.com/gitlab-org/gitlab/issues/29404 context 'when data are stored in properties' do let(:properties) { access_params.merge(title: title, description: description) } let(:service) do diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb index c3b2e52848c..28d2ea79641 100644 --- a/spec/models/project_services/jira_service_spec.rb +++ b/spec/models/project_services/jira_service_spec.rb @@ -278,7 +278,7 @@ describe JiraService do end end - # this will be removed as part of https://gitlab.com/gitlab-org/gitlab-foss/issues/63084 + # this will be removed as part of https://gitlab.com/gitlab-org/gitlab/issues/29404 context 'when data are stored in properties' do let(:properties) { data_params.merge(title: title, description: description) } let!(:service) do diff --git a/spec/models/project_services/redmine_service_spec.rb b/spec/models/project_services/redmine_service_spec.rb index 2339c5a8421..6220d7b1fac 100644 --- a/spec/models/project_services/redmine_service_spec.rb +++ b/spec/models/project_services/redmine_service_spec.rb @@ -57,7 +57,7 @@ describe RedmineService do { project_url: url, issues_url: url, new_issue_url: url } end - # this will be removed as part of https://gitlab.com/gitlab-org/gitlab-foss/issues/63084 + # this will be removed as part of https://gitlab.com/gitlab-org/gitlab/issues/29404 context 'when data are stored in properties' do let(:properties) { access_params.merge(title: title, description: description) } let(:service) do diff --git a/spec/models/project_services/youtrack_service_spec.rb b/spec/models/project_services/youtrack_service_spec.rb index fe608baf16b..19d4cb95315 100644 --- a/spec/models/project_services/youtrack_service_spec.rb +++ b/spec/models/project_services/youtrack_service_spec.rb @@ -45,7 +45,7 @@ describe YoutrackService do { project_url: url, issues_url: url, new_issue_url: url } end - # this will be removed as part of https://gitlab.com/gitlab-org/gitlab-foss/issues/63084 + # this will be removed as part of https://gitlab.com/gitlab-org/gitlab/issues/29404 context 'when data are stored in properties' do let(:properties) { access_params.merge(title: title, description: description) } let(:service) do diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb index 4049ddcff7f..64077b76f01 100644 --- a/spec/models/service_spec.rb +++ b/spec/models/service_spec.rb @@ -121,7 +121,7 @@ describe Service do end end - # this will be removed as part of https://gitlab.com/gitlab-org/gitlab-foss/issues/63084 + # this will be removed as part of https://gitlab.com/gitlab-org/gitlab/issues/29404 context 'when data are stored in properties' do let(:properties) { data_params.merge(title: title, description: description) } let!(:template) do diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index ec587a28f4f..902a5ec2a86 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -1,6 +1,7 @@ require 'spec_helper' describe API::Groups do + include GroupAPIHelpers include UploadHelpers let(:user1) { create(:user, can_create_group: false) } @@ -350,6 +351,13 @@ describe API::Groups do expect(json_response['description']).to eq(group1.description) expect(json_response['visibility']).to eq(Gitlab::VisibilityLevel.string_level(group1.visibility_level)) expect(json_response['avatar_url']).to eq(group1.avatar_url(only_path: false)) + expect(json_response['share_with_group_lock']).to eq(group1.share_with_group_lock) + expect(json_response['require_two_factor_authentication']).to eq(group1.require_two_factor_authentication) + expect(json_response['two_factor_grace_period']).to eq(group1.two_factor_grace_period) + expect(json_response['auto_devops_enabled']).to eq(group1.auto_devops_enabled) + expect(json_response['emails_disabled']).to eq(group1.emails_disabled) + expect(json_response['project_creation_level']).to eq('maintainer') + expect(json_response['subgroup_creation_level']).to eq('maintainer') expect(json_response['web_url']).to eq(group1.web_url) expect(json_response['request_access_enabled']).to eq(group1.request_access_enabled) expect(json_response['full_name']).to eq(group1.full_name) @@ -485,11 +493,30 @@ describe API::Groups do context 'when authenticated as the group owner' do it 'updates the group' do - put api("/groups/#{group1.id}", user1), params: { name: new_group_name, request_access_enabled: true } + put api("/groups/#{group1.id}", user1), params: { + name: new_group_name, + request_access_enabled: true, + project_creation_level: "noone", + subgroup_creation_level: "maintainer" + } expect(response).to have_gitlab_http_status(200) expect(json_response['name']).to eq(new_group_name) + expect(json_response['description']).to eq('') + expect(json_response['visibility']).to eq('public') + expect(json_response['share_with_group_lock']).to eq(false) + expect(json_response['require_two_factor_authentication']).to eq(false) + expect(json_response['two_factor_grace_period']).to eq(48) + expect(json_response['auto_devops_enabled']).to eq(nil) + expect(json_response['emails_disabled']).to eq(nil) + expect(json_response['project_creation_level']).to eq("noone") + expect(json_response['subgroup_creation_level']).to eq("maintainer") expect(json_response['request_access_enabled']).to eq(true) + expect(json_response['parent_id']).to eq(nil) + expect(json_response['projects']).to be_an Array + expect(json_response['projects'].length).to eq(2) + expect(json_response['shared_projects']).to be_an Array + expect(json_response['shared_projects'].length).to eq(0) end it 'returns 404 for a non existing group' do @@ -864,7 +891,9 @@ describe API::Groups do describe "POST /groups" do context "when authenticated as user without group permissions" do it "does not create group" do - post api("/groups", user1), params: attributes_for(:group) + group = attributes_for_group_api + + post api("/groups", user1), params: group expect(response).to have_gitlab_http_status(403) end @@ -896,7 +925,7 @@ describe API::Groups do context "when authenticated as user with group permissions" do it "creates group" do - group = attributes_for(:group, { request_access_enabled: false }) + group = attributes_for_group_api request_access_enabled: false post api("/groups", user3), params: group @@ -911,7 +940,7 @@ describe API::Groups do it "creates a nested group" do parent = create(:group) parent.add_owner(user3) - group = attributes_for(:group, { parent_id: parent.id }) + group = attributes_for_group_api parent_id: parent.id post api("/groups", user3), params: group diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index af2bee4563a..df76b62b40e 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -634,10 +634,47 @@ describe API::Users do end describe "GET /users/sign_up" do - it "redirects to sign in page" do - get "/users/sign_up" - expect(response).to have_gitlab_http_status(302) - expect(response).to redirect_to(new_user_session_path) + context 'when experimental_separate_sign_up_flow is active' do + before do + stub_feature_flags(experimental_separate_sign_up_flow: true) + end + + context 'on gitlab.com' do + before do + allow(::Gitlab).to receive(:com?).and_return(true) + end + + it "shows sign up page" do + get "/users/sign_up" + expect(response).to have_gitlab_http_status(200) + expect(response).to render_template(:new) + end + end + + context 'not on gitlab.com' do + before do + allow(::Gitlab).to receive(:com?).and_return(false) + end + + it "redirects to sign in page" do + get "/users/sign_up" + expect(response).to have_gitlab_http_status(302) + expect(response).to redirect_to(new_user_session_path(anchor: 'register-pane')) + end + end + end + + context 'when experimental_separate_sign_up_flow is not active' do + before do + allow(::Gitlab).to receive(:com?).and_return(true) + stub_feature_flags(experimental_separate_sign_up_flow: false) + end + + it "redirects to sign in page" do + get "/users/sign_up" + expect(response).to have_gitlab_http_status(302) + expect(response).to redirect_to(new_user_session_path(anchor: 'register-pane')) + end end end diff --git a/spec/support/helpers/group_api_helpers.rb b/spec/support/helpers/group_api_helpers.rb new file mode 100644 index 00000000000..56c4cc121a7 --- /dev/null +++ b/spec/support/helpers/group_api_helpers.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module GroupAPIHelpers + extend self + + def attributes_for_group_api(params = {}) + # project_creation_level and subgroup_creation_level are Integers in the model + # but are strings in the API + attributes_for(:group, params).except(:project_creation_level, :subgroup_creation_level) + end +end |