diff options
48 files changed, 2241 insertions, 1292 deletions
diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS index 2dc0e0df51a..7fcbd782723 100644 --- a/.gitlab/CODEOWNERS +++ b/.gitlab/CODEOWNERS @@ -804,249 +804,256 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab /doc/user/workspace/index.md @fneill [Authentication and Authorization] -/app/assets/javascripts/access_tokens/ @gitlab-org/manage/authentication-and-authorization -/app/assets/javascripts/alerts_settings/graphql/mutations/reset_http_token.mutation.graphql @gitlab-org/manage/authentication-and-authorization -/app/assets/javascripts/authentication/ @gitlab-org/manage/authentication-and-authorization -/app/assets/javascripts/ide/components/shared/tokened_input.vue @gitlab-org/manage/authentication-and-authorization -/app/assets/javascripts/invite_members/components/members_token_select.vue @gitlab-org/manage/authentication-and-authorization -/app/assets/javascripts/packages_and_registries/package_registry/components/list/tokens/ @gitlab-org/manage/authentication-and-authorization -/app/assets/javascripts/pages/admin/impersonation_tokens/ @gitlab-org/manage/authentication-and-authorization -/app/assets/javascripts/pages/groups/settings/access_tokens/ @gitlab-org/manage/authentication-and-authorization -/app/assets/javascripts/pages/ldap/ @gitlab-org/manage/authentication-and-authorization -/app/assets/javascripts/pages/oauth/ @gitlab-org/manage/authentication-and-authorization -/app/assets/javascripts/pages/omniauth_callbacks/ @gitlab-org/manage/authentication-and-authorization -/app/assets/javascripts/pages/profiles/password_prompt/ @gitlab-org/manage/authentication-and-authorization -/app/assets/javascripts/pages/profiles/personal_access_tokens/ @gitlab-org/manage/authentication-and-authorization -/app/assets/javascripts/pages/profiles/two_factor_auths/ @gitlab-org/manage/authentication-and-authorization -/app/assets/javascripts/pages/projects/settings/access_tokens/ @gitlab-org/manage/authentication-and-authorization -/app/assets/javascripts/pages/sessions/new/oauth_remember_me.js @gitlab-org/manage/authentication-and-authorization -/app/assets/javascripts/pipelines/components/pipelines_list/tokens/constants.js @gitlab-org/manage/authentication-and-authorization -/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_branch_name_token.vue @gitlab-org/manage/authentication-and-authorization -/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_source_token.vue @gitlab-org/manage/authentication-and-authorization -/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_status_token.vue @gitlab-org/manage/authentication-and-authorization -/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_tag_name_token.vue @gitlab-org/manage/authentication-and-authorization -/app/assets/javascripts/projects/settings/topics/components/ @gitlab-org/manage/authentication-and-authorization -/app/assets/javascripts/related_issues/components/issue_token.vue @gitlab-org/manage/authentication-and-authorization -/app/assets/javascripts/runner/components/registration/registration_token.vue @gitlab-org/manage/authentication-and-authorization -/app/assets/javascripts/runner/components/registration/registration_token_reset_dropdown_item.vue @gitlab-org/manage/authentication-and-authorization -/app/assets/javascripts/runner/components/search_tokens/ @gitlab-org/manage/authentication-and-authorization -/app/assets/javascripts/token_access/components/ @gitlab-org/manage/authentication-and-authorization -/app/assets/javascripts/token_access/index.js @gitlab-org/manage/authentication-and-authorization -/app/assets/stylesheets/page_bundles/profile_two_factor_auth.scss @gitlab-org/manage/authentication-and-authorization -/app/controllers/admin/impersonation_tokens_controller.rb @gitlab-org/manage/authentication-and-authorization -/app/controllers/concerns/access_tokens_actions.rb @gitlab-org/manage/authentication-and-authorization -/app/controllers/concerns/authenticates_with_two_factor.rb @gitlab-org/manage/authentication-and-authorization -/app/controllers/concerns/authenticates_with_two_factor_for_admin_mode.rb @gitlab-org/manage/authentication-and-authorization -/app/controllers/concerns/enforces_admin_authentication.rb @gitlab-org/manage/authentication-and-authorization -/app/controllers/concerns/enforces_two_factor_authentication.rb @gitlab-org/manage/authentication-and-authorization -/app/controllers/concerns/oauth_applications.rb @gitlab-org/manage/authentication-and-authorization -/app/controllers/concerns/project_unauthorized.rb @gitlab-org/manage/authentication-and-authorization -/app/controllers/concerns/sessionless_authentication.rb @gitlab-org/manage/authentication-and-authorization -/app/controllers/concerns/snippet_authorizations.rb @gitlab-org/manage/authentication-and-authorization -/app/controllers/concerns/workhorse_authorization.rb @gitlab-org/manage/authentication-and-authorization -/app/controllers/groups/settings/access_tokens_controller.rb @gitlab-org/manage/authentication-and-authorization -/app/controllers/ldap/ @gitlab-org/manage/authentication-and-authorization -/app/controllers/oauth/ @gitlab-org/manage/authentication-and-authorization -/app/controllers/omniauth_callbacks_controller.rb @gitlab-org/manage/authentication-and-authorization -/app/controllers/passwords_controller.rb @gitlab-org/manage/authentication-and-authorization -/app/controllers/profiles/passwords_controller.rb @gitlab-org/manage/authentication-and-authorization -/app/controllers/profiles/personal_access_tokens_controller.rb @gitlab-org/manage/authentication-and-authorization -/app/controllers/profiles/two_factor_auths_controller.rb @gitlab-org/manage/authentication-and-authorization -/app/controllers/profiles/webauthn_registrations_controller.rb @gitlab-org/manage/authentication-and-authorization -/app/controllers/projects/settings/access_tokens_controller.rb @gitlab-org/manage/authentication-and-authorization -/app/finders/groups/projects_requiring_authorizations_refresh/ @gitlab-org/manage/authentication-and-authorization -/app/finders/personal_access_tokens_finder.rb @gitlab-org/manage/authentication-and-authorization -/app/helpers/access_tokens_helper.rb @gitlab-org/manage/authentication-and-authorization -/app/helpers/auth_helper.rb @gitlab-org/manage/authentication-and-authorization -/app/models/authentication_event.rb @gitlab-org/manage/authentication-and-authorization -/app/models/concerns/admin_changed_password_notifier.rb @gitlab-org/manage/authentication-and-authorization -/app/models/concerns/mirror_authentication.rb @gitlab-org/manage/authentication-and-authorization -/app/models/concerns/select_for_project_authorization.rb @gitlab-org/manage/authentication-and-authorization -/app/models/concerns/token_authenticatable.rb @gitlab-org/manage/authentication-and-authorization -/app/models/concerns/token_authenticatable_strategies/ @gitlab-org/manage/authentication-and-authorization -/app/models/oauth_access_grant.rb @gitlab-org/manage/authentication-and-authorization -/app/models/oauth_access_token.rb @gitlab-org/manage/authentication-and-authorization -/app/models/personal_access_token.rb @gitlab-org/manage/authentication-and-authorization -/app/models/project_authorization.rb @gitlab-org/manage/authentication-and-authorization -/app/models/token_with_iv.rb @gitlab-org/manage/authentication-and-authorization -/app/models/webauthn_registration.rb @gitlab-org/manage/authentication-and-authorization -/app/policies/personal_access_token_policy.rb @gitlab-org/manage/authentication-and-authorization -/app/services/access_token_validation_service.rb @gitlab-org/manage/authentication-and-authorization -/app/services/auth/ @gitlab-org/manage/authentication-and-authorization -/app/services/authorized_project_update/ @gitlab-org/manage/authentication-and-authorization -/app/services/chat_names/authorize_user_service.rb @gitlab-org/manage/authentication-and-authorization -/app/services/personal_access_tokens/ @gitlab-org/manage/authentication-and-authorization -/app/services/projects/move_project_authorizations_service.rb @gitlab-org/manage/authentication-and-authorization -/app/services/resource_access_tokens/ @gitlab-org/manage/authentication-and-authorization -/app/services/todos/destroy/unauthorized_features_service.rb @gitlab-org/manage/authentication-and-authorization -/app/services/users/authorized_build_service.rb @gitlab-org/manage/authentication-and-authorization -/app/services/users/authorized_create_service.rb @gitlab-org/manage/authentication-and-authorization -/app/services/users/refresh_authorized_projects_service.rb @gitlab-org/manage/authentication-and-authorization -/app/services/webauthn/ @gitlab-org/manage/authentication-and-authorization -/app/validators/json_schemas/cluster_agent_authorization_configuration.json @gitlab-org/manage/authentication-and-authorization -/app/views/admin/application_settings/_external_authorization_service_form.html.haml @gitlab-org/manage/authentication-and-authorization -/app/views/admin/impersonation_tokens/ @gitlab-org/manage/authentication-and-authorization -/app/views/authentication/ @gitlab-org/manage/authentication-and-authorization -/app/views/ci/token_access/ @gitlab-org/manage/authentication-and-authorization -/app/views/dashboard/projects/_zero_authorized_projects.html.haml @gitlab-org/manage/authentication-and-authorization -/app/views/devise/mailer/password_change.html.haml @gitlab-org/manage/authentication-and-authorization -/app/views/devise/mailer/password_change.text.erb @gitlab-org/manage/authentication-and-authorization -/app/views/devise/mailer/password_change_by_admin.html.haml @gitlab-org/manage/authentication-and-authorization -/app/views/devise/mailer/password_change_by_admin.text.erb @gitlab-org/manage/authentication-and-authorization -/app/views/devise/mailer/reset_password_instructions.html.haml @gitlab-org/manage/authentication-and-authorization -/app/views/devise/mailer/reset_password_instructions.text.erb @gitlab-org/manage/authentication-and-authorization -/app/views/devise/passwords/ @gitlab-org/manage/authentication-and-authorization -/app/views/devise/shared/_omniauth_box.html.haml @gitlab-org/manage/authentication-and-authorization -/app/views/devise/shared/_signup_omniauth_provider_list.haml @gitlab-org/manage/authentication-and-authorization -/app/views/devise/shared/_signup_omniauth_providers.haml @gitlab-org/manage/authentication-and-authorization -/app/views/devise/shared/_signup_omniauth_providers_top.haml @gitlab-org/manage/authentication-and-authorization -/app/views/doorkeeper/authorizations/ @gitlab-org/manage/authentication-and-authorization -/app/views/doorkeeper/authorized_applications/ @gitlab-org/manage/authentication-and-authorization -/app/views/errors/omniauth_error.html.haml @gitlab-org/manage/authentication-and-authorization -/app/views/groups/settings/_resource_access_token_creation.html.haml @gitlab-org/manage/authentication-and-authorization -/app/views/groups/settings/_two_factor_auth.html.haml @gitlab-org/manage/authentication-and-authorization -/app/views/groups/settings/access_tokens/ @gitlab-org/manage/authentication-and-authorization -/app/views/layouts/oauth_error.html.haml @gitlab-org/manage/authentication-and-authorization -/app/views/notify/access_token_about_to_expire_email.html.haml @gitlab-org/manage/authentication-and-authorization -/app/views/notify/access_token_about_to_expire_email.text.erb @gitlab-org/manage/authentication-and-authorization -/app/views/notify/access_token_created_email.html.haml @gitlab-org/manage/authentication-and-authorization -/app/views/notify/access_token_created_email.text.erb @gitlab-org/manage/authentication-and-authorization -/app/views/notify/access_token_expired_email.html.haml @gitlab-org/manage/authentication-and-authorization -/app/views/notify/access_token_expired_email.text.erb @gitlab-org/manage/authentication-and-authorization -/app/views/profiles/passwords/ @gitlab-org/manage/authentication-and-authorization -/app/views/profiles/personal_access_tokens/ @gitlab-org/manage/authentication-and-authorization -/app/views/profiles/two_factor_auths/ @gitlab-org/manage/authentication-and-authorization -/app/views/projects/mirrors/_authentication_method.html.haml @gitlab-org/manage/authentication-and-authorization -/app/views/projects/settings/access_tokens/ @gitlab-org/manage/authentication-and-authorization -/app/views/shared/_no_password.html.haml @gitlab-org/manage/authentication-and-authorization -/app/views/shared/_two_factor_auth_recovery_settings_check.html.haml @gitlab-org/manage/authentication-and-authorization -/app/views/shared/access_tokens/ @gitlab-org/manage/authentication-and-authorization -/app/views/shared/members/_two_factor_auth_badge.html.haml @gitlab-org/manage/authentication-and-authorization -/app/views/shared/tokens/ @gitlab-org/manage/authentication-and-authorization -/app/workers/authorized_keys_worker.rb @gitlab-org/manage/authentication-and-authorization -/app/workers/authorized_project_update/ @gitlab-org/manage/authentication-and-authorization -/app/workers/authorized_projects_worker.rb @gitlab-org/manage/authentication-and-authorization -/app/workers/personal_access_tokens/ @gitlab-org/manage/authentication-and-authorization -/config/feature_flags/development/application_settings_tokens_optional_encryption.yml @gitlab-org/manage/authentication-and-authorization -/config/feature_flags/development/async_only_project_authorizations_refresh.yml @gitlab-org/manage/authentication-and-authorization -/config/feature_flags/development/enforce_auth_checks_on_uploads.yml @gitlab-org/manage/authentication-and-authorization -/config/feature_flags/development/forti_authenticator.yml @gitlab-org/manage/authentication-and-authorization -/config/feature_flags/development/forti_token_cloud.yml @gitlab-org/manage/authentication-and-authorization -/config/feature_flags/development/groups_tokens_optional_encryption.yml @gitlab-org/manage/authentication-and-authorization -/config/feature_flags/development/omniauth_login_minimal_scopes.yml @gitlab-org/manage/authentication-and-authorization -/config/feature_flags/development/projects_tokens_optional_encryption.yml @gitlab-org/manage/authentication-and-authorization -/config/feature_flags/development/refresh_authorizations_via_affected_projects_on_group_membership.yml @gitlab-org/manage/authentication-and-authorization -/config/feature_flags/development/skip_group_share_unlink_auth_refresh.yml @gitlab-org/manage/authentication-and-authorization -/config/feature_flags/development/specialized_worker_for_group_lock_update_auth_recalculation.yml @gitlab-org/manage/authentication-and-authorization -/config/feature_flags/development/update_oauth_registration_flow.yml @gitlab-org/manage/authentication-and-authorization -/config/feature_flags/development/webauthn.yml @gitlab-org/manage/authentication-and-authorization -/config/feature_flags/ops/block_password_auth_for_saml_users.yml @gitlab-org/manage/authentication-and-authorization -/config/initializers/01_secret_token.rb @gitlab-org/manage/authentication-and-authorization -/config/initializers/devise_dynamic_password_length_validation.rb @gitlab-org/manage/authentication-and-authorization -/config/initializers/devise_password_length.rb.example @gitlab-org/manage/authentication-and-authorization -/config/initializers/gitlab_shell_secret_token.rb @gitlab-org/manage/authentication-and-authorization -/config/initializers/omniauth.rb @gitlab-org/manage/authentication-and-authorization -/config/initializers/rails_host_authorization.rb @gitlab-org/manage/authentication-and-authorization -/config/initializers/rails_host_authorization_gitpod.rb @gitlab-org/manage/authentication-and-authorization -/config/initializers/webauthn.rb @gitlab-org/manage/authentication-and-authorization -/config/initializers_before_autoloader/100_patch_omniauth_oauth2.rb @gitlab-org/manage/authentication-and-authorization -/config/initializers_before_autoloader/100_patch_omniauth_saml.rb @gitlab-org/manage/authentication-and-authorization -/ee/app/assets/javascripts/access_tokens/ @gitlab-org/manage/authentication-and-authorization -/ee/app/assets/javascripts/audit_events/components/tokens/ @gitlab-org/manage/authentication-and-authorization -/ee/app/assets/javascripts/audit_events/token_utils.js @gitlab-org/manage/authentication-and-authorization -/ee/app/assets/javascripts/groups/settings/components/ @gitlab-org/manage/authentication-and-authorization -/ee/app/assets/javascripts/pages/groups/omniauth_callbacks/ @gitlab-org/manage/authentication-and-authorization -/ee/app/assets/javascripts/pipelines/components/pipelines_list/ @gitlab-org/manage/authentication-and-authorization -/ee/app/assets/javascripts/requirements/components/tokens/ @gitlab-org/manage/authentication-and-authorization -/ee/app/assets/javascripts/saml_providers/scim_token_service.js @gitlab-org/manage/authentication-and-authorization -/ee/app/assets/javascripts/saml_sso/components/ @gitlab-org/manage/authentication-and-authorization -/ee/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals_auth.vue @gitlab-org/manage/authentication-and-authorization -/ee/app/controllers/concerns/ee/authenticates_with_two_factor.rb @gitlab-org/manage/authentication-and-authorization -/ee/app/controllers/concerns/ee/enforces_two_factor_authentication.rb @gitlab-org/manage/authentication-and-authorization -/ee/app/controllers/concerns/saml_authorization.rb @gitlab-org/manage/authentication-and-authorization -/ee/app/controllers/ee/ldap/ @gitlab-org/manage/authentication-and-authorization -/ee/app/controllers/ee/omniauth_callbacks_controller.rb @gitlab-org/manage/authentication-and-authorization -/ee/app/controllers/ee/passwords_controller.rb @gitlab-org/manage/authentication-and-authorization -/ee/app/controllers/groups/omniauth_callbacks_controller.rb @gitlab-org/manage/authentication-and-authorization -/ee/app/controllers/groups/scim_oauth_controller.rb @gitlab-org/manage/authentication-and-authorization -/ee/app/controllers/oauth/ @gitlab-org/manage/authentication-and-authorization -/ee/app/controllers/omniauth_kerberos_spnego_controller.rb @gitlab-org/manage/authentication-and-authorization -/ee/app/finders/auth/ @gitlab-org/manage/authentication-and-authorization -/ee/app/helpers/ee/access_tokens_helper.rb @gitlab-org/manage/authentication-and-authorization -/ee/app/helpers/ee/auth_helper.rb @gitlab-org/manage/authentication-and-authorization -/ee/app/helpers/ee/personal_access_tokens_helper.rb @gitlab-org/manage/authentication-and-authorization -/ee/app/models/concerns/password_complexity.rb @gitlab-org/manage/authentication-and-authorization -/ee/app/models/ee/personal_access_token.rb @gitlab-org/manage/authentication-and-authorization -/ee/app/models/ee/project_authorization.rb @gitlab-org/manage/authentication-and-authorization -/ee/app/models/scim_oauth_access_token.rb @gitlab-org/manage/authentication-and-authorization -/ee/app/serializers/scim_oauth_access_token_entity.rb @gitlab-org/manage/authentication-and-authorization -/ee/app/services/ee/auth/ @gitlab-org/manage/authentication-and-authorization -/ee/app/services/ee/personal_access_tokens/ @gitlab-org/manage/authentication-and-authorization -/ee/app/services/ee/resource_access_tokens/ @gitlab-org/manage/authentication-and-authorization -/ee/app/services/personal_access_tokens/ @gitlab-org/manage/authentication-and-authorization -/ee/app/services/security/token_revocation_service.rb @gitlab-org/manage/authentication-and-authorization -/ee/app/validators/password/ @gitlab-org/manage/authentication-and-authorization -/ee/app/views/admin/application_settings/_personal_access_token_expiration_policy.html.haml @gitlab-org/manage/authentication-and-authorization -/ee/app/views/credentials_inventory_mailer/personal_access_token_revoked_email.html.haml @gitlab-org/manage/authentication-and-authorization -/ee/app/views/credentials_inventory_mailer/personal_access_token_revoked_email.text.haml @gitlab-org/manage/authentication-and-authorization -/ee/app/views/groups/_personal_access_token_expiration_policy.html.haml @gitlab-org/manage/authentication-and-authorization -/ee/app/views/groups/sso/_authorize_pane.html.haml @gitlab-org/manage/authentication-and-authorization -/ee/app/views/notify/policy_revoked_personal_access_tokens_email.html.haml @gitlab-org/manage/authentication-and-authorization -/ee/app/views/notify/policy_revoked_personal_access_tokens_email.text.erb @gitlab-org/manage/authentication-and-authorization -/ee/app/views/oauth/ @gitlab-org/manage/authentication-and-authorization -/ee/app/views/shared/credentials_inventory/_personal_access_tokens.html.haml @gitlab-org/manage/authentication-and-authorization -/ee/app/views/shared/credentials_inventory/_project_access_tokens.html.haml @gitlab-org/manage/authentication-and-authorization -/ee/app/views/shared/credentials_inventory/personal_access_tokens/ @gitlab-org/manage/authentication-and-authorization -/ee/app/views/shared/credentials_inventory/project_access_tokens/ @gitlab-org/manage/authentication-and-authorization -/ee/app/workers/auth/ @gitlab-org/manage/authentication-and-authorization -/ee/app/workers/personal_access_tokens/ @gitlab-org/manage/authentication-and-authorization -/ee/config/routes/oauth.rb @gitlab-org/manage/authentication-and-authorization -/ee/lib/ee/gitlab/auth/ @gitlab-org/manage/authentication-and-authorization -/ee/lib/ee/gitlab/omniauth_initializer.rb @gitlab-org/manage/authentication-and-authorization -/ee/lib/gitlab/auth/ @gitlab-org/manage/authentication-and-authorization -/ee/lib/gitlab/auth_logger.rb @gitlab-org/manage/authentication-and-authorization -/ee/lib/gitlab/authority_analyzer.rb @gitlab-org/manage/authentication-and-authorization -/ee/lib/gitlab/geo/oauth/ @gitlab-org/manage/authentication-and-authorization -/ee/lib/gitlab/kerberos/ @gitlab-org/manage/authentication-and-authorization -/ee/lib/omni_auth/ @gitlab-org/manage/authentication-and-authorization -/ee/lib/system_check/geo/authorized_keys_check.rb @gitlab-org/manage/authentication-and-authorization -/ee/lib/system_check/geo/authorized_keys_flag_check.rb @gitlab-org/manage/authentication-and-authorization -/lib/api/entities/ci/reset_token_result.rb @gitlab-org/manage/authentication-and-authorization -/lib/api/entities/impersonation_token.rb @gitlab-org/manage/authentication-and-authorization -/lib/api/entities/impersonation_token_with_token.rb @gitlab-org/manage/authentication-and-authorization -/lib/api/entities/personal_access_token.rb @gitlab-org/manage/authentication-and-authorization -/lib/api/entities/personal_access_token_with_details.rb @gitlab-org/manage/authentication-and-authorization -/lib/api/entities/personal_access_token_with_token.rb @gitlab-org/manage/authentication-and-authorization -/lib/api/entities/resource_access_token.rb @gitlab-org/manage/authentication-and-authorization -/lib/api/entities/resource_access_token_with_token.rb @gitlab-org/manage/authentication-and-authorization -/lib/api/helpers/authentication.rb @gitlab-org/manage/authentication-and-authorization -/lib/api/helpers/packages/basic_auth_helpers.rb @gitlab-org/manage/authentication-and-authorization -/lib/api/personal_access_tokens.rb @gitlab-org/manage/authentication-and-authorization -/lib/api/resource_access_tokens.rb @gitlab-org/manage/authentication-and-authorization -/lib/api/support/token_with_expiration.rb @gitlab-org/manage/authentication-and-authorization -/lib/gitlab/api_authentication/ @gitlab-org/manage/authentication-and-authorization -/lib/gitlab/auth/ @gitlab-org/manage/authentication-and-authorization -/lib/gitlab/auth.rb @gitlab-org/manage/authentication-and-authorization -/lib/gitlab/auth_logger.rb @gitlab-org/manage/authentication-and-authorization -/lib/gitlab/authorized_keys.rb @gitlab-org/manage/authentication-and-authorization -/lib/gitlab/background_migration/encrypt_static_object_token.rb @gitlab-org/manage/authentication-and-authorization -/lib/gitlab/background_migration/expire_o_auth_tokens.rb @gitlab-org/manage/authentication-and-authorization -/lib/gitlab/background_migration/migrate_u2f_webauthn.rb @gitlab-org/manage/authentication-and-authorization -/lib/gitlab/background_migration/update_users_where_two_factor_auth_required_from_group.rb @gitlab-org/manage/authentication-and-authorization -/lib/gitlab/chat_name_token.rb @gitlab-org/manage/authentication-and-authorization -/lib/gitlab/ci/pipeline/expression/token.rb @gitlab-org/manage/authentication-and-authorization -/lib/gitlab/external_authorization/ @gitlab-org/manage/authentication-and-authorization -/lib/gitlab/external_authorization.rb @gitlab-org/manage/authentication-and-authorization -/lib/gitlab/graphql/authorize/ @gitlab-org/manage/authentication-and-authorization -/lib/gitlab/jwt_authenticatable.rb @gitlab-org/manage/authentication-and-authorization -/lib/gitlab/jwt_token.rb @gitlab-org/manage/authentication-and-authorization -/lib/gitlab/lfs_token.rb @gitlab-org/manage/authentication-and-authorization -/lib/gitlab/mail_room/ @gitlab-org/manage/authentication-and-authorization -/lib/gitlab/omniauth_initializer.rb @gitlab-org/manage/authentication-and-authorization -/lib/gitlab/project_authorizations.rb @gitlab-org/manage/authentication-and-authorization -/lib/json_web_token/ @gitlab-org/manage/authentication-and-authorization -/lib/omni_auth/ @gitlab-org/manage/authentication-and-authorization -/lib/system_check/app/authorized_keys_permission_check.rb @gitlab-org/manage/authentication-and-authorization -/lib/system_check/incoming_email/imap_authentication_check.rb @gitlab-org/manage/authentication-and-authorization -/lib/tasks/gitlab/password.rake @gitlab-org/manage/authentication-and-authorization -/lib/tasks/tokens.rake @gitlab-org/manage/authentication-and-authorization +/app/assets/javascripts/access_tokens/ @gitlab-org/manage/authentication-and-authorization/approvers +/app/assets/javascripts/alerts_settings/graphql/mutations/reset_http_token.mutation.graphql @gitlab-org/manage/authentication-and-authorization/approvers +/app/assets/javascripts/authentication/ @gitlab-org/manage/authentication-and-authorization/approvers +/app/assets/javascripts/ide/components/shared/tokened_input.vue @gitlab-org/manage/authentication-and-authorization/approvers +/app/assets/javascripts/invite_members/components/members_token_select.vue @gitlab-org/manage/authentication-and-authorization/approvers +/app/assets/javascripts/packages_and_registries/package_registry/components/list/tokens/ @gitlab-org/manage/authentication-and-authorization/approvers +/app/assets/javascripts/pages/admin/impersonation_tokens/ @gitlab-org/manage/authentication-and-authorization/approvers +/app/assets/javascripts/pages/groups/settings/access_tokens/ @gitlab-org/manage/authentication-and-authorization/approvers +/app/assets/javascripts/pages/ldap/ @gitlab-org/manage/authentication-and-authorization/approvers +/app/assets/javascripts/pages/oauth/ @gitlab-org/manage/authentication-and-authorization/approvers +/app/assets/javascripts/pages/omniauth_callbacks/ @gitlab-org/manage/authentication-and-authorization/approvers +/app/assets/javascripts/pages/profiles/password_prompt/ @gitlab-org/manage/authentication-and-authorization/approvers +/app/assets/javascripts/pages/profiles/personal_access_tokens/ @gitlab-org/manage/authentication-and-authorization/approvers +/app/assets/javascripts/pages/profiles/two_factor_auths/ @gitlab-org/manage/authentication-and-authorization/approvers +/app/assets/javascripts/pages/projects/settings/access_tokens/ @gitlab-org/manage/authentication-and-authorization/approvers +/app/assets/javascripts/pages/sessions/new/oauth_remember_me.js @gitlab-org/manage/authentication-and-authorization/approvers +/app/assets/javascripts/pipelines/components/pipelines_list/tokens/constants.js @gitlab-org/manage/authentication-and-authorization/approvers +/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_branch_name_token.vue @gitlab-org/manage/authentication-and-authorization/approvers +/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_source_token.vue @gitlab-org/manage/authentication-and-authorization/approvers +/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_status_token.vue @gitlab-org/manage/authentication-and-authorization/approvers +/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_tag_name_token.vue @gitlab-org/manage/authentication-and-authorization/approvers +/app/assets/javascripts/projects/settings/topics/components/ @gitlab-org/manage/authentication-and-authorization/approvers +/app/assets/javascripts/related_issues/components/issue_token.vue @gitlab-org/manage/authentication-and-authorization/approvers +/app/assets/javascripts/runner/components/registration/registration_token.vue @gitlab-org/manage/authentication-and-authorization/approvers +/app/assets/javascripts/runner/components/registration/registration_token_reset_dropdown_item.vue @gitlab-org/manage/authentication-and-authorization/approvers +/app/assets/javascripts/runner/components/search_tokens/ @gitlab-org/manage/authentication-and-authorization/approvers +/app/assets/javascripts/token_access/components/ @gitlab-org/manage/authentication-and-authorization/approvers +/app/assets/javascripts/token_access/index.js @gitlab-org/manage/authentication-and-authorization/approvers +/app/assets/stylesheets/page_bundles/profile_two_factor_auth.scss @gitlab-org/manage/authentication-and-authorization/approvers +/app/controllers/admin/impersonation_tokens_controller.rb @gitlab-org/manage/authentication-and-authorization/approvers +/app/controllers/concerns/access_tokens_actions.rb @gitlab-org/manage/authentication-and-authorization/approvers +/app/controllers/concerns/authenticates_with_two_factor.rb @gitlab-org/manage/authentication-and-authorization/approvers +/app/controllers/concerns/authenticates_with_two_factor_for_admin_mode.rb @gitlab-org/manage/authentication-and-authorization/approvers +/app/controllers/concerns/enforces_admin_authentication.rb @gitlab-org/manage/authentication-and-authorization/approvers +/app/controllers/concerns/enforces_two_factor_authentication.rb @gitlab-org/manage/authentication-and-authorization/approvers +/app/controllers/concerns/oauth_applications.rb @gitlab-org/manage/authentication-and-authorization/approvers +/app/controllers/concerns/project_unauthorized.rb @gitlab-org/manage/authentication-and-authorization/approvers +/app/controllers/concerns/sessionless_authentication.rb @gitlab-org/manage/authentication-and-authorization/approvers +/app/controllers/concerns/snippet_authorizations.rb @gitlab-org/manage/authentication-and-authorization/approvers +/app/controllers/concerns/workhorse_authorization.rb @gitlab-org/manage/authentication-and-authorization/approvers +/app/controllers/groups/settings/access_tokens_controller.rb @gitlab-org/manage/authentication-and-authorization/approvers +/app/controllers/ldap/ @gitlab-org/manage/authentication-and-authorization/approvers +/app/controllers/oauth/ @gitlab-org/manage/authentication-and-authorization/approvers +/app/controllers/omniauth_callbacks_controller.rb @gitlab-org/manage/authentication-and-authorization/approvers +/app/controllers/passwords_controller.rb @gitlab-org/manage/authentication-and-authorization/approvers +/app/controllers/profiles/passwords_controller.rb @gitlab-org/manage/authentication-and-authorization/approvers +/app/controllers/profiles/personal_access_tokens_controller.rb @gitlab-org/manage/authentication-and-authorization/approvers +/app/controllers/profiles/two_factor_auths_controller.rb @gitlab-org/manage/authentication-and-authorization/approvers +/app/controllers/profiles/webauthn_registrations_controller.rb @gitlab-org/manage/authentication-and-authorization/approvers +/app/controllers/projects/settings/access_tokens_controller.rb @gitlab-org/manage/authentication-and-authorization/approvers +/app/finders/groups/projects_requiring_authorizations_refresh/ @gitlab-org/manage/authentication-and-authorization/approvers +/app/finders/personal_access_tokens_finder.rb @gitlab-org/manage/authentication-and-authorization/approvers +/app/helpers/access_tokens_helper.rb @gitlab-org/manage/authentication-and-authorization/approvers +/app/helpers/auth_helper.rb @gitlab-org/manage/authentication-and-authorization/approvers +/app/models/authentication_event.rb @gitlab-org/manage/authentication-and-authorization/approvers +/app/models/concerns/admin_changed_password_notifier.rb @gitlab-org/manage/authentication-and-authorization/approvers +/app/models/concerns/mirror_authentication.rb @gitlab-org/manage/authentication-and-authorization/approvers +/app/models/concerns/select_for_project_authorization.rb @gitlab-org/manage/authentication-and-authorization/approvers +/app/models/concerns/token_authenticatable.rb @gitlab-org/manage/authentication-and-authorization/approvers +/app/models/concerns/token_authenticatable_strategies/ @gitlab-org/manage/authentication-and-authorization/approvers +/app/models/oauth_access_grant.rb @gitlab-org/manage/authentication-and-authorization/approvers +/app/models/oauth_access_token.rb @gitlab-org/manage/authentication-and-authorization/approvers +/app/models/personal_access_token.rb @gitlab-org/manage/authentication-and-authorization/approvers +/app/models/project_authorization.rb @gitlab-org/manage/authentication-and-authorization/approvers +/app/models/token_with_iv.rb @gitlab-org/manage/authentication-and-authorization/approvers +/app/models/webauthn_registration.rb @gitlab-org/manage/authentication-and-authorization/approvers +/app/policies/personal_access_token_policy.rb @gitlab-org/manage/authentication-and-authorization/approvers +/app/services/access_token_validation_service.rb @gitlab-org/manage/authentication-and-authorization/approvers +/app/services/auth/ @gitlab-org/manage/authentication-and-authorization/approvers +/app/services/authorized_project_update/ @gitlab-org/manage/authentication-and-authorization/approvers +/app/services/chat_names/authorize_user_service.rb @gitlab-org/manage/authentication-and-authorization/approvers +/app/services/personal_access_tokens/ @gitlab-org/manage/authentication-and-authorization/approvers +/app/services/projects/move_project_authorizations_service.rb @gitlab-org/manage/authentication-and-authorization/approvers +/app/services/resource_access_tokens/ @gitlab-org/manage/authentication-and-authorization/approvers +/app/services/todos/destroy/unauthorized_features_service.rb @gitlab-org/manage/authentication-and-authorization/approvers +/app/services/users/authorized_build_service.rb @gitlab-org/manage/authentication-and-authorization/approvers +/app/services/users/authorized_create_service.rb @gitlab-org/manage/authentication-and-authorization/approvers +/app/services/users/refresh_authorized_projects_service.rb @gitlab-org/manage/authentication-and-authorization/approvers +/app/services/webauthn/ @gitlab-org/manage/authentication-and-authorization/approvers +/app/validators/json_schemas/cluster_agent_authorization_configuration.json @gitlab-org/manage/authentication-and-authorization/approvers +/app/views/admin/application_settings/_external_authorization_service_form.html.haml @gitlab-org/manage/authentication-and-authorization/approvers +/app/views/admin/impersonation_tokens/ @gitlab-org/manage/authentication-and-authorization/approvers +/app/views/authentication/ @gitlab-org/manage/authentication-and-authorization/approvers +/app/views/ci/token_access/ @gitlab-org/manage/authentication-and-authorization/approvers +/app/views/dashboard/projects/_zero_authorized_projects.html.haml @gitlab-org/manage/authentication-and-authorization/approvers +/app/views/devise/mailer/password_change.html.haml @gitlab-org/manage/authentication-and-authorization/approvers +/app/views/devise/mailer/password_change.text.erb @gitlab-org/manage/authentication-and-authorization/approvers +/app/views/devise/mailer/password_change_by_admin.html.haml @gitlab-org/manage/authentication-and-authorization/approvers +/app/views/devise/mailer/password_change_by_admin.text.erb @gitlab-org/manage/authentication-and-authorization/approvers +/app/views/devise/mailer/reset_password_instructions.html.haml @gitlab-org/manage/authentication-and-authorization/approvers +/app/views/devise/mailer/reset_password_instructions.text.erb @gitlab-org/manage/authentication-and-authorization/approvers +/app/views/devise/passwords/ @gitlab-org/manage/authentication-and-authorization/approvers +/app/views/devise/shared/_omniauth_box.html.haml @gitlab-org/manage/authentication-and-authorization/approvers +/app/views/devise/shared/_signup_omniauth_provider_list.haml @gitlab-org/manage/authentication-and-authorization/approvers +/app/views/devise/shared/_signup_omniauth_providers.haml @gitlab-org/manage/authentication-and-authorization/approvers +/app/views/devise/shared/_signup_omniauth_providers_top.haml @gitlab-org/manage/authentication-and-authorization/approvers +/app/views/doorkeeper/authorizations/ @gitlab-org/manage/authentication-and-authorization/approvers +/app/views/doorkeeper/authorized_applications/ @gitlab-org/manage/authentication-and-authorization/approvers +/app/views/errors/omniauth_error.html.haml @gitlab-org/manage/authentication-and-authorization/approvers +/app/views/groups/settings/_resource_access_token_creation.html.haml @gitlab-org/manage/authentication-and-authorization/approvers +/app/views/groups/settings/_two_factor_auth.html.haml @gitlab-org/manage/authentication-and-authorization/approvers +/app/views/groups/settings/access_tokens/ @gitlab-org/manage/authentication-and-authorization/approvers +/app/views/layouts/oauth_error.html.haml @gitlab-org/manage/authentication-and-authorization/approvers +/app/views/notify/access_token_about_to_expire_email.html.haml @gitlab-org/manage/authentication-and-authorization/approvers +/app/views/notify/access_token_about_to_expire_email.text.erb @gitlab-org/manage/authentication-and-authorization/approvers +/app/views/notify/access_token_created_email.html.haml @gitlab-org/manage/authentication-and-authorization/approvers +/app/views/notify/access_token_created_email.text.erb @gitlab-org/manage/authentication-and-authorization/approvers +/app/views/notify/access_token_expired_email.html.haml @gitlab-org/manage/authentication-and-authorization/approvers +/app/views/notify/access_token_expired_email.text.erb @gitlab-org/manage/authentication-and-authorization/approvers +/app/views/profiles/passwords/ @gitlab-org/manage/authentication-and-authorization/approvers +/app/views/profiles/personal_access_tokens/ @gitlab-org/manage/authentication-and-authorization/approvers +/app/views/profiles/two_factor_auths/ @gitlab-org/manage/authentication-and-authorization/approvers +/app/views/projects/mirrors/_authentication_method.html.haml @gitlab-org/manage/authentication-and-authorization/approvers +/app/views/projects/settings/access_tokens/ @gitlab-org/manage/authentication-and-authorization/approvers +/app/views/shared/_no_password.html.haml @gitlab-org/manage/authentication-and-authorization/approvers +/app/views/shared/_two_factor_auth_recovery_settings_check.html.haml @gitlab-org/manage/authentication-and-authorization/approvers +/app/views/shared/access_tokens/ @gitlab-org/manage/authentication-and-authorization/approvers +/app/views/shared/members/_two_factor_auth_badge.html.haml @gitlab-org/manage/authentication-and-authorization/approvers +/app/views/shared/tokens/ @gitlab-org/manage/authentication-and-authorization/approvers +/app/workers/authorized_keys_worker.rb @gitlab-org/manage/authentication-and-authorization/approvers +/app/workers/authorized_project_update/ @gitlab-org/manage/authentication-and-authorization/approvers +/app/workers/authorized_projects_worker.rb @gitlab-org/manage/authentication-and-authorization/approvers +/app/workers/personal_access_tokens/ @gitlab-org/manage/authentication-and-authorization/approvers +/config/feature_flags/development/access_token_pagination.yml @gitlab-org/manage/authentication-and-authorization/approvers +/config/feature_flags/development/application_settings_tokens_optional_encryption.yml @gitlab-org/manage/authentication-and-authorization/approvers +/config/feature_flags/development/enforce_auth_checks_on_uploads.yml @gitlab-org/manage/authentication-and-authorization/approvers +/config/feature_flags/development/forti_authenticator.yml @gitlab-org/manage/authentication-and-authorization/approvers +/config/feature_flags/development/forti_token_cloud.yml @gitlab-org/manage/authentication-and-authorization/approvers +/config/feature_flags/development/groups_tokens_optional_encryption.yml @gitlab-org/manage/authentication-and-authorization/approvers +/config/feature_flags/development/pbkdf2_password_encryption.yml @gitlab-org/manage/authentication-and-authorization/approvers +/config/feature_flags/development/pbkdf2_password_encryption_write.yml @gitlab-org/manage/authentication-and-authorization/approvers +/config/feature_flags/development/projects_tokens_optional_encryption.yml @gitlab-org/manage/authentication-and-authorization/approvers +/config/feature_flags/development/skip_group_share_unlink_auth_refresh.yml @gitlab-org/manage/authentication-and-authorization/approvers +/config/feature_flags/development/specialized_worker_for_group_lock_update_auth_recalculation.yml @gitlab-org/manage/authentication-and-authorization/approvers +/config/feature_flags/development/update_oauth_registration_flow.yml @gitlab-org/manage/authentication-and-authorization/approvers +/config/feature_flags/development/webauthn.yml @gitlab-org/manage/authentication-and-authorization/approvers +/config/feature_flags/ops/block_password_auth_for_saml_users.yml @gitlab-org/manage/authentication-and-authorization/approvers +/config/initializers/01_secret_token.rb @gitlab-org/manage/authentication-and-authorization/approvers +/config/initializers/devise_dynamic_password_length_validation.rb @gitlab-org/manage/authentication-and-authorization/approvers +/config/initializers/devise_password_length.rb.example @gitlab-org/manage/authentication-and-authorization/approvers +/config/initializers/gitlab_shell_secret_token.rb @gitlab-org/manage/authentication-and-authorization/approvers +/config/initializers/omniauth.rb @gitlab-org/manage/authentication-and-authorization/approvers +/config/initializers/rails_host_authorization.rb @gitlab-org/manage/authentication-and-authorization/approvers +/config/initializers/rails_host_authorization_gitpod.rb @gitlab-org/manage/authentication-and-authorization/approvers +/config/initializers/webauthn.rb @gitlab-org/manage/authentication-and-authorization/approvers +/config/initializers_before_autoloader/100_patch_omniauth_oauth2.rb @gitlab-org/manage/authentication-and-authorization/approvers +/config/initializers_before_autoloader/100_patch_omniauth_saml.rb @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/assets/javascripts/access_tokens/ @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/assets/javascripts/audit_events/components/tokens/ @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/assets/javascripts/audit_events/token_utils.js @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/assets/javascripts/groups/settings/components/ @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/assets/javascripts/pages/admin/application_settings/general/components/ @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/assets/javascripts/pages/groups/omniauth_callbacks/ @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/assets/javascripts/pages/passwords/ @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/assets/javascripts/pages/profiles/passwords/ @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/assets/javascripts/password/ @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/assets/javascripts/pipelines/components/pipelines_list/ @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/assets/javascripts/requirements/components/tokens/ @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/assets/javascripts/runner/components/search_tokens/ @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/assets/javascripts/saml_providers/scim_token_service.js @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/assets/javascripts/saml_sso/components/ @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals_auth.vue @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/controllers/concerns/ee/authenticates_with_two_factor.rb @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/controllers/concerns/ee/enforces_two_factor_authentication.rb @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/controllers/concerns/saml_authorization.rb @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/controllers/ee/ldap/ @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/controllers/ee/omniauth_callbacks_controller.rb @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/controllers/ee/passwords_controller.rb @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/controllers/groups/omniauth_callbacks_controller.rb @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/controllers/groups/scim_oauth_controller.rb @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/controllers/oauth/ @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/controllers/omniauth_kerberos_spnego_controller.rb @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/finders/auth/ @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/helpers/ee/access_tokens_helper.rb @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/helpers/ee/auth_helper.rb @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/helpers/ee/personal_access_tokens_helper.rb @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/models/concerns/password_complexity.rb @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/models/ee/personal_access_token.rb @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/models/ee/project_authorization.rb @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/models/scim_oauth_access_token.rb @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/serializers/scim_oauth_access_token_entity.rb @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/services/ee/auth/ @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/services/ee/personal_access_tokens/ @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/services/ee/resource_access_tokens/ @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/services/personal_access_tokens/ @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/services/security/token_revocation_service.rb @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/validators/password/ @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/views/admin/application_settings/_personal_access_token_expiration_policy.html.haml @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/views/credentials_inventory_mailer/personal_access_token_revoked_email.html.haml @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/views/credentials_inventory_mailer/personal_access_token_revoked_email.text.haml @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/views/groups/_personal_access_token_expiration_policy.html.haml @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/views/groups/sso/_authorize_pane.html.haml @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/views/notify/policy_revoked_personal_access_tokens_email.html.haml @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/views/notify/policy_revoked_personal_access_tokens_email.text.erb @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/views/oauth/ @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/views/shared/_password_requirements_list.html.haml @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/views/shared/credentials_inventory/_personal_access_tokens.html.haml @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/views/shared/credentials_inventory/_project_access_tokens.html.haml @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/views/shared/credentials_inventory/personal_access_tokens/ @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/views/shared/credentials_inventory/project_access_tokens/ @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/workers/auth/ @gitlab-org/manage/authentication-and-authorization/approvers +/ee/app/workers/personal_access_tokens/ @gitlab-org/manage/authentication-and-authorization/approvers +/ee/config/routes/oauth.rb @gitlab-org/manage/authentication-and-authorization/approvers +/ee/lib/ee/gitlab/auth/ @gitlab-org/manage/authentication-and-authorization/approvers +/ee/lib/ee/gitlab/omniauth_initializer.rb @gitlab-org/manage/authentication-and-authorization/approvers +/ee/lib/gitlab/auth/ @gitlab-org/manage/authentication-and-authorization/approvers +/ee/lib/gitlab/auth_logger.rb @gitlab-org/manage/authentication-and-authorization/approvers +/ee/lib/gitlab/authority_analyzer.rb @gitlab-org/manage/authentication-and-authorization/approvers +/ee/lib/gitlab/geo/oauth/ @gitlab-org/manage/authentication-and-authorization/approvers +/ee/lib/gitlab/kerberos/ @gitlab-org/manage/authentication-and-authorization/approvers +/ee/lib/omni_auth/ @gitlab-org/manage/authentication-and-authorization/approvers +/ee/lib/system_check/geo/authorized_keys_check.rb @gitlab-org/manage/authentication-and-authorization/approvers +/ee/lib/system_check/geo/authorized_keys_flag_check.rb @gitlab-org/manage/authentication-and-authorization/approvers +/lib/api/entities/ci/reset_token_result.rb @gitlab-org/manage/authentication-and-authorization/approvers +/lib/api/entities/impersonation_token.rb @gitlab-org/manage/authentication-and-authorization/approvers +/lib/api/entities/impersonation_token_with_token.rb @gitlab-org/manage/authentication-and-authorization/approvers +/lib/api/entities/personal_access_token.rb @gitlab-org/manage/authentication-and-authorization/approvers +/lib/api/entities/personal_access_token_with_details.rb @gitlab-org/manage/authentication-and-authorization/approvers +/lib/api/entities/personal_access_token_with_token.rb @gitlab-org/manage/authentication-and-authorization/approvers +/lib/api/entities/resource_access_token.rb @gitlab-org/manage/authentication-and-authorization/approvers +/lib/api/entities/resource_access_token_with_token.rb @gitlab-org/manage/authentication-and-authorization/approvers +/lib/api/helpers/authentication.rb @gitlab-org/manage/authentication-and-authorization/approvers +/lib/api/helpers/packages/basic_auth_helpers.rb @gitlab-org/manage/authentication-and-authorization/approvers +/lib/api/personal_access_tokens.rb @gitlab-org/manage/authentication-and-authorization/approvers +/lib/api/resource_access_tokens.rb @gitlab-org/manage/authentication-and-authorization/approvers +/lib/api/support/token_with_expiration.rb @gitlab-org/manage/authentication-and-authorization/approvers +/lib/gitlab/api_authentication/ @gitlab-org/manage/authentication-and-authorization/approvers +/lib/gitlab/auth/ @gitlab-org/manage/authentication-and-authorization/approvers +/lib/gitlab/auth.rb @gitlab-org/manage/authentication-and-authorization/approvers +/lib/gitlab/auth_logger.rb @gitlab-org/manage/authentication-and-authorization/approvers +/lib/gitlab/authorized_keys.rb @gitlab-org/manage/authentication-and-authorization/approvers +/lib/gitlab/background_migration/encrypt_static_object_token.rb @gitlab-org/manage/authentication-and-authorization/approvers +/lib/gitlab/background_migration/expire_o_auth_tokens.rb @gitlab-org/manage/authentication-and-authorization/approvers +/lib/gitlab/background_migration/migrate_u2f_webauthn.rb @gitlab-org/manage/authentication-and-authorization/approvers +/lib/gitlab/background_migration/update_users_where_two_factor_auth_required_from_group.rb @gitlab-org/manage/authentication-and-authorization/approvers +/lib/gitlab/chat_name_token.rb @gitlab-org/manage/authentication-and-authorization/approvers +/lib/gitlab/ci/pipeline/expression/token.rb @gitlab-org/manage/authentication-and-authorization/approvers +/lib/gitlab/external_authorization/ @gitlab-org/manage/authentication-and-authorization/approvers +/lib/gitlab/external_authorization.rb @gitlab-org/manage/authentication-and-authorization/approvers +/lib/gitlab/grape_logging/loggers/token_logger.rb @gitlab-org/manage/authentication-and-authorization/approvers +/lib/gitlab/graphql/authorize/ @gitlab-org/manage/authentication-and-authorization/approvers +/lib/gitlab/jwt_authenticatable.rb @gitlab-org/manage/authentication-and-authorization/approvers +/lib/gitlab/jwt_token.rb @gitlab-org/manage/authentication-and-authorization/approvers +/lib/gitlab/lfs_token.rb @gitlab-org/manage/authentication-and-authorization/approvers +/lib/gitlab/mail_room/ @gitlab-org/manage/authentication-and-authorization/approvers +/lib/gitlab/omniauth_initializer.rb @gitlab-org/manage/authentication-and-authorization/approvers +/lib/gitlab/project_authorizations.rb @gitlab-org/manage/authentication-and-authorization/approvers +/lib/json_web_token/ @gitlab-org/manage/authentication-and-authorization/approvers +/lib/omni_auth/ @gitlab-org/manage/authentication-and-authorization/approvers +/lib/system_check/app/authorized_keys_permission_check.rb @gitlab-org/manage/authentication-and-authorization/approvers +/lib/system_check/incoming_email/imap_authentication_check.rb @gitlab-org/manage/authentication-and-authorization/approvers +/lib/tasks/gitlab/password.rake @gitlab-org/manage/authentication-and-authorization/approvers +/lib/tasks/tokens.rake @gitlab-org/manage/authentication-and-authorization/approvers diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 908da71a8e4..5a655c92e46 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -83,8 +83,9 @@ class SearchController < ApplicationController @project = search_service.project @ref = params[:project_ref] if params[:project_ref].present? + @filter = params[:filter] - render json: search_autocomplete_opts(term).to_json + render json: search_autocomplete_opts(term, filter: @filter).to_json end def opensearch diff --git a/app/graphql/mutations/uploads/delete.rb b/app/graphql/mutations/uploads/delete.rb new file mode 100644 index 00000000000..e2fb967cd2c --- /dev/null +++ b/app/graphql/mutations/uploads/delete.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module Mutations + module Uploads + class Delete < BaseMutation + graphql_name 'UploadDelete' + description 'Deletes an upload.' + + include Mutations::ResolvesResourceParent + + authorize :destroy_upload + + argument :secret, GraphQL::Types::String, + required: true, + description: 'Secret part of upload path.' + + argument :filename, GraphQL::Types::String, + required: true, + description: 'Upload filename.' + + field :upload, Types::UploadType, + null: true, + description: 'Deleted upload.' + + def resolve(args) + parent = authorized_resource_parent_find!(args) + + result = ::Uploads::DestroyService.new(parent, current_user).execute(args[:secret], args[:filename]) + + { + upload: result[:status] == :success ? result[:upload] : nil, + errors: Array(result[:message]) + } + end + end + end +end diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb index 3907b096c2c..dc9eb369dc8 100644 --- a/app/graphql/types/mutation_type.rb +++ b/app/graphql/types/mutation_type.rb @@ -151,6 +151,7 @@ module Types mount_mutation Mutations::SavedReplies::Update mount_mutation Mutations::Pages::MarkOnboardingComplete mount_mutation Mutations::SavedReplies::Destroy + mount_mutation Mutations::Uploads::Delete end end diff --git a/app/graphql/types/upload_type.rb b/app/graphql/types/upload_type.rb new file mode 100644 index 00000000000..68792fa526f --- /dev/null +++ b/app/graphql/types/upload_type.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module Types + class UploadType < BaseObject + graphql_name 'FileUpload' + + authorize :read_upload + + field :id, Types::GlobalIDType[::Upload], null: false, + description: 'Global ID of the upload.' + field :path, GraphQL::Types::String, null: false, + description: 'Path of the upload.' + field :size, GraphQL::Types::Int, null: false, + description: 'Size of the upload in bytes.' + end +end diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index ecbcaec27bc..6e2e0a86446 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -14,28 +14,42 @@ module SearchHelper :project_ids ].freeze - def search_autocomplete_opts(term) + def search_autocomplete_opts(term, filter: nil) return unless current_user - resources_results = [ - recent_items_autocomplete(term), + results = case filter&.to_sym + when :search + resource_results(term) + when :generic + [ + generic_results(term), + recent_items_autocomplete(term) + ] + else + [ + generic_results(term), + resource_results(term), + recent_items_autocomplete(term) + ] + end + + results.flatten { |item| item[:label] } + end + + def resource_results(term) + [ groups_autocomplete(term), projects_autocomplete(term), issue_autocomplete(term) ].flatten + end + def generic_results(term) search_pattern = Regexp.new(Regexp.escape(term), "i") generic_results = project_autocomplete + default_autocomplete + help_autocomplete generic_results.concat(default_autocomplete_admin) if current_user.admin? - generic_results.select! { |result| result[:label] =~ search_pattern } - - [ - resources_results, - generic_results - ].flatten do |item| - item[:label] - end + generic_results.select { |result| result[:label] =~ search_pattern } end def recent_items_autocomplete(term) diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb index 50b6f4bbe15..0ee759a6ad6 100644 --- a/app/policies/group_policy.rb +++ b/app/policies/group_policy.rb @@ -181,6 +181,8 @@ class GroupPolicy < Namespaces::GroupProjectNamespaceSharedPolicy enable :create_jira_connect_subscription enable :maintainer_access enable :maintain_namespace + enable :read_upload + enable :destroy_upload end rule { owner }.policy do diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index 70e8542c44a..9d121715d2c 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -498,6 +498,8 @@ class ProjectPolicy < BasePolicy enable :admin_project_google_cloud enable :admin_secure_files enable :read_web_hooks + enable :read_upload + enable :destroy_upload end rule { public_project & metrics_dashboard_allowed }.policy do diff --git a/app/policies/upload_policy.rb b/app/policies/upload_policy.rb new file mode 100644 index 00000000000..c7fde5d9df4 --- /dev/null +++ b/app/policies/upload_policy.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class UploadPolicy < BasePolicy # rubocop:disable Gitlab/NamespacedClass + delegate { @subject.model } +end diff --git a/app/services/uploads/destroy_service.rb b/app/services/uploads/destroy_service.rb new file mode 100644 index 00000000000..1f0d99ff7bb --- /dev/null +++ b/app/services/uploads/destroy_service.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module Uploads + class DestroyService < BaseService + attr_accessor :model, :current_user + + def initialize(model, user = nil) + @model = model + @current_user = user + end + + def execute(secret, filename) + upload = find_upload(secret, filename) + + unless current_user && upload && current_user.can?(:destroy_upload, upload) + return error(_("The resource that you are attempting to access does not "\ + "exist or you don't have permission to perform this action.")) + end + + if upload.destroy + success(upload: upload) + else + error(_('Upload could not be deleted.')) + end + end + + private + + # rubocop: disable CodeReuse/ActiveRecord + def find_upload(secret, filename) + uploader = uploader_class.new(model, secret: secret) + upload_paths = uploader.upload_paths(filename) + + Upload.find_by(model: model, uploader: uploader_class.to_s, path: upload_paths) + rescue FileUploader::InvalidSecret + nil + end + # rubocop: enable CodeReuse/ActiveRecord + + def uploader_class + case model + when Group + NamespaceFileUploader + when Project + FileUploader + else + raise ArgumentError, "unknown uploader for #{model.class.name}" + end + end + end +end diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb index 68a0934e2b7..329ccfc6362 100644 --- a/app/workers/post_receive.rb +++ b/app/workers/post_receive.rb @@ -85,6 +85,7 @@ class PostReceive replicate_snippet_changes(snippet) expire_caches(post_received, snippet.repository) + snippet.touch Snippets::UpdateStatisticsService.new(snippet).execute end diff --git a/config/metrics/aggregates/code_review.yml b/config/metrics/aggregates/code_review.yml index 64f7825dce0..15ef2c06a01 100644 --- a/config/metrics/aggregates/code_review.yml +++ b/config/metrics/aggregates/code_review.yml @@ -92,6 +92,12 @@ - 'i_code_review_merge_request_widget_accessibility_expand_success' - 'i_code_review_merge_request_widget_accessibility_expand_warning' - 'i_code_review_merge_request_widget_accessibility_expand_failed' + - 'i_code_review_merge_request_widget_code_quality_view' + - 'i_code_review_merge_request_widget_code_quality_full_report_clicked' + - 'i_code_review_merge_request_widget_code_quality_expand' + - 'i_code_review_merge_request_widget_code_quality_expand_success' + - 'i_code_review_merge_request_widget_code_quality_expand_warning' + - 'i_code_review_merge_request_widget_code_quality_expand_failed' - name: code_review_category_monthly_active_users operator: OR source: redis @@ -172,6 +178,12 @@ - 'i_code_review_merge_request_widget_accessibility_expand_success' - 'i_code_review_merge_request_widget_accessibility_expand_warning' - 'i_code_review_merge_request_widget_accessibility_expand_failed' + - 'i_code_review_merge_request_widget_code_quality_view' + - 'i_code_review_merge_request_widget_code_quality_full_report_clicked' + - 'i_code_review_merge_request_widget_code_quality_expand' + - 'i_code_review_merge_request_widget_code_quality_expand_success' + - 'i_code_review_merge_request_widget_code_quality_expand_warning' + - 'i_code_review_merge_request_widget_code_quality_expand_failed' - name: code_review_extension_category_monthly_active_users operator: OR source: redis diff --git a/config/metrics/counts_28d/20220727020440_i_code_review_merge_request_widget_code_quality_view_monthly.yml b/config/metrics/counts_28d/20220727020440_i_code_review_merge_request_widget_code_quality_view_monthly.yml new file mode 100644 index 00000000000..a73f6a919ea --- /dev/null +++ b/config/metrics/counts_28d/20220727020440_i_code_review_merge_request_widget_code_quality_view_monthly.yml @@ -0,0 +1,25 @@ +--- +key_path: redis_hll_counters.code_review.i_code_review_merge_request_widget_code_quality_view_monthly +description: The count of unique users (monthly) who were able to see the Code Quality widget extension +product_section: dev +product_stage: create +product_group: code_review +product_category: code_review +value_type: number +status: active +milestone: "15.3" +introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93333" +time_frame: 28d +data_source: redis_hll +data_category: optional +instrumentation_class: RedisHLLMetric +options: + events: + - i_code_review_merge_request_widget_code_quality_view +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate diff --git a/config/metrics/counts_28d/20220727020446_i_code_review_merge_request_widget_code_quality_full_report_clicked_monthly.yml b/config/metrics/counts_28d/20220727020446_i_code_review_merge_request_widget_code_quality_full_report_clicked_monthly.yml new file mode 100644 index 00000000000..3fbd759deab --- /dev/null +++ b/config/metrics/counts_28d/20220727020446_i_code_review_merge_request_widget_code_quality_full_report_clicked_monthly.yml @@ -0,0 +1,25 @@ +--- +key_path: redis_hll_counters.code_review.i_code_review_merge_request_widget_code_quality_full_report_clicked_monthly +description: The count of unique users (monthly) who clicked the Full Report button on the Code Quality widget extension +product_section: dev +product_stage: create +product_group: code_review +product_category: code_review +value_type: number +status: active +milestone: "15.3" +introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93333" +time_frame: 28d +data_source: redis_hll +data_category: optional +instrumentation_class: RedisHLLMetric +options: + events: + - i_code_review_merge_request_widget_code_quality_full_report_clicked +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate diff --git a/config/metrics/counts_28d/20220727020452_i_code_review_merge_request_widget_code_quality_expand_monthly.yml b/config/metrics/counts_28d/20220727020452_i_code_review_merge_request_widget_code_quality_expand_monthly.yml new file mode 100644 index 00000000000..3b4686c28cc --- /dev/null +++ b/config/metrics/counts_28d/20220727020452_i_code_review_merge_request_widget_code_quality_expand_monthly.yml @@ -0,0 +1,25 @@ +--- +key_path: redis_hll_counters.code_review.i_code_review_merge_request_widget_code_quality_expand_monthly +description: The count of unique users (monthly) who expanded the Code Quality widget extension +product_section: dev +product_stage: create +product_group: code_review +product_category: code_review +value_type: number +status: active +milestone: "15.3" +introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93333" +time_frame: 28d +data_source: redis_hll +data_category: optional +instrumentation_class: RedisHLLMetric +options: + events: + - i_code_review_merge_request_widget_code_quality_expand +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate diff --git a/config/metrics/counts_28d/20220727020457_i_code_review_merge_request_widget_code_quality_expand_success_monthly.yml b/config/metrics/counts_28d/20220727020457_i_code_review_merge_request_widget_code_quality_expand_success_monthly.yml new file mode 100644 index 00000000000..e36a74550d4 --- /dev/null +++ b/config/metrics/counts_28d/20220727020457_i_code_review_merge_request_widget_code_quality_expand_success_monthly.yml @@ -0,0 +1,25 @@ +--- +key_path: redis_hll_counters.code_review.i_code_review_merge_request_widget_code_quality_expand_success_monthly +description: The count of unique users (monthly) who expanded the Code Quality widget extension while it is in its Success state +product_section: dev +product_stage: create +product_group: code_review +product_category: code_review +value_type: number +status: active +milestone: "15.3" +introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93333" +time_frame: 28d +data_source: redis_hll +data_category: optional +instrumentation_class: RedisHLLMetric +options: + events: + - i_code_review_merge_request_widget_code_quality_expand_success +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate diff --git a/config/metrics/counts_28d/20220727020503_i_code_review_merge_request_widget_code_quality_expand_warning_monthly.yml b/config/metrics/counts_28d/20220727020503_i_code_review_merge_request_widget_code_quality_expand_warning_monthly.yml new file mode 100644 index 00000000000..181c06ec06f --- /dev/null +++ b/config/metrics/counts_28d/20220727020503_i_code_review_merge_request_widget_code_quality_expand_warning_monthly.yml @@ -0,0 +1,25 @@ +--- +key_path: redis_hll_counters.code_review.i_code_review_merge_request_widget_code_quality_expand_warning_monthly +description: The count of unique users (monthly) who expanded the Code Quality widget extension while it is in its Warning state +product_section: dev +product_stage: create +product_group: code_review +product_category: code_review +value_type: number +status: active +milestone: "15.3" +introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93333" +time_frame: 28d +data_source: redis_hll +data_category: optional +instrumentation_class: RedisHLLMetric +options: + events: + - i_code_review_merge_request_widget_code_quality_expand_warning +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate diff --git a/config/metrics/counts_28d/20220727020509_i_code_review_merge_request_widget_code_quality_expand_failed_monthly.yml b/config/metrics/counts_28d/20220727020509_i_code_review_merge_request_widget_code_quality_expand_failed_monthly.yml new file mode 100644 index 00000000000..5e4e6221142 --- /dev/null +++ b/config/metrics/counts_28d/20220727020509_i_code_review_merge_request_widget_code_quality_expand_failed_monthly.yml @@ -0,0 +1,25 @@ +--- +key_path: redis_hll_counters.code_review.i_code_review_merge_request_widget_code_quality_expand_failed_monthly +description: The count of unique users (monthly) who expanded the Code Quality widget extension while it is in its Failed state +product_section: dev +product_stage: create +product_group: code_review +product_category: code_review +value_type: number +status: active +milestone: "15.3" +introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93333" +time_frame: 28d +data_source: redis_hll +data_category: optional +instrumentation_class: RedisHLLMetric +options: + events: + - i_code_review_merge_request_widget_code_quality_expand_failed +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate diff --git a/config/metrics/counts_7d/20220727020407_i_code_review_merge_request_widget_code_quality_view_weekly.yml b/config/metrics/counts_7d/20220727020407_i_code_review_merge_request_widget_code_quality_view_weekly.yml new file mode 100644 index 00000000000..029103609cd --- /dev/null +++ b/config/metrics/counts_7d/20220727020407_i_code_review_merge_request_widget_code_quality_view_weekly.yml @@ -0,0 +1,25 @@ +--- +key_path: redis_hll_counters.code_review.i_code_review_merge_request_widget_code_quality_view_weekly +description: The count of unique users (weekly) who were able to see the Code Quality widget extension +product_section: dev +product_stage: create +product_group: code_review +product_category: code_review +value_type: number +status: active +milestone: "15.3" +introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93333" +time_frame: 7d +data_source: redis_hll +data_category: optional +instrumentation_class: RedisHLLMetric +options: + events: + - i_code_review_merge_request_widget_code_quality_view +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate diff --git a/config/metrics/counts_7d/20220727020413_i_code_review_merge_request_widget_code_quality_full_report_clicked_weekly.yml b/config/metrics/counts_7d/20220727020413_i_code_review_merge_request_widget_code_quality_full_report_clicked_weekly.yml new file mode 100644 index 00000000000..d6e9d88d395 --- /dev/null +++ b/config/metrics/counts_7d/20220727020413_i_code_review_merge_request_widget_code_quality_full_report_clicked_weekly.yml @@ -0,0 +1,25 @@ +--- +key_path: redis_hll_counters.code_review.i_code_review_merge_request_widget_code_quality_full_report_clicked_weekly +description: The count of unique users (weekly) who clicked the Full Report button on the Code Quality widget extension +product_section: dev +product_stage: create +product_group: code_review +product_category: code_review +value_type: number +status: active +milestone: "15.3" +introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93333" +time_frame: 7d +data_source: redis_hll +data_category: optional +instrumentation_class: RedisHLLMetric +options: + events: + - i_code_review_merge_request_widget_code_quality_full_report_clicked +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate diff --git a/config/metrics/counts_7d/20220727020419_i_code_review_merge_request_widget_code_quality_expand_weekly.yml b/config/metrics/counts_7d/20220727020419_i_code_review_merge_request_widget_code_quality_expand_weekly.yml new file mode 100644 index 00000000000..7d35f90ff4d --- /dev/null +++ b/config/metrics/counts_7d/20220727020419_i_code_review_merge_request_widget_code_quality_expand_weekly.yml @@ -0,0 +1,25 @@ +--- +key_path: redis_hll_counters.code_review.i_code_review_merge_request_widget_code_quality_expand_weekly +description: The count of unique users (weekly) who expanded the Code Quality widget extension +product_section: dev +product_stage: create +product_group: code_review +product_category: code_review +value_type: number +status: active +milestone: "15.3" +introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93333" +time_frame: 7d +data_source: redis_hll +data_category: optional +instrumentation_class: RedisHLLMetric +options: + events: + - i_code_review_merge_request_widget_code_quality_expand +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate diff --git a/config/metrics/counts_7d/20220727020424_i_code_review_merge_request_widget_code_quality_expand_success_weekly.yml b/config/metrics/counts_7d/20220727020424_i_code_review_merge_request_widget_code_quality_expand_success_weekly.yml new file mode 100644 index 00000000000..a0c94702a5f --- /dev/null +++ b/config/metrics/counts_7d/20220727020424_i_code_review_merge_request_widget_code_quality_expand_success_weekly.yml @@ -0,0 +1,25 @@ +--- +key_path: redis_hll_counters.code_review.i_code_review_merge_request_widget_code_quality_expand_success_weekly +description: The count of unique users (weekly) who expanded the Code Quality widget extension while it is in its Success state +product_section: dev +product_stage: create +product_group: code_review +product_category: code_review +value_type: number +status: active +milestone: "15.3" +introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93333" +time_frame: 7d +data_source: redis_hll +data_category: optional +instrumentation_class: RedisHLLMetric +options: + events: + - i_code_review_merge_request_widget_code_quality_expand_success +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate diff --git a/config/metrics/counts_7d/20220727020429_i_code_review_merge_request_widget_code_quality_expand_warning_weekly.yml b/config/metrics/counts_7d/20220727020429_i_code_review_merge_request_widget_code_quality_expand_warning_weekly.yml new file mode 100644 index 00000000000..c88c435eaf2 --- /dev/null +++ b/config/metrics/counts_7d/20220727020429_i_code_review_merge_request_widget_code_quality_expand_warning_weekly.yml @@ -0,0 +1,25 @@ +--- +key_path: redis_hll_counters.code_review.i_code_review_merge_request_widget_code_quality_expand_warning_weekly +description: The count of unique users (weekly) who expanded the Code Quality widget extension while it is in its Warning state +product_section: dev +product_stage: create +product_group: code_review +product_category: code_review +value_type: number +status: active +milestone: "15.3" +introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93333" +time_frame: 7d +data_source: redis_hll +data_category: optional +instrumentation_class: RedisHLLMetric +options: + events: + - i_code_review_merge_request_widget_code_quality_expand_warning +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate diff --git a/config/metrics/counts_7d/20220727020435_i_code_review_merge_request_widget_code_quality_expand_failed_weekly.yml b/config/metrics/counts_7d/20220727020435_i_code_review_merge_request_widget_code_quality_expand_failed_weekly.yml new file mode 100644 index 00000000000..4ef43c77c5d --- /dev/null +++ b/config/metrics/counts_7d/20220727020435_i_code_review_merge_request_widget_code_quality_expand_failed_weekly.yml @@ -0,0 +1,25 @@ +--- +key_path: redis_hll_counters.code_review.i_code_review_merge_request_widget_code_quality_expand_failed_weekly +description: The count of unique users (weekly) who expanded the Code Quality widget extension while it is in its Failed state +product_section: dev +product_stage: create +product_group: code_review +product_category: code_review +value_type: number +status: active +milestone: "15.3" +introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93333" +time_frame: 7d +data_source: redis_hll +data_category: optional +instrumentation_class: RedisHLLMetric +options: + events: + - i_code_review_merge_request_widget_code_quality_expand_failed +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate diff --git a/config/metrics/counts_all/20220727004434_i_code_review_merge_request_widget_code_quality_count_view.yml b/config/metrics/counts_all/20220727004434_i_code_review_merge_request_widget_code_quality_count_view.yml new file mode 100644 index 00000000000..1687d346e34 --- /dev/null +++ b/config/metrics/counts_all/20220727004434_i_code_review_merge_request_widget_code_quality_count_view.yml @@ -0,0 +1,24 @@ +--- +key_path: counts.i_code_review_merge_request_widget_code_quality_count_view +description: Total number of times the Code Quality widget extension was viewed (rendered to the screen) +product_section: dev +product_stage: create +product_group: code_review +product_category: code_review +value_type: number +status: active +milestone: "15.3" +introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93333" +time_frame: all +data_source: redis +data_category: optional +options: + events: + - i_code_review_merge_request_widget_code_quality_count_view +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate diff --git a/config/metrics/counts_all/20220727004440_i_code_review_merge_request_widget_code_quality_count_full_report_clicked.yml b/config/metrics/counts_all/20220727004440_i_code_review_merge_request_widget_code_quality_count_full_report_clicked.yml new file mode 100644 index 00000000000..21ff87b8e39 --- /dev/null +++ b/config/metrics/counts_all/20220727004440_i_code_review_merge_request_widget_code_quality_count_full_report_clicked.yml @@ -0,0 +1,24 @@ +--- +key_path: counts.i_code_review_merge_request_widget_code_quality_count_full_report_clicked +description: Total number of times the Code Quality widget extension Full Report button was clicked +product_section: dev +product_stage: create +product_group: code_review +product_category: code_review +value_type: number +status: active +milestone: "15.3" +introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93333" +time_frame: all +data_source: redis +data_category: optional +options: + events: + - i_code_review_merge_request_widget_code_quality_count_full_report_clicked +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate diff --git a/config/metrics/counts_all/20220727004446_i_code_review_merge_request_widget_code_quality_count_expand.yml b/config/metrics/counts_all/20220727004446_i_code_review_merge_request_widget_code_quality_count_expand.yml new file mode 100644 index 00000000000..49123b462d9 --- /dev/null +++ b/config/metrics/counts_all/20220727004446_i_code_review_merge_request_widget_code_quality_count_expand.yml @@ -0,0 +1,24 @@ +--- +key_path: counts.i_code_review_merge_request_widget_code_quality_count_expand +description: Total number of times the Code Quality widget extension was expanded (in any state) +product_section: dev +product_stage: create +product_group: code_review +product_category: code_review +value_type: number +status: active +milestone: "15.3" +introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93333" +time_frame: all +data_source: redis +data_category: optional +options: + events: + - i_code_review_merge_request_widget_code_quality_count_expand +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate diff --git a/config/metrics/counts_all/20220727004451_i_code_review_merge_request_widget_code_quality_count_expand_success.yml b/config/metrics/counts_all/20220727004451_i_code_review_merge_request_widget_code_quality_count_expand_success.yml new file mode 100644 index 00000000000..8b349e04d21 --- /dev/null +++ b/config/metrics/counts_all/20220727004451_i_code_review_merge_request_widget_code_quality_count_expand_success.yml @@ -0,0 +1,24 @@ +--- +key_path: counts.i_code_review_merge_request_widget_code_quality_count_expand_success +description: Total number of times the Code Quality widget extension was expanded (while in its Success state) +product_section: dev +product_stage: create +product_group: code_review +product_category: code_review +value_type: number +status: active +milestone: "15.3" +introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93333" +time_frame: all +data_source: redis +data_category: optional +options: + events: + - i_code_review_merge_request_widget_code_quality_count_expand_success +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate diff --git a/config/metrics/counts_all/20220727004457_i_code_review_merge_request_widget_code_quality_count_expand_warning.yml b/config/metrics/counts_all/20220727004457_i_code_review_merge_request_widget_code_quality_count_expand_warning.yml new file mode 100644 index 00000000000..e94c6999711 --- /dev/null +++ b/config/metrics/counts_all/20220727004457_i_code_review_merge_request_widget_code_quality_count_expand_warning.yml @@ -0,0 +1,24 @@ +--- +key_path: counts.i_code_review_merge_request_widget_code_quality_count_expand_warning +description: Total number of times the Code Quality widget extension was expanded (while in its Warning state) +product_section: dev +product_stage: create +product_group: code_review +product_category: code_review +value_type: number +status: active +milestone: "15.3" +introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93333" +time_frame: all +data_source: redis +data_category: optional +options: + events: + - i_code_review_merge_request_widget_code_quality_count_expand_warning +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate diff --git a/config/metrics/counts_all/20220727004502_i_code_review_merge_request_widget_code_quality_count_expand_failed.yml b/config/metrics/counts_all/20220727004502_i_code_review_merge_request_widget_code_quality_count_expand_failed.yml new file mode 100644 index 00000000000..5e49393afff --- /dev/null +++ b/config/metrics/counts_all/20220727004502_i_code_review_merge_request_widget_code_quality_count_expand_failed.yml @@ -0,0 +1,24 @@ +--- +key_path: counts.i_code_review_merge_request_widget_code_quality_count_expand_failed +description: Total number of times the Code Quality widget extension was expanded (while in its Failed state) +product_section: dev +product_stage: create +product_group: code_review +product_category: code_review +value_type: number +status: active +milestone: "15.3" +introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93333" +time_frame: all +data_source: redis +data_category: optional +options: + events: + - i_code_review_merge_request_widget_code_quality_count_expand_failed +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index e04ef88b277..7ee604207a0 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -5385,6 +5385,30 @@ Input type: `UpdateSnippetInput` | <a id="mutationupdatesnippeterrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. | | <a id="mutationupdatesnippetsnippet"></a>`snippet` | [`Snippet`](#snippet) | Snippet after mutation. | +### `Mutation.uploadDelete` + +Deletes an upload. + +Input type: `UploadDeleteInput` + +#### Arguments + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="mutationuploaddeleteclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | +| <a id="mutationuploaddeletefilename"></a>`filename` | [`String!`](#string) | Upload filename. | +| <a id="mutationuploaddeletegrouppath"></a>`groupPath` | [`ID`](#id) | Full path of the group with which the resource is associated. | +| <a id="mutationuploaddeleteprojectpath"></a>`projectPath` | [`ID`](#id) | Full path of the project with which the resource is associated. | +| <a id="mutationuploaddeletesecret"></a>`secret` | [`String!`](#string) | Secret part of upload path. | + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="mutationuploaddeleteclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | +| <a id="mutationuploaddeleteerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. | +| <a id="mutationuploaddeleteupload"></a>`upload` | [`FileUpload`](#fileupload) | Deleted upload. | + ### `Mutation.userCalloutCreate` Input type: `UserCalloutCreateInput` @@ -11845,6 +11869,16 @@ Represents an external issue. | <a id="externalissueupdatedat"></a>`updatedAt` | [`Time`](#time) | Timestamp of when the issue was updated. | | <a id="externalissueweburl"></a>`webUrl` | [`String`](#string) | URL to the issue in the external tracker. | +### `FileUpload` + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="fileuploadid"></a>`id` | [`UploadID!`](#uploadid) | Global ID of the upload. | +| <a id="fileuploadpath"></a>`path` | [`String!`](#string) | Path of the upload. | +| <a id="fileuploadsize"></a>`size` | [`Int!`](#int) | Size of the upload in bytes. | + ### `GeoNode` #### Fields @@ -21247,6 +21281,12 @@ A regexp containing patterns sourced from user input. ### `Upload` +### `UploadID` + +A `UploadID` is a global ID. It is encoded as a string. + +An example `UploadID` is: `"gid://gitlab/Upload/1"`. + ### `UserID` A `UserID` is a global ID. It is encoded as a string. diff --git a/doc/update/index.md b/doc/update/index.md index 6c594053b4a..6515a9301e4 100644 --- a/doc/update/index.md +++ b/doc/update/index.md @@ -12,11 +12,6 @@ GitLab version is, if you're upgrading to a major version, and so on. Make sure to read the whole page as it contains information related to every upgrade method. -NOTE: -Upgrade GitLab to the latest available patch release, for example `13.8.8` rather than `13.8.0`. -This includes [versions you must stop at on the upgrade path](#upgrade-paths) as there may -be fixes for issues relating to the upgrade process. - The [maintenance policy documentation](../policy/maintenance.md) has additional information about upgrading, including: @@ -388,6 +383,12 @@ accordingly, while also consulting the `8.11.Z` -> `8.12.0` -> `8.17.7` -> `9.5.10` -> `10.8.7` -> [`11.11.8`](#1200) -> `12.0.12` -> [`12.1.17`](#1210) -> [`12.10.14`](#12100) -> `13.0.14` -> [`13.1.11`](#1310) -> [`13.8.8`](#1388) -> [`13.12.15`](#13120) -> [`14.0.12`](#1400) -> [`14.3.6`](#1430) -> [`14.9.5`](#1490) -> [`14.10.Z`](#14100) -> [`15.0.Z`](#1500) -> [latest `15.Y.Z`](https://gitlab.com/gitlab-org/gitlab/-/releases) +NOTE: +When not explicitly specified, upgrade GitLab to the latest available patch +release rather than the first patch release, for example `13.8.8` instead of `13.8.0`. +This includes versions you must stop at on the upgrade path as there may +be fixes for issues relating to the upgrade process. + The following table, while not exhaustive, shows some examples of the supported upgrade paths. Additional steps between the mentioned versions are possible. We list the minimally necessary steps only. diff --git a/doc/user/group/manage.md b/doc/user/group/manage.md index 453b9b976af..c54d58931dc 100644 --- a/doc/user/group/manage.md +++ b/doc/user/group/manage.md @@ -8,8 +8,6 @@ info: To determine the technical writer assigned to the Stage/Group associated w Use Groups to manage one or more related projects at the same time. -For instructions on how to view, create, and manage groups, see [Groups](index.md). - ## Create a group To create a group: diff --git a/lib/gitlab/usage_data_counters/known_events/code_review_events.yml b/lib/gitlab/usage_data_counters/known_events/code_review_events.yml index d97d22ef8d3..7adb45bc9fa 100644 --- a/lib/gitlab/usage_data_counters/known_events/code_review_events.yml +++ b/lib/gitlab/usage_data_counters/known_events/code_review_events.yml @@ -350,3 +350,28 @@ redis_slot: code_review category: code_review aggregation: weekly +## Code Quality +- name: i_code_review_merge_request_widget_code_quality_view + redis_slot: code_review + category: code_review + aggregation: weekly +- name: i_code_review_merge_request_widget_code_quality_full_report_clicked + redis_slot: code_review + category: code_review + aggregation: weekly +- name: i_code_review_merge_request_widget_code_quality_expand + redis_slot: code_review + category: code_review + aggregation: weekly +- name: i_code_review_merge_request_widget_code_quality_expand_success + redis_slot: code_review + category: code_review + aggregation: weekly +- name: i_code_review_merge_request_widget_code_quality_expand_warning + redis_slot: code_review + category: code_review + aggregation: weekly +- name: i_code_review_merge_request_widget_code_quality_expand_failed + redis_slot: code_review + category: code_review + aggregation: weekly diff --git a/lib/gitlab/usage_data_counters/merge_request_widget_extension_counter.rb b/lib/gitlab/usage_data_counters/merge_request_widget_extension_counter.rb index 6a0731ba5ab..e5634203932 100644 --- a/lib/gitlab/usage_data_counters/merge_request_widget_extension_counter.rb +++ b/lib/gitlab/usage_data_counters/merge_request_widget_extension_counter.rb @@ -5,7 +5,7 @@ module Gitlab class MergeRequestWidgetExtensionCounter < BaseCounter KNOWN_EVENTS = %w[view full_report_clicked expand expand_success expand_warning expand_failed].freeze PREFIX = 'i_code_review_merge_request_widget' - WIDGETS = %w[accessibility test_summary].freeze + WIDGETS = %w[accessibility code_quality test_summary].freeze class << self private diff --git a/locale/gitlab.pot b/locale/gitlab.pot index d07818c19f5..0f3b5a747e3 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -39185,6 +39185,9 @@ msgstr "" msgid "The repository must be accessible over %{code_open}http://%{code_close}, %{code_open}https://%{code_close}, %{code_open}ssh://%{code_close} or %{code_open}git://%{code_close}." msgstr "" +msgid "The resource that you are attempting to access does not exist or you don't have permission to perform this action." +msgstr "" + msgid "The same shared runner executes code from multiple projects, unless you configure autoscaling with %{link} set to 1 (which it is on GitLab.com)." msgstr "" @@ -41700,6 +41703,9 @@ msgstr "" msgid "Upload a private key for your certificate" msgstr "" +msgid "Upload could not be deleted." +msgstr "" + msgid "Upload file" msgstr "" diff --git a/spec/controllers/search_controller_spec.rb b/spec/controllers/search_controller_spec.rb index 6922b0f3831..e03a8b7d321 100644 --- a/spec/controllers/search_controller_spec.rb +++ b/spec/controllers/search_controller_spec.rb @@ -393,6 +393,13 @@ RSpec.describe SearchController do get(:autocomplete, params: { term: 'foo@bar.com', scope: 'users' }) end end + + it 'can be filtered with params[:filter]' do + get :autocomplete, params: { term: 'setting', filter: 'generic' } + expect(response).to have_gitlab_http_status(:ok) + expect(json_response.count).to eq(1) + expect(json_response.first['label']).to match(/User settings/) + end end describe '#append_info_to_payload' do diff --git a/spec/graphql/types/upload_type_spec.rb b/spec/graphql/types/upload_type_spec.rb new file mode 100644 index 00000000000..2b959fbf105 --- /dev/null +++ b/spec/graphql/types/upload_type_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['FileUpload'] do + it { expect(described_class).to require_graphql_authorizations(:read_upload) } + + it 'has the expected fields' do + expected_fields = %w[id size path] + + expect(described_class).to include_graphql_fields(*expected_fields) + end +end diff --git a/spec/policies/upload_policy_spec.rb b/spec/policies/upload_policy_spec.rb new file mode 100644 index 00000000000..1169df0b300 --- /dev/null +++ b/spec/policies/upload_policy_spec.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe UploadPolicy do + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, group: group) } + let_it_be(:guest) { create(:user).tap { |user| group.add_guest(user) } } + let_it_be(:developer) { create(:user).tap { |user| group.add_developer(user) } } + let_it_be(:maintainer) { create(:user).tap { |user| group.add_maintainer(user) } } + let_it_be(:owner) { create(:user).tap { |user| group.add_owner(user) } } + let_it_be(:admin) { create(:admin) } + let_it_be(:non_member_user) { create(:user) } + + let(:upload_permissions) { [:read_upload, :destroy_upload] } + + shared_examples_for 'uploads policy' do + subject { described_class.new(current_user, upload) } + + context 'when user is guest' do + let(:current_user) { guest } + + it { is_expected.to be_disallowed(*upload_permissions) } + end + + context 'when user is developer' do + let(:current_user) { developer } + + it { is_expected.to be_disallowed(*upload_permissions) } + end + + context 'when user is maintainer' do + let(:current_user) { maintainer } + + it { is_expected.to be_allowed(*upload_permissions) } + end + + context 'when user is owner' do + let(:current_user) { owner } + + it { is_expected.to be_allowed(*upload_permissions) } + end + + context 'when user is admin' do + let(:current_user) { admin } + + it { is_expected.to be_disallowed(*upload_permissions) } + + context 'with admin mode', :enable_admin_mode do + it { is_expected.to be_allowed(*upload_permissions) } + end + end + end + + describe 'destroy_upload' do + context 'when deleting project upload' do + let_it_be(:upload) { create(:upload, model: project) } + + it_behaves_like 'uploads policy' + end + + context 'when deleting group upload' do + let_it_be(:upload) { create(:upload, model: group) } + + it_behaves_like 'uploads policy' + end + + context 'when deleting upload associated with other model' do + let_it_be(:upload) { create(:upload, model: maintainer) } + + subject { described_class.new(maintainer, upload) } + + it { is_expected.to be_disallowed(*upload_permissions) } + end + end +end diff --git a/spec/requests/api/graphql/mutations/uploads/delete_spec.rb b/spec/requests/api/graphql/mutations/uploads/delete_spec.rb new file mode 100644 index 00000000000..f44bf179397 --- /dev/null +++ b/spec/requests/api/graphql/mutations/uploads/delete_spec.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Delete an upload' do + include GraphqlHelpers + + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, group: group) } + let_it_be(:developer) { create(:user).tap { |user| group.add_developer(user) } } + let_it_be(:maintainer) { create(:user).tap { |user| group.add_maintainer(user) } } + + let(:extra_params) { {} } + let(:params) { { filename: File.basename(upload.path), secret: upload.secret }.merge(extra_params) } + let(:mutation) { graphql_mutation(:uploadDelete, params) } + let(:mutation_response) { graphql_mutation_response(:upload_delete) } + + shared_examples_for 'upload deletion' do + context 'when the user is not allowed to delete uploads' do + let(:current_user) { developer } + + it_behaves_like 'a mutation that returns a top-level access error' + end + + context 'when the user is anonymous' do + let(:current_user) { nil } + + it_behaves_like 'a mutation that returns a top-level access error' + end + + context 'when user has permissions to delete uploads' do + let(:current_user) { maintainer } + + it 'deletes the upload' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(response).to have_gitlab_http_status(:success) + expect(mutation_response['upload']).to include('id' => upload.to_global_id.to_s) + expect(mutation_response['errors']).to be_empty + end + + context 'when upload does not exist' do + let(:params) { { filename: 'invalid', secret: upload.secret }.merge(extra_params) } + + it 'returns an error' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(response).to have_gitlab_http_status(:success) + expect(mutation_response['upload']).to be_nil + expect(mutation_response['errors']).to match_array([ + "The resource that you are attempting to access does not "\ + "exist or you don't have permission to perform this action." + ]) + end + end + end + end + + context 'when deleting project upload' do + let_it_be_with_reload(:upload) { create(:upload, :issuable_upload, model: project) } + + let(:extra_params) { { project_path: project.full_path } } + + it_behaves_like 'upload deletion' + end + + context 'when deleting group upload' do + let_it_be_with_reload(:upload) { create(:upload, :namespace_upload, model: group) } + + let(:extra_params) { { group_path: group.full_path } } + + it_behaves_like 'upload deletion' + end +end diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 68d5fad8ff4..1c8b74558ae 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -1184,7 +1184,7 @@ RSpec.describe API::Users do post api('/users', admin), params: { email: 'invalid email', - password: 'password', + password: User.random_password, name: 'test' } expect(response).to have_gitlab_http_status(:bad_request) @@ -1250,7 +1250,7 @@ RSpec.describe API::Users do post api('/users', admin), params: { email: 'test@example.com', - password: 'password', + password: User.random_password, username: 'test', name: 'foo' } @@ -1262,7 +1262,7 @@ RSpec.describe API::Users do params: { name: 'foo', email: 'test@example.com', - password: 'password', + password: User.random_password, username: 'foo' } end.to change { User.count }.by(0) @@ -1276,7 +1276,7 @@ RSpec.describe API::Users do params: { name: 'foo', email: 'foo@example.com', - password: 'password', + password: User.random_password, username: 'test' } end.to change { User.count }.by(0) @@ -1290,7 +1290,7 @@ RSpec.describe API::Users do params: { name: 'foo', email: 'foo@example.com', - password: 'password', + password: User.random_password, username: 'TEST' } end.to change { User.count }.by(0) @@ -1635,8 +1635,8 @@ RSpec.describe API::Users do context "with existing user" do before do - post api("/users", admin), params: { email: 'test@example.com', password: 'password', username: 'test', name: 'test' } - post api("/users", admin), params: { email: 'foo@bar.com', password: 'password', username: 'john', name: 'john' } + post api("/users", admin), params: { email: 'test@example.com', password: User.random_password, username: 'test', name: 'test' } + post api("/users", admin), params: { email: 'foo@bar.com', password: User.random_password, username: 'john', name: 'john' } @user = User.all.last end diff --git a/spec/services/ci/create_pipeline_service/rules_spec.rb b/spec/services/ci/create_pipeline_service/rules_spec.rb index d0ce1c5aba8..88cec0cde9a 100644 --- a/spec/services/ci/create_pipeline_service/rules_spec.rb +++ b/spec/services/ci/create_pipeline_service/rules_spec.rb @@ -7,10 +7,38 @@ RSpec.describe Ci::CreatePipelineService do let(:ref) { 'refs/heads/master' } let(:source) { :push } let(:service) { described_class.new(project, user, { ref: ref }) } - let(:pipeline) { service.execute(source).payload } + let(:response) { execute_service } + let(:pipeline) { response.payload } let(:build_names) { pipeline.builds.pluck(:name) } + def execute_service(before: '00000000', variables_attributes: nil) + params = { ref: ref, before: before, after: project.commit(ref).sha, variables_attributes: variables_attributes } + + described_class + .new(project, user, params) + .execute(source) do |pipeline| + yield(pipeline) if block_given? + end + end + context 'job:rules' do + let(:regular_job) { find_job('regular-job') } + let(:rules_job) { find_job('rules-job') } + let(:delayed_job) { find_job('delayed-job') } + + def find_job(name) + pipeline.builds.find_by(name: name) + end + + shared_examples 'rules jobs are excluded' do + it 'only persists the job without rules' do + expect(pipeline).to be_persisted + expect(regular_job).to be_persisted + expect(rules_job).to be_nil + expect(delayed_job).to be_nil + end + end + before do stub_ci_pipeline_yaml_file(config) allow_next_instance_of(Ci::BuildScheduleWorker) do |instance| @@ -95,10 +123,6 @@ RSpec.describe Ci::CreatePipelineService do end context 'with allow_failure and exit_codes', :aggregate_failures do - def find_job(name) - pipeline.builds.find_by(name: name) - end - let(:config) do <<-EOY job-1: @@ -280,6 +304,773 @@ RSpec.describe Ci::CreatePipelineService do end end end + + context 'with simple if: clauses' do + let(:config) do + <<-EOY + regular-job: + script: 'echo Hello, World!' + + master-job: + script: "echo hello world, $CI_COMMIT_REF_NAME" + rules: + - if: $CI_COMMIT_REF_NAME == "nonexistant-branch" + when: never + - if: $CI_COMMIT_REF_NAME =~ /master/ + when: manual + + negligible-job: + script: "exit 1" + rules: + - if: $CI_COMMIT_REF_NAME =~ /master/ + allow_failure: true + + delayed-job: + script: "echo See you later, World!" + rules: + - if: $CI_COMMIT_REF_NAME =~ /master/ + when: delayed + start_in: 1 hour + + never-job: + script: "echo Goodbye, World!" + rules: + - if: $CI_COMMIT_REF_NAME + when: never + EOY + end + + context 'with matches' do + it 'creates a pipeline with the vanilla and manual jobs' do + expect(pipeline).to be_persisted + expect(build_names).to contain_exactly( + 'regular-job', 'delayed-job', 'master-job', 'negligible-job' + ) + end + + it 'assigns job:when values to the builds' do + expect(find_job('regular-job').when).to eq('on_success') + expect(find_job('master-job').when).to eq('manual') + expect(find_job('negligible-job').when).to eq('on_success') + expect(find_job('delayed-job').when).to eq('delayed') + end + + it 'assigns job:allow_failure values to the builds' do + expect(find_job('regular-job').allow_failure).to eq(false) + expect(find_job('master-job').allow_failure).to eq(false) + expect(find_job('negligible-job').allow_failure).to eq(true) + expect(find_job('delayed-job').allow_failure).to eq(false) + end + + it 'assigns start_in for delayed jobs' do + expect(delayed_job.options[:start_in]).to eq('1 hour') + end + end + + context 'with no matches' do + let(:ref) { 'refs/heads/feature' } + + it_behaves_like 'rules jobs are excluded' + end + end + + context 'with complex if: clauses' do + let(:config) do + <<-EOY + regular-job: + script: 'echo Hello, World!' + rules: + - if: $VAR == 'present' && $OTHER || $CI_COMMIT_REF_NAME + when: manual + allow_failure: true + EOY + end + + it 'matches the first rule' do + expect(pipeline).to be_persisted + expect(build_names).to contain_exactly('regular-job') + expect(regular_job.when).to eq('manual') + expect(regular_job.allow_failure).to eq(true) + end + end + end + + context 'changes:' do + let(:config) do + <<-EOY + regular-job: + script: 'echo Hello, World!' + + rules-job: + script: "echo hello world, $CI_COMMIT_REF_NAME" + rules: + - changes: + - README.md + when: manual + - changes: + - app.rb + when: on_success + + delayed-job: + script: "echo See you later, World!" + rules: + - changes: + - README.md + when: delayed + start_in: 4 hours + + negligible-job: + script: "can be failed sometimes" + rules: + - changes: + - README.md + allow_failure: true + + README: + script: "I use variables for changes!" + rules: + - changes: + - $CI_JOB_NAME* + + changes-paths: + script: "I am using a new syntax!" + rules: + - changes: + paths: [README.md] + EOY + end + + context 'and matches' do + before do + allow_next_instance_of(Ci::Pipeline) do |pipeline| + allow(pipeline).to receive(:modified_paths).and_return(%w[README.md]) + end + end + + it 'creates five jobs' do + expect(pipeline).to be_persisted + expect(build_names).to contain_exactly( + 'regular-job', 'rules-job', 'delayed-job', 'negligible-job', 'README', 'changes-paths' + ) + end + + it 'sets when: for all jobs' do + expect(regular_job.when).to eq('on_success') + expect(rules_job.when).to eq('manual') + expect(delayed_job.when).to eq('delayed') + expect(delayed_job.options[:start_in]).to eq('4 hours') + end + + it 'sets allow_failure: for negligible job' do + expect(find_job('negligible-job').allow_failure).to eq(true) + end + end + + context 'and matches the second rule' do + before do + allow_next_instance_of(Ci::Pipeline) do |pipeline| + allow(pipeline).to receive(:modified_paths).and_return(%w[app.rb]) + end + end + + it 'includes both jobs' do + expect(pipeline).to be_persisted + expect(build_names).to contain_exactly('regular-job', 'rules-job') + end + + it 'sets when: for the created rules job based on the second clause' do + expect(regular_job.when).to eq('on_success') + expect(rules_job.when).to eq('on_success') + end + end + + context 'and does not match' do + before do + allow_next_instance_of(Ci::Pipeline) do |pipeline| + allow(pipeline).to receive(:modified_paths).and_return(%w[useless_script.rb]) + end + end + + it_behaves_like 'rules jobs are excluded' + + it 'sets when: for the created job' do + expect(regular_job.when).to eq('on_success') + end + end + + context 'with paths and compare_to' do + let_it_be(:project) { create(:project, :empty_repo) } + let_it_be(:user) { project.first_owner } + + before_all do + project.repository.add_branch(user, 'feature_1', 'master') + + project.repository.create_file( + user, 'file1.txt', 'file 1', message: 'Create file1.txt', branch_name: 'feature_1' + ) + + project.repository.add_branch(user, 'feature_2', 'feature_1') + + project.repository.create_file( + user, 'file2.txt', 'file 2', message: 'Create file2.txt', branch_name: 'feature_2' + ) + end + + let(:changed_file) { 'file2.txt' } + let(:ref) { 'feature_2' } + + let(:response) { execute_service(before: nil) } + + context 'for jobs rules' do + let(:config) do + <<-EOY + job1: + script: exit 0 + rules: + - changes: + paths: [#{changed_file}] + compare_to: #{compare_to} + + job2: + script: exit 0 + EOY + end + + context 'when there is no such compare_to ref' do + let(:compare_to) { 'invalid-branch' } + + it 'returns an error' do + expect(pipeline.errors.full_messages).to eq([ + 'Failed to parse rule for job1: rules:changes:compare_to is not a valid ref' + ]) + end + + context 'when the FF ci_rules_changes_compare is not enabled' do + before do + stub_feature_flags(ci_rules_changes_compare: false) + end + + it 'ignores compare_to and changes is always true' do + expect(build_names).to contain_exactly('job1', 'job2') + end + end + end + + context 'when the compare_to ref exists' do + let(:compare_to) { 'feature_1'} + + context 'when the rule matches' do + it 'creates job1 and job2' do + expect(build_names).to contain_exactly('job1', 'job2') + end + + context 'when the FF ci_rules_changes_compare is not enabled' do + before do + stub_feature_flags(ci_rules_changes_compare: false) + end + + it 'ignores compare_to and changes is always true' do + expect(build_names).to contain_exactly('job1', 'job2') + end + end + end + + context 'when the rule does not match' do + let(:changed_file) { 'file1.txt' } + + it 'does not create job1' do + expect(build_names).to contain_exactly('job2') + end + + context 'when the FF ci_rules_changes_compare is not enabled' do + before do + stub_feature_flags(ci_rules_changes_compare: false) + end + + it 'ignores compare_to and changes is always true' do + expect(build_names).to contain_exactly('job1', 'job2') + end + end + end + end + end + + context 'for workflow rules' do + let(:config) do + <<-EOY + workflow: + rules: + - changes: + paths: [#{changed_file}] + compare_to: #{compare_to} + + job1: + script: exit 0 + EOY + end + + let(:compare_to) { 'feature_1'} + + context 'when the rule matches' do + it 'creates job1' do + expect(pipeline).to be_created_successfully + expect(build_names).to contain_exactly('job1') + end + + context 'when the FF ci_rules_changes_compare is not enabled' do + before do + stub_feature_flags(ci_rules_changes_compare: false) + end + + it 'ignores compare_to and changes is always true' do + expect(pipeline).to be_created_successfully + expect(build_names).to contain_exactly('job1') + end + end + end + + context 'when the rule does not match' do + let(:changed_file) { 'file1.txt' } + + it 'does not create job1' do + expect(pipeline).not_to be_created_successfully + expect(build_names).to be_empty + end + end + end + end + end + + context 'mixed if: and changes: rules' do + let(:config) do + <<-EOY + regular-job: + script: 'echo Hello, World!' + + rules-job: + script: "echo hello world, $CI_COMMIT_REF_NAME" + allow_failure: true + rules: + - changes: + - README.md + when: manual + - if: $CI_COMMIT_REF_NAME == "master" + when: on_success + allow_failure: false + + delayed-job: + script: "echo See you later, World!" + rules: + - changes: + - README.md + when: delayed + start_in: 4 hours + allow_failure: true + - if: $CI_COMMIT_REF_NAME == "master" + when: delayed + start_in: 1 hour + EOY + end + + context 'and changes: matches before if' do + before do + allow_next_instance_of(Ci::Pipeline) do |pipeline| + allow(pipeline).to receive(:modified_paths).and_return(%w[README.md]) + end + end + + it 'creates two jobs' do + expect(pipeline).to be_persisted + expect(build_names) + .to contain_exactly('regular-job', 'rules-job', 'delayed-job') + end + + it 'sets when: for all jobs' do + expect(regular_job.when).to eq('on_success') + expect(rules_job.when).to eq('manual') + expect(delayed_job.when).to eq('delayed') + expect(delayed_job.options[:start_in]).to eq('4 hours') + end + + it 'sets allow_failure: for all jobs' do + expect(regular_job.allow_failure).to eq(false) + expect(rules_job.allow_failure).to eq(true) + expect(delayed_job.allow_failure).to eq(true) + end + end + + context 'and if: matches after changes' do + it 'includes both jobs' do + expect(pipeline).to be_persisted + expect(build_names).to contain_exactly('regular-job', 'rules-job', 'delayed-job') + end + + it 'sets when: for the created rules job based on the second clause' do + expect(regular_job.when).to eq('on_success') + expect(rules_job.when).to eq('on_success') + expect(delayed_job.when).to eq('delayed') + expect(delayed_job.options[:start_in]).to eq('1 hour') + end + end + + context 'and does not match' do + let(:ref) { 'refs/heads/wip' } + + it_behaves_like 'rules jobs are excluded' + + it 'sets when: for the created job' do + expect(regular_job.when).to eq('on_success') + end + end + end + + context 'mixed if: and changes: clauses' do + let(:config) do + <<-EOY + regular-job: + script: 'echo Hello, World!' + + rules-job: + script: "echo hello world, $CI_COMMIT_REF_NAME" + rules: + - if: $CI_COMMIT_REF_NAME =~ /master/ + changes: [README.md] + when: on_success + allow_failure: true + - if: $CI_COMMIT_REF_NAME =~ /master/ + changes: [app.rb] + when: manual + EOY + end + + context 'with if matches and changes matches' do + before do + allow_next_instance_of(Ci::Pipeline) do |pipeline| + allow(pipeline).to receive(:modified_paths).and_return(%w[app.rb]) + end + end + + it 'persists all jobs' do + expect(pipeline).to be_persisted + expect(regular_job).to be_persisted + expect(rules_job).to be_persisted + expect(rules_job.when).to eq('manual') + expect(rules_job.allow_failure).to eq(false) + end + end + + context 'with if matches and no change matches' do + it_behaves_like 'rules jobs are excluded' + end + + context 'with change matches and no if matches' do + let(:ref) { 'refs/heads/feature' } + + before do + allow_next_instance_of(Ci::Pipeline) do |pipeline| + allow(pipeline).to receive(:modified_paths).and_return(%w[README.md]) + end + end + + it_behaves_like 'rules jobs are excluded' + end + + context 'and no matches' do + let(:ref) { 'refs/heads/feature' } + + it_behaves_like 'rules jobs are excluded' + end + end + + context 'complex if: allow_failure usages' do + let(:config) do + <<-EOY + job-1: + script: "exit 1" + allow_failure: true + rules: + - if: $CI_COMMIT_REF_NAME =~ /master/ + allow_failure: false + + job-2: + script: "exit 1" + allow_failure: true + rules: + - if: $CI_COMMIT_REF_NAME =~ /nonexistant-branch/ + allow_failure: false + + job-3: + script: "exit 1" + rules: + - if: $CI_COMMIT_REF_NAME =~ /nonexistant-branch/ + allow_failure: true + + job-4: + script: "exit 1" + rules: + - if: $CI_COMMIT_REF_NAME =~ /master/ + allow_failure: false + + job-5: + script: "exit 1" + allow_failure: false + rules: + - if: $CI_COMMIT_REF_NAME =~ /master/ + allow_failure: true + + job-6: + script: "exit 1" + rules: + - if: $CI_COMMIT_REF_NAME =~ /nonexistant-branch/ + allow_failure: false + - allow_failure: true + EOY + end + + it 'creates a pipeline' do + expect(pipeline).to be_persisted + expect(build_names).to contain_exactly('job-1', 'job-4', 'job-5', 'job-6') + end + + it 'assigns job:allow_failure values to the builds' do + expect(find_job('job-1').allow_failure).to eq(false) + expect(find_job('job-4').allow_failure).to eq(false) + expect(find_job('job-5').allow_failure).to eq(true) + expect(find_job('job-6').allow_failure).to eq(true) + end + end + + context 'complex if: allow_failure & when usages' do + let(:config) do + <<-EOY + job-1: + script: "exit 1" + rules: + - if: $CI_COMMIT_REF_NAME =~ /master/ + when: manual + + job-2: + script: "exit 1" + rules: + - if: $CI_COMMIT_REF_NAME =~ /master/ + when: manual + allow_failure: true + + job-3: + script: "exit 1" + allow_failure: true + rules: + - if: $CI_COMMIT_REF_NAME =~ /master/ + when: manual + + job-4: + script: "exit 1" + allow_failure: true + rules: + - if: $CI_COMMIT_REF_NAME =~ /master/ + when: manual + allow_failure: false + + job-5: + script: "exit 1" + rules: + - if: $CI_COMMIT_REF_NAME =~ /nonexistant-branch/ + when: manual + allow_failure: false + - when: always + allow_failure: true + + job-6: + script: "exit 1" + allow_failure: false + rules: + - if: $CI_COMMIT_REF_NAME =~ /master/ + when: manual + + job-7: + script: "exit 1" + allow_failure: false + rules: + - if: $CI_COMMIT_REF_NAME =~ /nonexistant-branch/ + when: manual + - when: :on_failure + allow_failure: true + EOY + end + + it 'creates a pipeline' do + expect(pipeline).to be_persisted + expect(build_names).to contain_exactly( + 'job-1', 'job-2', 'job-3', 'job-4', 'job-5', 'job-6', 'job-7' + ) + end + + it 'assigns job:allow_failure values to the builds' do + expect(find_job('job-1').allow_failure).to eq(false) + expect(find_job('job-2').allow_failure).to eq(true) + expect(find_job('job-3').allow_failure).to eq(true) + expect(find_job('job-4').allow_failure).to eq(false) + expect(find_job('job-5').allow_failure).to eq(true) + expect(find_job('job-6').allow_failure).to eq(false) + expect(find_job('job-7').allow_failure).to eq(true) + end + + it 'assigns job:when values to the builds' do + expect(find_job('job-1').when).to eq('manual') + expect(find_job('job-2').when).to eq('manual') + expect(find_job('job-3').when).to eq('manual') + expect(find_job('job-4').when).to eq('manual') + expect(find_job('job-5').when).to eq('always') + expect(find_job('job-6').when).to eq('manual') + expect(find_job('job-7').when).to eq('on_failure') + end + end + + context 'deploy freeze period `if:` clause' do + # '0 23 * * 5' == "At 23:00 on Friday."", '0 7 * * 1' == "At 07:00 on Monday."" + let!(:freeze_period) { create(:ci_freeze_period, project: project, freeze_start: '0 23 * * 5', freeze_end: '0 7 * * 1') } + + context 'with 2 jobs' do + let(:config) do + <<-EOY + stages: + - test + - deploy + + test-job: + script: + - echo 'running TEST stage' + + deploy-job: + stage: deploy + script: + - echo 'running DEPLOY stage' + rules: + - if: $CI_DEPLOY_FREEZE == null + EOY + end + + context 'when outside freeze period' do + it 'creates two jobs' do + Timecop.freeze(2020, 4, 10, 22, 59) do + expect(pipeline).to be_persisted + expect(build_names).to contain_exactly('test-job', 'deploy-job') + end + end + end + + context 'when inside freeze period' do + it 'creates one job' do + Timecop.freeze(2020, 4, 10, 23, 1) do + expect(pipeline).to be_persisted + expect(build_names).to contain_exactly('test-job') + end + end + end + end + + context 'with 1 job' do + let(:config) do + <<-EOY + stages: + - deploy + + deploy-job: + stage: deploy + script: + - echo 'running DEPLOY stage' + rules: + - if: $CI_DEPLOY_FREEZE == null + EOY + end + + context 'when outside freeze period' do + it 'creates two jobs' do + Timecop.freeze(2020, 4, 10, 22, 59) do + expect(pipeline).to be_persisted + expect(build_names).to contain_exactly('deploy-job') + end + end + end + + context 'when inside freeze period' do + it 'does not create the pipeline', :aggregate_failures do + Timecop.freeze(2020, 4, 10, 23, 1) do + expect(response).to be_error + expect(pipeline).not_to be_persisted + end + end + end + end + end + + context 'with when:manual' do + let(:config) do + <<-EOY + job-with-rules: + script: 'echo hey' + rules: + - if: $CI_COMMIT_REF_NAME =~ /master/ + + job-when-with-rules: + script: 'echo hey' + when: manual + rules: + - if: $CI_COMMIT_REF_NAME =~ /master/ + + job-when-with-rules-when: + script: 'echo hey' + when: manual + rules: + - if: $CI_COMMIT_REF_NAME =~ /master/ + when: on_success + + job-with-rules-when: + script: 'echo hey' + rules: + - if: $CI_COMMIT_REF_NAME =~ /master/ + when: manual + + job-without-rules: + script: 'echo this is a job with NO rules' + EOY + end + + let(:job_with_rules) { find_job('job-with-rules') } + let(:job_when_with_rules) { find_job('job-when-with-rules') } + let(:job_when_with_rules_when) { find_job('job-when-with-rules-when') } + let(:job_with_rules_when) { find_job('job-with-rules-when') } + let(:job_without_rules) { find_job('job-without-rules') } + + context 'when matching the rules' do + let(:ref) { 'refs/heads/master' } + + it 'adds the job-with-rules with a when:manual' do + expect(job_with_rules).to be_persisted + expect(job_when_with_rules).to be_persisted + expect(job_when_with_rules_when).to be_persisted + expect(job_with_rules_when).to be_persisted + expect(job_without_rules).to be_persisted + + expect(job_with_rules.when).to eq('on_success') + expect(job_when_with_rules.when).to eq('manual') + expect(job_when_with_rules_when.when).to eq('on_success') + expect(job_with_rules_when.when).to eq('manual') + expect(job_without_rules.when).to eq('on_success') + end + end + + context 'when there is no match to the rule' do + let(:ref) { 'refs/heads/wip' } + + it 'does not add job_with_rules' do + expect(job_with_rules).to be_nil + expect(job_when_with_rules).to be_nil + expect(job_when_with_rules_when).to be_nil + expect(job_with_rules_when).to be_nil + expect(job_without_rules).to be_persisted + end + end end end @@ -447,5 +1238,232 @@ RSpec.describe Ci::CreatePipelineService do end end end + + context 'with persisted variables' do + let(:config) do + <<-EOY + workflow: + rules: + - if: $CI_COMMIT_REF_NAME == "master" + + regular-job: + script: 'echo Hello, World!' + EOY + end + + context 'with matches' do + it 'creates a pipeline' do + expect(pipeline).to be_persisted + expect(build_names).to contain_exactly('regular-job') + end + end + + context 'with no matches' do + let(:ref) { 'refs/heads/feature' } + + it 'does not create a pipeline', :aggregate_failures do + expect(response).to be_error + expect(pipeline).not_to be_persisted + end + end + end + + context 'with pipeline variables' do + let(:pipeline) do + execute_service(variables_attributes: variables_attributes).payload + end + + let(:config) do + <<-EOY + workflow: + rules: + - if: $SOME_VARIABLE + + regular-job: + script: 'echo Hello, World!' + EOY + end + + context 'with matches' do + let(:variables_attributes) do + [{ key: 'SOME_VARIABLE', secret_value: 'SOME_VAR' }] + end + + it 'creates a pipeline' do + expect(pipeline).to be_persisted + expect(build_names).to contain_exactly('regular-job') + end + end + + context 'with no matches' do + let(:variables_attributes) { {} } + + it 'does not create a pipeline', :aggregate_failures do + expect(response).to be_error + expect(pipeline).not_to be_persisted + end + end + end + + context 'with trigger variables' do + let(:pipeline) do + execute_service do |pipeline| + pipeline.variables.build(variables) + end.payload + end + + let(:config) do + <<-EOY + workflow: + rules: + - if: $SOME_VARIABLE + + regular-job: + script: 'echo Hello, World!' + EOY + end + + context 'with matches' do + let(:variables) do + [{ key: 'SOME_VARIABLE', secret_value: 'SOME_VAR' }] + end + + it 'creates a pipeline' do + expect(pipeline).to be_persisted + expect(build_names).to contain_exactly('regular-job') + end + + context 'when a job requires the same variable' do + let(:config) do + <<-EOY + workflow: + rules: + - if: $SOME_VARIABLE + + build: + stage: build + script: 'echo build' + rules: + - if: $SOME_VARIABLE + + test1: + stage: test + script: 'echo test1' + needs: [build] + + test2: + stage: test + script: 'echo test2' + EOY + end + + it 'creates a pipeline' do + expect(pipeline).to be_persisted + expect(build_names).to contain_exactly('build', 'test1', 'test2') + end + end + end + + context 'with no matches' do + let(:variables) { {} } + + it 'does not create a pipeline', :aggregate_failures do + expect(response).to be_error + expect(pipeline).not_to be_persisted + end + + context 'when a job requires the same variable' do + let(:config) do + <<-EOY + workflow: + rules: + - if: $SOME_VARIABLE + + build: + stage: build + script: 'echo build' + rules: + - if: $SOME_VARIABLE + + test1: + stage: test + script: 'echo test1' + needs: [build] + + test2: + stage: test + script: 'echo test2' + EOY + end + + it 'does not create a pipeline', :aggregate_failures do + expect(response).to be_error + expect(pipeline).not_to be_persisted + end + end + end + end + + context 'changes' do + shared_examples 'comparing file changes with workflow rules' do + context 'when matches' do + before do + allow_next_instance_of(Ci::Pipeline) do |pipeline| + allow(pipeline).to receive(:modified_paths).and_return(%w[file1.md]) + end + end + + it 'creates the pipeline with a job' do + expect(pipeline).to be_persisted + expect(build_names).to contain_exactly('job') + end + end + + context 'when does not match' do + before do + allow_next_instance_of(Ci::Pipeline) do |pipeline| + allow(pipeline).to receive(:modified_paths).and_return(%w[unknown]) + end + end + + it 'creates the pipeline with a job' do + expect(pipeline.errors.full_messages).to eq(['Pipeline filtered out by workflow rules.']) + expect(response).to be_error + expect(pipeline).not_to be_persisted + end + end + end + + context 'changes is an array' do + let(:config) do + <<-EOY + workflow: + rules: + - changes: [file1.md] + + job: + script: exit 0 + EOY + end + + it_behaves_like 'comparing file changes with workflow rules' + end + + context 'changes:paths is an array' do + let(:config) do + <<-EOY + workflow: + rules: + - changes: + paths: [file1.md] + + job: + script: exit 0 + EOY + end + + it_behaves_like 'comparing file changes with workflow rules' + end + end end end diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb index 6f89e10da7f..a9442b0dc68 100644 --- a/spec/services/ci/create_pipeline_service_spec.rb +++ b/spec/services/ci/create_pipeline_service_spec.rb @@ -1866,1019 +1866,6 @@ RSpec.describe Ci::CreatePipelineService do end end end - - context 'when rules are used' do - let(:ref_name) { 'refs/heads/master' } - let(:response) { execute_service } - let(:pipeline) { response.payload } - let(:build_names) { pipeline.builds.pluck(:name) } - let(:regular_job) { find_job('regular-job') } - let(:rules_job) { find_job('rules-job') } - let(:delayed_job) { find_job('delayed-job') } - - context 'with when:manual' do - let(:config) do - <<-EOY - job-with-rules: - script: 'echo hey' - rules: - - if: $CI_COMMIT_REF_NAME =~ /master/ - - job-when-with-rules: - script: 'echo hey' - when: manual - rules: - - if: $CI_COMMIT_REF_NAME =~ /master/ - - job-when-with-rules-when: - script: 'echo hey' - when: manual - rules: - - if: $CI_COMMIT_REF_NAME =~ /master/ - when: on_success - - job-with-rules-when: - script: 'echo hey' - rules: - - if: $CI_COMMIT_REF_NAME =~ /master/ - when: manual - - job-without-rules: - script: 'echo this is a job with NO rules' - EOY - end - - let(:job_with_rules) { find_job('job-with-rules') } - let(:job_when_with_rules) { find_job('job-when-with-rules') } - let(:job_when_with_rules_when) { find_job('job-when-with-rules-when') } - let(:job_with_rules_when) { find_job('job-with-rules-when') } - let(:job_without_rules) { find_job('job-without-rules') } - - context 'when matching the rules' do - let(:ref_name) { 'refs/heads/master' } - - it 'adds the job-with-rules with a when:manual' do - expect(job_with_rules).to be_persisted - expect(job_when_with_rules).to be_persisted - expect(job_when_with_rules_when).to be_persisted - expect(job_with_rules_when).to be_persisted - expect(job_without_rules).to be_persisted - - expect(job_with_rules.when).to eq('on_success') - expect(job_when_with_rules.when).to eq('manual') - expect(job_when_with_rules_when.when).to eq('on_success') - expect(job_with_rules_when.when).to eq('manual') - expect(job_without_rules.when).to eq('on_success') - end - end - - context 'when there is no match to the rule' do - let(:ref_name) { 'refs/heads/wip' } - - it 'does not add job_with_rules' do - expect(job_with_rules).to be_nil - expect(job_when_with_rules).to be_nil - expect(job_when_with_rules_when).to be_nil - expect(job_with_rules_when).to be_nil - expect(job_without_rules).to be_persisted - end - end - end - - shared_examples 'rules jobs are excluded' do - it 'only persists the job without rules' do - expect(pipeline).to be_persisted - expect(regular_job).to be_persisted - expect(rules_job).to be_nil - expect(delayed_job).to be_nil - end - end - - def find_job(name) - pipeline.builds.find_by(name: name) - end - - before do - stub_ci_pipeline_yaml_file(config) - allow_any_instance_of(Ci::BuildScheduleWorker).to receive(:perform).and_return(true) - end - - context 'with simple if: clauses' do - let(:config) do - <<-EOY - regular-job: - script: 'echo Hello, World!' - - master-job: - script: "echo hello world, $CI_COMMIT_REF_NAME" - rules: - - if: $CI_COMMIT_REF_NAME == "nonexistant-branch" - when: never - - if: $CI_COMMIT_REF_NAME =~ /master/ - when: manual - - negligible-job: - script: "exit 1" - rules: - - if: $CI_COMMIT_REF_NAME =~ /master/ - allow_failure: true - - delayed-job: - script: "echo See you later, World!" - rules: - - if: $CI_COMMIT_REF_NAME =~ /master/ - when: delayed - start_in: 1 hour - - never-job: - script: "echo Goodbye, World!" - rules: - - if: $CI_COMMIT_REF_NAME - when: never - EOY - end - - context 'with matches' do - it 'creates a pipeline with the vanilla and manual jobs' do - expect(pipeline).to be_persisted - expect(build_names).to contain_exactly( - 'regular-job', 'delayed-job', 'master-job', 'negligible-job' - ) - end - - it 'assigns job:when values to the builds' do - expect(find_job('regular-job').when).to eq('on_success') - expect(find_job('master-job').when).to eq('manual') - expect(find_job('negligible-job').when).to eq('on_success') - expect(find_job('delayed-job').when).to eq('delayed') - end - - it 'assigns job:allow_failure values to the builds' do - expect(find_job('regular-job').allow_failure).to eq(false) - expect(find_job('master-job').allow_failure).to eq(false) - expect(find_job('negligible-job').allow_failure).to eq(true) - expect(find_job('delayed-job').allow_failure).to eq(false) - end - - it 'assigns start_in for delayed jobs' do - expect(delayed_job.options[:start_in]).to eq('1 hour') - end - end - - context 'with no matches' do - let(:ref_name) { 'refs/heads/feature' } - - it_behaves_like 'rules jobs are excluded' - end - end - - context 'with complex if: clauses' do - let(:config) do - <<-EOY - regular-job: - script: 'echo Hello, World!' - rules: - - if: $VAR == 'present' && $OTHER || $CI_COMMIT_REF_NAME - when: manual - allow_failure: true - EOY - end - - it 'matches the first rule' do - expect(pipeline).to be_persisted - expect(build_names).to contain_exactly('regular-job') - expect(regular_job.when).to eq('manual') - expect(regular_job.allow_failure).to eq(true) - end - end - - context 'with changes:' do - let(:config) do - <<-EOY - regular-job: - script: 'echo Hello, World!' - - rules-job: - script: "echo hello world, $CI_COMMIT_REF_NAME" - rules: - - changes: - - README.md - when: manual - - changes: - - app.rb - when: on_success - - delayed-job: - script: "echo See you later, World!" - rules: - - changes: - - README.md - when: delayed - start_in: 4 hours - - negligible-job: - script: "can be failed sometimes" - rules: - - changes: - - README.md - allow_failure: true - - README: - script: "I use variables for changes!" - rules: - - changes: - - $CI_JOB_NAME* - - changes-paths: - script: "I am using a new syntax!" - rules: - - changes: - paths: [README.md] - EOY - end - - context 'and matches' do - before do - allow_any_instance_of(Ci::Pipeline) - .to receive(:modified_paths).and_return(%w[README.md]) - end - - it 'creates five jobs' do - expect(pipeline).to be_persisted - expect(build_names).to contain_exactly( - 'regular-job', 'rules-job', 'delayed-job', 'negligible-job', 'README', 'changes-paths' - ) - end - - it 'sets when: for all jobs' do - expect(regular_job.when).to eq('on_success') - expect(rules_job.when).to eq('manual') - expect(delayed_job.when).to eq('delayed') - expect(delayed_job.options[:start_in]).to eq('4 hours') - end - - it 'sets allow_failure: for negligible job' do - expect(find_job('negligible-job').allow_failure).to eq(true) - end - end - - context 'and matches the second rule' do - before do - allow_any_instance_of(Ci::Pipeline) - .to receive(:modified_paths).and_return(%w[app.rb]) - end - - it 'includes both jobs' do - expect(pipeline).to be_persisted - expect(build_names).to contain_exactly('regular-job', 'rules-job') - end - - it 'sets when: for the created rules job based on the second clause' do - expect(regular_job.when).to eq('on_success') - expect(rules_job.when).to eq('on_success') - end - end - - context 'and does not match' do - before do - allow_any_instance_of(Ci::Pipeline) - .to receive(:modified_paths).and_return(%w[useless_script.rb]) - end - - it_behaves_like 'rules jobs are excluded' - - it 'sets when: for the created job' do - expect(regular_job.when).to eq('on_success') - end - end - end - - context 'with changes: paths and compare_to' do - before_all do - project.repository.add_branch(user, 'feature_1', 'master') - - project.repository.create_file( - user, 'file1.txt', 'file 1', message: 'Create file1.txt', branch_name: 'feature_1' - ) - - project.repository.add_branch(user, 'feature_2', 'feature_1') - - project.repository.create_file( - user, 'file2.txt', 'file 2', message: 'Create file2.txt', branch_name: 'feature_2' - ) - end - - let(:changed_file) { 'file2.txt' } - let(:ref_name) { 'feature_2' } - - let(:response) { execute_service(ref: ref_name, before: nil, after: project.commit(ref_name).sha) } - - context 'for jobs rules' do - let(:config) do - <<-EOY - job1: - script: exit 0 - rules: - - changes: - paths: [#{changed_file}] - compare_to: #{compare_to} - - job2: - script: exit 0 - EOY - end - - context 'when there is no such compare_to ref' do - let(:compare_to) { 'invalid-branch' } - - it 'returns an error' do - expect(pipeline.errors.full_messages).to eq([ - 'Failed to parse rule for job1: rules:changes:compare_to is not a valid ref' - ]) - end - - context 'when the FF ci_rules_changes_compare is not enabled' do - before do - stub_feature_flags(ci_rules_changes_compare: false) - end - - it 'ignores compare_to and changes is always true' do - expect(build_names).to contain_exactly('job1', 'job2') - end - end - end - - context 'when the compare_to ref exists' do - let(:compare_to) { 'feature_1'} - - context 'when the rule matches' do - it 'creates job1 and job2' do - expect(build_names).to contain_exactly('job1', 'job2') - end - - context 'when the FF ci_rules_changes_compare is not enabled' do - before do - stub_feature_flags(ci_rules_changes_compare: false) - end - - it 'ignores compare_to and changes is always true' do - expect(build_names).to contain_exactly('job1', 'job2') - end - end - end - - context 'when the rule does not match' do - let(:changed_file) { 'file1.txt' } - - it 'does not create job1' do - expect(build_names).to contain_exactly('job2') - end - - context 'when the FF ci_rules_changes_compare is not enabled' do - before do - stub_feature_flags(ci_rules_changes_compare: false) - end - - it 'ignores compare_to and changes is always true' do - expect(build_names).to contain_exactly('job1', 'job2') - end - end - end - end - end - - context 'for workflow rules' do - let(:config) do - <<-EOY - workflow: - rules: - - changes: - paths: [#{changed_file}] - compare_to: #{compare_to} - - job1: - script: exit 0 - EOY - end - - let(:compare_to) { 'feature_1'} - - context 'when the rule matches' do - it 'creates job1' do - expect(pipeline).to be_created_successfully - expect(build_names).to contain_exactly('job1') - end - - context 'when the FF ci_rules_changes_compare is not enabled' do - before do - stub_feature_flags(ci_rules_changes_compare: false) - end - - it 'ignores compare_to and changes is always true' do - expect(pipeline).to be_created_successfully - expect(build_names).to contain_exactly('job1') - end - end - end - - context 'when the rule does not match' do - let(:changed_file) { 'file1.txt' } - - it 'does not create job1' do - expect(pipeline).not_to be_created_successfully - expect(build_names).to be_empty - end - end - end - end - - context 'with mixed if: and changes: rules' do - let(:config) do - <<-EOY - regular-job: - script: 'echo Hello, World!' - - rules-job: - script: "echo hello world, $CI_COMMIT_REF_NAME" - allow_failure: true - rules: - - changes: - - README.md - when: manual - - if: $CI_COMMIT_REF_NAME == "master" - when: on_success - allow_failure: false - - delayed-job: - script: "echo See you later, World!" - rules: - - changes: - - README.md - when: delayed - start_in: 4 hours - allow_failure: true - - if: $CI_COMMIT_REF_NAME == "master" - when: delayed - start_in: 1 hour - EOY - end - - context 'and changes: matches before if' do - before do - allow_any_instance_of(Ci::Pipeline) - .to receive(:modified_paths).and_return(%w[README.md]) - end - - it 'creates two jobs' do - expect(pipeline).to be_persisted - expect(build_names) - .to contain_exactly('regular-job', 'rules-job', 'delayed-job') - end - - it 'sets when: for all jobs' do - expect(regular_job.when).to eq('on_success') - expect(rules_job.when).to eq('manual') - expect(delayed_job.when).to eq('delayed') - expect(delayed_job.options[:start_in]).to eq('4 hours') - end - - it 'sets allow_failure: for all jobs' do - expect(regular_job.allow_failure).to eq(false) - expect(rules_job.allow_failure).to eq(true) - expect(delayed_job.allow_failure).to eq(true) - end - end - - context 'and if: matches after changes' do - it 'includes both jobs' do - expect(pipeline).to be_persisted - expect(build_names).to contain_exactly('regular-job', 'rules-job', 'delayed-job') - end - - it 'sets when: for the created rules job based on the second clause' do - expect(regular_job.when).to eq('on_success') - expect(rules_job.when).to eq('on_success') - expect(delayed_job.when).to eq('delayed') - expect(delayed_job.options[:start_in]).to eq('1 hour') - end - end - - context 'and does not match' do - let(:ref_name) { 'refs/heads/wip' } - - it_behaves_like 'rules jobs are excluded' - - it 'sets when: for the created job' do - expect(regular_job.when).to eq('on_success') - end - end - end - - context 'with mixed if: and changes: clauses' do - let(:config) do - <<-EOY - regular-job: - script: 'echo Hello, World!' - - rules-job: - script: "echo hello world, $CI_COMMIT_REF_NAME" - rules: - - if: $CI_COMMIT_REF_NAME =~ /master/ - changes: [README.md] - when: on_success - allow_failure: true - - if: $CI_COMMIT_REF_NAME =~ /master/ - changes: [app.rb] - when: manual - EOY - end - - context 'with if matches and changes matches' do - before do - allow_any_instance_of(Ci::Pipeline) - .to receive(:modified_paths).and_return(%w[app.rb]) - end - - it 'persists all jobs' do - expect(pipeline).to be_persisted - expect(regular_job).to be_persisted - expect(rules_job).to be_persisted - expect(rules_job.when).to eq('manual') - expect(rules_job.allow_failure).to eq(false) - end - end - - context 'with if matches and no change matches' do - it_behaves_like 'rules jobs are excluded' - end - - context 'with change matches and no if matches' do - let(:ref_name) { 'refs/heads/feature' } - - before do - allow_any_instance_of(Ci::Pipeline) - .to receive(:modified_paths).and_return(%w[README.md]) - end - - it_behaves_like 'rules jobs are excluded' - end - - context 'and no matches' do - let(:ref_name) { 'refs/heads/feature' } - - it_behaves_like 'rules jobs are excluded' - end - end - - context 'with complex if: allow_failure usages' do - let(:config) do - <<-EOY - job-1: - script: "exit 1" - allow_failure: true - rules: - - if: $CI_COMMIT_REF_NAME =~ /master/ - allow_failure: false - - job-2: - script: "exit 1" - allow_failure: true - rules: - - if: $CI_COMMIT_REF_NAME =~ /nonexistant-branch/ - allow_failure: false - - job-3: - script: "exit 1" - rules: - - if: $CI_COMMIT_REF_NAME =~ /nonexistant-branch/ - allow_failure: true - - job-4: - script: "exit 1" - rules: - - if: $CI_COMMIT_REF_NAME =~ /master/ - allow_failure: false - - job-5: - script: "exit 1" - allow_failure: false - rules: - - if: $CI_COMMIT_REF_NAME =~ /master/ - allow_failure: true - - job-6: - script: "exit 1" - rules: - - if: $CI_COMMIT_REF_NAME =~ /nonexistant-branch/ - allow_failure: false - - allow_failure: true - EOY - end - - it 'creates a pipeline' do - expect(pipeline).to be_persisted - expect(build_names).to contain_exactly('job-1', 'job-4', 'job-5', 'job-6') - end - - it 'assigns job:allow_failure values to the builds' do - expect(find_job('job-1').allow_failure).to eq(false) - expect(find_job('job-4').allow_failure).to eq(false) - expect(find_job('job-5').allow_failure).to eq(true) - expect(find_job('job-6').allow_failure).to eq(true) - end - end - - context 'with complex if: allow_failure & when usages' do - let(:config) do - <<-EOY - job-1: - script: "exit 1" - rules: - - if: $CI_COMMIT_REF_NAME =~ /master/ - when: manual - - job-2: - script: "exit 1" - rules: - - if: $CI_COMMIT_REF_NAME =~ /master/ - when: manual - allow_failure: true - - job-3: - script: "exit 1" - allow_failure: true - rules: - - if: $CI_COMMIT_REF_NAME =~ /master/ - when: manual - - job-4: - script: "exit 1" - allow_failure: true - rules: - - if: $CI_COMMIT_REF_NAME =~ /master/ - when: manual - allow_failure: false - - job-5: - script: "exit 1" - rules: - - if: $CI_COMMIT_REF_NAME =~ /nonexistant-branch/ - when: manual - allow_failure: false - - when: always - allow_failure: true - - job-6: - script: "exit 1" - allow_failure: false - rules: - - if: $CI_COMMIT_REF_NAME =~ /master/ - when: manual - - job-7: - script: "exit 1" - allow_failure: false - rules: - - if: $CI_COMMIT_REF_NAME =~ /nonexistant-branch/ - when: manual - - when: :on_failure - allow_failure: true - EOY - end - - it 'creates a pipeline' do - expect(pipeline).to be_persisted - expect(build_names).to contain_exactly( - 'job-1', 'job-2', 'job-3', 'job-4', 'job-5', 'job-6', 'job-7' - ) - end - - it 'assigns job:allow_failure values to the builds' do - expect(find_job('job-1').allow_failure).to eq(false) - expect(find_job('job-2').allow_failure).to eq(true) - expect(find_job('job-3').allow_failure).to eq(true) - expect(find_job('job-4').allow_failure).to eq(false) - expect(find_job('job-5').allow_failure).to eq(true) - expect(find_job('job-6').allow_failure).to eq(false) - expect(find_job('job-7').allow_failure).to eq(true) - end - - it 'assigns job:when values to the builds' do - expect(find_job('job-1').when).to eq('manual') - expect(find_job('job-2').when).to eq('manual') - expect(find_job('job-3').when).to eq('manual') - expect(find_job('job-4').when).to eq('manual') - expect(find_job('job-5').when).to eq('always') - expect(find_job('job-6').when).to eq('manual') - expect(find_job('job-7').when).to eq('on_failure') - end - end - - context 'with deploy freeze period `if:` clause' do - # '0 23 * * 5' == "At 23:00 on Friday."", '0 7 * * 1' == "At 07:00 on Monday."" - let!(:freeze_period) { create(:ci_freeze_period, project: project, freeze_start: '0 23 * * 5', freeze_end: '0 7 * * 1') } - - context 'with 2 jobs' do - let(:config) do - <<-EOY - stages: - - test - - deploy - - test-job: - script: - - echo 'running TEST stage' - - deploy-job: - stage: deploy - script: - - echo 'running DEPLOY stage' - rules: - - if: $CI_DEPLOY_FREEZE == null - EOY - end - - context 'when outside freeze period' do - it 'creates two jobs' do - Timecop.freeze(2020, 4, 10, 22, 59) do - expect(pipeline).to be_persisted - expect(build_names).to contain_exactly('test-job', 'deploy-job') - end - end - end - - context 'when inside freeze period' do - it 'creates one job' do - Timecop.freeze(2020, 4, 10, 23, 1) do - expect(pipeline).to be_persisted - expect(build_names).to contain_exactly('test-job') - end - end - end - end - - context 'with 1 job' do - let(:config) do - <<-EOY - stages: - - deploy - - deploy-job: - stage: deploy - script: - - echo 'running DEPLOY stage' - rules: - - if: $CI_DEPLOY_FREEZE == null - EOY - end - - context 'when outside freeze period' do - it 'creates two jobs' do - Timecop.freeze(2020, 4, 10, 22, 59) do - expect(pipeline).to be_persisted - expect(build_names).to contain_exactly('deploy-job') - end - end - end - - context 'when inside freeze period' do - it 'does not create the pipeline', :aggregate_failures do - Timecop.freeze(2020, 4, 10, 23, 1) do - expect(response).to be_error - expect(pipeline).not_to be_persisted - end - end - end - end - end - - context 'with workflow rules with persisted variables' do - let(:config) do - <<-EOY - workflow: - rules: - - if: $CI_COMMIT_REF_NAME == "master" - - regular-job: - script: 'echo Hello, World!' - EOY - end - - context 'with matches' do - it 'creates a pipeline' do - expect(pipeline).to be_persisted - expect(build_names).to contain_exactly('regular-job') - end - end - - context 'with no matches' do - let(:ref_name) { 'refs/heads/feature' } - - it 'does not create a pipeline', :aggregate_failures do - expect(response).to be_error - expect(pipeline).not_to be_persisted - end - end - end - - context 'with workflow rules with pipeline variables' do - let(:pipeline) do - execute_service(variables_attributes: variables_attributes).payload - end - - let(:config) do - <<-EOY - workflow: - rules: - - if: $SOME_VARIABLE - - regular-job: - script: 'echo Hello, World!' - EOY - end - - context 'with matches' do - let(:variables_attributes) do - [{ key: 'SOME_VARIABLE', secret_value: 'SOME_VAR' }] - end - - it 'creates a pipeline' do - expect(pipeline).to be_persisted - expect(build_names).to contain_exactly('regular-job') - end - end - - context 'with no matches' do - let(:variables_attributes) { {} } - - it 'does not create a pipeline', :aggregate_failures do - expect(response).to be_error - expect(pipeline).not_to be_persisted - end - end - end - - context 'with workflow rules with trigger variables' do - let(:pipeline) do - execute_service do |pipeline| - pipeline.variables.build(variables) - end.payload - end - - let(:config) do - <<-EOY - workflow: - rules: - - if: $SOME_VARIABLE - - regular-job: - script: 'echo Hello, World!' - EOY - end - - context 'with matches' do - let(:variables) do - [{ key: 'SOME_VARIABLE', secret_value: 'SOME_VAR' }] - end - - it 'creates a pipeline' do - expect(pipeline).to be_persisted - expect(build_names).to contain_exactly('regular-job') - end - - context 'when a job requires the same variable' do - let(:config) do - <<-EOY - workflow: - rules: - - if: $SOME_VARIABLE - - build: - stage: build - script: 'echo build' - rules: - - if: $SOME_VARIABLE - - test1: - stage: test - script: 'echo test1' - needs: [build] - - test2: - stage: test - script: 'echo test2' - EOY - end - - it 'creates a pipeline' do - expect(pipeline).to be_persisted - expect(build_names).to contain_exactly('build', 'test1', 'test2') - end - end - end - - context 'with no matches' do - let(:variables) { {} } - - it 'does not create a pipeline', :aggregate_failures do - expect(response).to be_error - expect(pipeline).not_to be_persisted - end - - context 'when a job requires the same variable' do - let(:config) do - <<-EOY - workflow: - rules: - - if: $SOME_VARIABLE - - build: - stage: build - script: 'echo build' - rules: - - if: $SOME_VARIABLE - - test1: - stage: test - script: 'echo test1' - needs: [build] - - test2: - stage: test - script: 'echo test2' - EOY - end - - it 'does not create a pipeline', :aggregate_failures do - expect(response).to be_error - expect(pipeline).not_to be_persisted - end - end - end - end - - context 'with workflow rules changes' do - shared_examples 'comparing file changes with workflow rules' do - context 'when matches' do - before do - allow_next_instance_of(Ci::Pipeline) do |pipeline| - allow(pipeline).to receive(:modified_paths).and_return(%w[file1.md]) - end - end - - it 'creates the pipeline with a job' do - expect(pipeline).to be_persisted - expect(build_names).to contain_exactly('job') - end - end - - context 'when does not match' do - before do - allow_next_instance_of(Ci::Pipeline) do |pipeline| - allow(pipeline).to receive(:modified_paths).and_return(%w[unknown]) - end - end - - it 'creates the pipeline with a job' do - expect(pipeline.errors.full_messages).to eq(['Pipeline filtered out by workflow rules.']) - expect(response).to be_error - expect(pipeline).not_to be_persisted - end - end - end - - context 'changes is an array' do - let(:config) do - <<-EOY - workflow: - rules: - - changes: [file1.md] - - job: - script: exit 0 - EOY - end - - it_behaves_like 'comparing file changes with workflow rules' - end - - context 'changes:paths is an array' do - let(:config) do - <<-EOY - workflow: - rules: - - changes: - paths: [file1.md] - - job: - script: exit 0 - EOY - end - - it_behaves_like 'comparing file changes with workflow rules' - end - end - end end describe '#execute!' do diff --git a/spec/services/uploads/destroy_service_spec.rb b/spec/services/uploads/destroy_service_spec.rb new file mode 100644 index 00000000000..bb58da231b6 --- /dev/null +++ b/spec/services/uploads/destroy_service_spec.rb @@ -0,0 +1,103 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Uploads::DestroyService do + let_it_be(:project) { create(:project) } + let_it_be(:user) { create(:user) } + let_it_be_with_reload(:upload) { create(:upload, :issuable_upload, model: project) } + + let(:filename) { File.basename(upload.path) } + let(:secret) { upload.secret } + let(:model) { project } + let(:service) { described_class.new(model, user) } + + describe '#execute' do + subject { service.execute(secret, filename) } + + shared_examples_for 'upload not found' do + it 'does not delete any upload' do + expect { subject }.not_to change { Upload.count } + end + + it 'returns an error' do + expect(subject[:status]).to eq(:error) + expect(subject[:message]).to eq("The resource that you are attempting to access does not "\ + "exist or you don't have permission to perform this action.") + end + end + + context 'when user is nil' do + let(:user) { nil } + + it_behaves_like 'upload not found' + end + + context 'when user cannot destroy upload' do + before do + project.add_developer(user) + end + + it_behaves_like 'upload not found' + end + + context 'when user can destroy upload' do + before do + project.add_maintainer(user) + end + + it 'deletes the upload' do + expect { subject }.to change { Upload.count }.by(-1) + end + + it 'returns success response' do + expect(subject[:status]).to eq(:success) + expect(subject[:upload]).to eq(upload) + end + + context 'when upload is not found' do + let(:filename) { 'not existing filename' } + + it_behaves_like 'upload not found' + end + + context 'when upload secret is not found' do + let(:secret) { 'aaaaaaaaaa' } + + it_behaves_like 'upload not found' + end + + context 'when upload secret has invalid format' do + let(:secret) { 'invalid' } + + it_behaves_like 'upload not found' + end + + context 'when unknown model is used' do + let(:model) { user } + + it 'raises an error' do + expect { subject }.to raise_exception(ArgumentError) + end + end + + context 'when upload belongs to other model' do + let_it_be(:upload) { create(:upload, :namespace_upload) } + + it_behaves_like 'upload not found' + end + + context 'when upload destroy fails' do + before do + allow(service).to receive(:find_upload).and_return(upload) + allow(upload).to receive(:destroy).and_return(false) + end + + it 'returns error' do + expect(subject[:status]).to eq(:error) + expect(subject[:message]).to eq('Upload could not be deleted.') + end + end + end + end +end diff --git a/spec/support/shared_contexts/policies/group_policy_shared_context.rb b/spec/support/shared_contexts/policies/group_policy_shared_context.rb index eec6e92c5fe..893d3702407 100644 --- a/spec/support/shared_contexts/policies/group_policy_shared_context.rb +++ b/spec/support/shared_contexts/policies/group_policy_shared_context.rb @@ -56,6 +56,7 @@ RSpec.shared_context 'GroupPolicy context' do admin_package create_projects create_cluster update_cluster admin_cluster add_cluster + destroy_upload ] end diff --git a/spec/support/shared_contexts/policies/project_policy_shared_context.rb b/spec/support/shared_contexts/policies/project_policy_shared_context.rb index 789b385c435..1d4731d9b39 100644 --- a/spec/support/shared_contexts/policies/project_policy_shared_context.rb +++ b/spec/support/shared_contexts/policies/project_policy_shared_context.rb @@ -62,6 +62,7 @@ RSpec.shared_context 'ProjectPolicy context' do admin_project admin_project_member admin_snippet admin_terraform_state admin_wiki create_deploy_token destroy_deploy_token push_to_delete_protected_branch read_deploy_token update_snippet + destroy_upload ] end diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb index 4ddb793516f..d632ca39e44 100644 --- a/spec/workers/post_receive_spec.rb +++ b/spec/workers/post_receive_spec.rb @@ -452,6 +452,12 @@ RSpec.describe PostReceive do perform end + it 'updates the snippet model updated_at' do + expect(snippet).to receive(:touch) + + perform + end + it 'updates snippet statistics' do expect(Snippets::UpdateStatisticsService).to receive(:new).with(snippet).and_call_original diff --git a/tooling/config/CODEOWNERS.yml b/tooling/config/CODEOWNERS.yml index a8ae90437e2..18f3abfc97b 100644 --- a/tooling/config/CODEOWNERS.yml +++ b/tooling/config/CODEOWNERS.yml @@ -3,7 +3,7 @@ # And paste the contents into .gitlab/CODEOWNERS '[Authentication and Authorization]': - '@gitlab-org/manage/authentication-and-authorization': + '@gitlab-org/manage/authentication-and-authorization/approvers': allow: keywords: - 'password' |