diff options
70 files changed, 607 insertions, 203 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 6088a1b3515..adf097b52f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,17 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 10.2.4 (2017-12-07) + +### Security (5 changes) + +- Fix e-mail address disclosure through member search fields +- Prevent creating issues through API when user does not have permissions +- Prevent an information disclosure in the Groups API +- Fix user without access to private Wiki being able to see it on the project page +- Fix Cross-Site Scripting (XSS) vulnerability while editing a comment + + ## 10.2.3 (2017-11-30) ### Fixed (7 changes) @@ -237,6 +248,17 @@ entry. - Add Gitaly metrics to the performance bar. +## 10.1.5 (2017-12-07) + +### Security (5 changes) + +- Fix e-mail address disclosure through member search fields +- Prevent creating issues through API when user does not have permissions +- Prevent an information disclosure in the Groups API +- Fix user without access to private Wiki being able to see it on the project page +- Fix Cross-Site Scripting (XSS) vulnerability while editing a comment + + ## 10.1.4 (2017-11-14) ### Fixed (4 changes) @@ -485,6 +507,17 @@ entry. - creation of keys moved to services. !13331 (haseebeqx) - Add username as GL_USERNAME in hooks. +## 10.0.7 (2017-12-07) + +### Security (5 changes) + +- Fix e-mail address disclosure through member search fields +- Prevent creating issues through API when user does not have permissions +- Prevent an information disclosure in the Groups API +- Fix user without access to private Wiki being able to see it on the project page +- Fix Cross-Site Scripting (XSS) vulnerability while editing a comment + + ## 10.0.5 (2017-11-03) - [FIXED] Fix incorrect X-axis labels in Prometheus graphs. !14258 @@ -283,7 +283,7 @@ group :metrics do gem 'influxdb', '~> 0.2', require: false # Prometheus - gem 'prometheus-client-mmap', '~> 0.7.0.beta39' + gem 'prometheus-client-mmap', '~> 0.7.0.beta43' gem 'raindrops', '~> 0.18' end diff --git a/Gemfile.lock b/Gemfile.lock index b5ca351fea8..efae71efdb7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1113,7 +1113,7 @@ DEPENDENCIES peek-sidekiq (~> 1.0.3) pg (~> 0.18.2) premailer-rails (~> 1.9.7) - prometheus-client-mmap (~> 0.7.0.beta39) + prometheus-client-mmap (~> 0.7.0.beta43) pry-byebug (~> 3.4.1) pry-rails (~> 0.3.4) rack-attack (~> 4.4.1) diff --git a/app/assets/javascripts/contextual_sidebar.js b/app/assets/javascripts/contextual_sidebar.js index 46b68ebe158..cd20dde2951 100644 --- a/app/assets/javascripts/contextual_sidebar.js +++ b/app/assets/javascripts/contextual_sidebar.js @@ -28,7 +28,7 @@ export default class ContextualSidebar { this.$closeSidebar.on('click', () => this.toggleSidebarNav(false)); this.$overlay.on('click', () => this.toggleSidebarNav(false)); this.$sidebarToggle.on('click', () => { - const value = !this.$sidebar.hasClass('sidebar-icons-only'); + const value = !this.$sidebar.hasClass('sidebar-collapsed-desktop'); this.toggleCollapsedSidebar(value); }); @@ -43,16 +43,16 @@ export default class ContextualSidebar { } toggleSidebarNav(show) { - this.$sidebar.toggleClass('nav-sidebar-expanded', show); + this.$sidebar.toggleClass('sidebar-expanded-mobile', show); this.$overlay.toggleClass('mobile-nav-open', show); - this.$sidebar.removeClass('sidebar-icons-only'); + this.$sidebar.removeClass('sidebar-collapsed-desktop'); } toggleCollapsedSidebar(collapsed) { const breakpoint = bp.getBreakpointSize(); if (this.$sidebar.length) { - this.$sidebar.toggleClass('sidebar-icons-only', collapsed); + this.$sidebar.toggleClass('sidebar-collapsed-desktop', collapsed); this.$page.toggleClass('page-with-icon-sidebar', breakpoint === 'sm' ? true : collapsed); } ContextualSidebar.setCollapsedCookie(collapsed); diff --git a/app/assets/javascripts/fly_out_nav.js b/app/assets/javascripts/fly_out_nav.js index 98837c3b2a0..6110d961609 100644 --- a/app/assets/javascripts/fly_out_nav.js +++ b/app/assets/javascripts/fly_out_nav.js @@ -21,7 +21,7 @@ let headerHeight = 50; export const getHeaderHeight = () => headerHeight; -export const isSidebarCollapsed = () => sidebar && sidebar.classList.contains('sidebar-icons-only'); +export const isSidebarCollapsed = () => sidebar && sidebar.classList.contains('sidebar-collapsed-desktop'); export const canShowActiveSubItems = (el) => { if (el.classList.contains('active') && !isSidebarCollapsed()) { diff --git a/app/assets/javascripts/notes/components/issue_note.vue b/app/assets/javascripts/notes/components/issue_note.vue index 8c81c5d6df3..3ceb961f58e 100644 --- a/app/assets/javascripts/notes/components/issue_note.vue +++ b/app/assets/javascripts/notes/components/issue_note.vue @@ -1,5 +1,6 @@ <script> import { mapGetters, mapActions } from 'vuex'; + import { escape } from 'underscore'; import Flash from '../../flash'; import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue'; import noteHeader from './note_header.vue'; @@ -85,7 +86,7 @@ }; this.isRequesting = true; this.oldContent = this.note.note_html; - this.note.note_html = noteText; + this.note.note_html = escape(noteText); this.updateNote(data) .then(() => { diff --git a/app/assets/stylesheets/framework/contextual-sidebar.scss b/app/assets/stylesheets/framework/contextual-sidebar.scss index b73932eb7e1..26a2db99e0a 100644 --- a/app/assets/stylesheets/framework/contextual-sidebar.scss +++ b/app/assets/stylesheets/framework/contextual-sidebar.scss @@ -1,4 +1,6 @@ .page-with-contextual-sidebar { + transition: padding-left $sidebar-transition-duration; + @media (min-width: $screen-md-min) { padding-left: $contextual-sidebar-collapsed-width; } @@ -27,8 +29,10 @@ .context-header { position: relative; margin-right: 2px; + width: $contextual-sidebar-width; a { + transition: padding $sidebar-transition-duration; font-weight: $gl-font-weight-bold; display: flex; align-items: center; @@ -63,10 +67,10 @@ } .nav-sidebar { + transition: width $sidebar-transition-duration, left $sidebar-transition-duration; position: fixed; z-index: 400; width: $contextual-sidebar-width; - transition: left $sidebar-transition-duration; top: $header-height; bottom: 0; left: 0; @@ -74,16 +78,15 @@ box-shadow: inset -2px 0 0 $border-color; transform: translate3d(0, 0, 0); - &:not(.sidebar-icons-only) { + &:not(.sidebar-collapsed-desktop) { @media (min-width: $screen-sm-min) and (max-width: $screen-md-max) { box-shadow: inset -2px 0 0 $border-color, 2px 1px 3px $dropdown-shadow-color; } } - &.sidebar-icons-only { - width: auto; - min-width: $contextual-sidebar-collapsed-width; + &.sidebar-collapsed-desktop { + width: $contextual-sidebar-collapsed-width; .nav-sidebar-inner-scroll { overflow-x: hidden; @@ -108,12 +111,11 @@ } } - &.nav-sidebar-expanded { + &.sidebar-expanded-mobile { left: 0; } a { - transition: none; text-decoration: none; } @@ -126,9 +128,10 @@ white-space: nowrap; a { + transition: padding $sidebar-transition-duration; display: flex; align-items: center; - padding: 12px 16px; + padding: 12px 15px; color: $gl-text-color-secondary; } @@ -288,7 +291,8 @@ > a { margin-left: 4px; - padding-left: 12px; + // Subtract width of left border on active element + padding-left: 11px; } .badge { @@ -313,6 +317,7 @@ .toggle-sidebar-button, .close-nav-button { width: $contextual-sidebar-width - 2px; + transition: width $sidebar-transition-duration; position: fixed; bottom: 0; padding: 16px; @@ -343,20 +348,21 @@ } } +.collapse-text { + white-space: nowrap; + overflow: hidden; +} -.sidebar-icons-only { +.sidebar-collapsed-desktop { .context-header { - height: 61px; + height: 60px; + width: $contextual-sidebar-collapsed-width; a { padding: 10px 4px; } } - li a { - padding: 12px 15px; - } - .sidebar-top-level-items > li { &.active a { padding-left: 12px; @@ -374,8 +380,8 @@ } .toggle-sidebar-button { - width: $contextual-sidebar-collapsed-width - 2px; padding: 16px; + width: $contextual-sidebar-collapsed-width - 2px; .collapse-text, .icon-angle-double-left { diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 4f99c27eff1..a6fa96d834c 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -5,10 +5,9 @@ $grid-size: 8px; $gutter_collapsed_width: 62px; $gutter_width: 290px; $gutter_inner_width: 250px; -$sidebar-transition-duration: .15s; +$sidebar-transition-duration: .3s; $sidebar-breakpoint: 1024px; $default-transition-duration: .15s; -$right-sidebar-transition-duration: .3s; $contextual-sidebar-width: 220px; $contextual-sidebar-collapsed-width: 50px; diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index 3683afa07de..862ea379cbc 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -57,7 +57,7 @@ position: relative; @media (min-width: $screen-sm-min) { - transition: width $right-sidebar-transition-duration; + transition: width $sidebar-transition-duration; width: 100%; &.is-compact { @@ -453,8 +453,8 @@ .right-sidebar.right-sidebar-expanded { &.boards-sidebar-slide-enter-active, &.boards-sidebar-slide-leave-active { - transition: width $right-sidebar-transition-duration, - padding $right-sidebar-transition-duration; + transition: width $sidebar-transition-duration, + padding $sidebar-transition-duration; } &.boards-sidebar-slide-enter, diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 11ee1232bfe..32f2fa88236 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -126,7 +126,7 @@ top: $header-height; bottom: 0; right: 0; - transition: width $right-sidebar-transition-duration; + transition: width $sidebar-transition-duration; background: $gray-light; z-index: 200; overflow: hidden; diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb index 8fc234a62b1..5919bf54468 100644 --- a/app/controllers/groups/group_members_controller.rb +++ b/app/controllers/groups/group_members_controller.rb @@ -22,7 +22,7 @@ class Groups::GroupMembersController < Groups::ApplicationController end def update - @group_member = @group.group_members.find(params[:id]) + @group_member = @group.members_and_requesters.find(params[:id]) return render_403 unless can?(current_user, :update_group_member, @group_member) diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index d925dcd21ff..5a01a59481b 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -26,7 +26,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController end def update - @project_member = @project.project_members.find(params[:id]) + @project_member = @project.members_and_requesters.find(params[:id]) return render_403 unless can?(current_user, :update_project_member, @project_member) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 3882fa4791d..8e9d6766d80 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -272,7 +272,7 @@ class ProjectsController < Projects::ApplicationController render 'projects/empty' if @project.empty_repo? else - if @project.wiki_enabled? + if can?(current_user, :read_wiki, @project) @project_wiki = @project.wiki @wiki_home = @project_wiki.find_page('home', params[:version_id]) elsif @project.feature_available?(:issues, current_user) diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb index 8e822ed0ea2..aaee6eaeedd 100644 --- a/app/helpers/preferences_helper.rb +++ b/app/helpers/preferences_helper.rb @@ -58,7 +58,7 @@ module PreferencesHelper user_view elsif user_view == "activity" "activity" - elsif @project.wiki_enabled? + elsif can?(current_user, :read_wiki, @project) "wiki" elsif @project.feature_available?(:issues, current_user) "projects/issues/issues" diff --git a/app/models/issue.rb b/app/models/issue.rb index 33db197e612..bbda848c39d 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -10,6 +10,9 @@ class Issue < ActiveRecord::Base include RelativePositioning include TimeTrackable include ThrottledTouch + include IgnorableColumn + + ignore_column :assignee_id DueDateStruct = Struct.new(:title, :name).freeze NoDueDate = DueDateStruct.new('No Due Date', '0').freeze diff --git a/app/models/user.rb b/app/models/user.rb index 093ff808626..92b461ce3ed 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -315,6 +315,8 @@ class User < ActiveRecord::Base # # Returns an ActiveRecord::Relation. def search(query) + query = query.downcase + order = <<~SQL CASE WHEN users.name = %{query} THEN 0 @@ -324,8 +326,11 @@ class User < ActiveRecord::Base END SQL - fuzzy_search(query, [:name, :email, :username]) - .reorder(order % { query: ActiveRecord::Base.connection.quote(query) }, :name) + where( + fuzzy_arel_match(:name, query) + .or(fuzzy_arel_match(:username, query)) + .or(arel_table[:email].eq(query)) + ).reorder(order % { query: ActiveRecord::Base.connection.quote(query) }, :name) end # searches user by given pattern @@ -333,15 +338,17 @@ class User < ActiveRecord::Base # This method uses ILIKE on PostgreSQL and LIKE on MySQL. def search_with_secondary_emails(query) + query = query.downcase + email_table = Email.arel_table matched_by_emails_user_ids = email_table .project(email_table[:user_id]) - .where(Email.fuzzy_arel_match(:email, query)) + .where(email_table[:email].eq(query)) where( fuzzy_arel_match(:name, query) - .or(fuzzy_arel_match(:email, query)) .or(fuzzy_arel_match(:username, query)) + .or(arel_table[:email].eq(query)) .or(arel_table[:id].in(matched_by_emails_user_ids)) ) end diff --git a/app/views/layouts/nav/sidebar/_admin.html.haml b/app/views/layouts/nav/sidebar/_admin.html.haml index 0ec07605631..cb8db306b56 100644 --- a/app/views/layouts/nav/sidebar/_admin.html.haml +++ b/app/views/layouts/nav/sidebar/_admin.html.haml @@ -1,4 +1,4 @@ -.nav-sidebar{ class: ("sidebar-icons-only" if collapsed_sidebar?) } +.nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?) } .nav-sidebar-inner-scroll .context-header = link_to admin_root_path, title: 'Admin Overview' do diff --git a/app/views/layouts/nav/sidebar/_group.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml index 0bf318b0b66..0c27b09f7b1 100644 --- a/app/views/layouts/nav/sidebar/_group.html.haml +++ b/app/views/layouts/nav/sidebar/_group.html.haml @@ -1,7 +1,7 @@ - issues = IssuesFinder.new(current_user, group_id: @group.id, state: 'opened').execute - merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened', non_archived: true).execute -.nav-sidebar{ class: ("sidebar-icons-only" if collapsed_sidebar?) } +.nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?) } .nav-sidebar-inner-scroll .context-header = link_to group_path(@group), title: @group.name do diff --git a/app/views/layouts/nav/sidebar/_profile.html.haml b/app/views/layouts/nav/sidebar/_profile.html.haml index 7e23f9c1f05..a5a62a0695f 100644 --- a/app/views/layouts/nav/sidebar/_profile.html.haml +++ b/app/views/layouts/nav/sidebar/_profile.html.haml @@ -1,4 +1,4 @@ -.nav-sidebar{ class: ("sidebar-icons-only" if collapsed_sidebar?) } +.nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?) } .nav-sidebar-inner-scroll .context-header = link_to profile_path, title: 'Profile Settings' do diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml index 53a9162b703..be39f577ba7 100644 --- a/app/views/layouts/nav/sidebar/_project.html.haml +++ b/app/views/layouts/nav/sidebar/_project.html.haml @@ -1,4 +1,4 @@ -.nav-sidebar{ class: ("sidebar-icons-only" if collapsed_sidebar?) } +.nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?) } .nav-sidebar-inner-scroll - can_edit = can?(current_user, :admin_project, @project) .context-header diff --git a/changelogs/unreleased/15832-fix-access-level-update-for-requesters.yml b/changelogs/unreleased/15832-fix-access-level-update-for-requesters.yml new file mode 100644 index 00000000000..9d6c958cb3e --- /dev/null +++ b/changelogs/unreleased/15832-fix-access-level-update-for-requesters.yml @@ -0,0 +1,5 @@ +--- +title: Fix error that was preventing users to change the access level of access requests for Groups or Projects +merge_request: 15832 +author: +type: fixed diff --git a/changelogs/unreleased/35724-animate-sidebar.yml b/changelogs/unreleased/35724-animate-sidebar.yml new file mode 100644 index 00000000000..5d0b46a23c8 --- /dev/null +++ b/changelogs/unreleased/35724-animate-sidebar.yml @@ -0,0 +1,5 @@ +--- +title: Animate contextual sidebar on collapse/expand +merge_request: +author: +type: changed diff --git a/changelogs/unreleased/sh-remove-allocation-tracking-influxdb.yml b/changelogs/unreleased/sh-remove-allocation-tracking-influxdb.yml new file mode 100644 index 00000000000..b98573df303 --- /dev/null +++ b/changelogs/unreleased/sh-remove-allocation-tracking-influxdb.yml @@ -0,0 +1,5 @@ +--- +title: Remove allocation tracking code from InfluxDB sampler for performance +merge_request: +author: +type: performance diff --git a/db/migrate/20160610194713_remove_deprecated_issues_tracker_columns_from_projects.rb b/db/migrate/20160610194713_remove_deprecated_issues_tracker_columns_from_projects.rb index 477b2106dea..21b367711c3 100644 --- a/db/migrate/20160610194713_remove_deprecated_issues_tracker_columns_from_projects.rb +++ b/db/migrate/20160610194713_remove_deprecated_issues_tracker_columns_from_projects.rb @@ -1,3 +1,4 @@ +# rubocop:disable Migration/RemoveColumn class RemoveDeprecatedIssuesTrackerColumnsFromProjects < ActiveRecord::Migration def change remove_column :projects, :issues_tracker, :string, default: 'gitlab', null: false diff --git a/db/migrate/20160610301627_remove_notification_level_from_users.rb b/db/migrate/20160610301627_remove_notification_level_from_users.rb index 8afb14df2cf..356e53b4b23 100644 --- a/db/migrate/20160610301627_remove_notification_level_from_users.rb +++ b/db/migrate/20160610301627_remove_notification_level_from_users.rb @@ -1,3 +1,4 @@ +# rubocop:disable Migration/RemoveColumn class RemoveNotificationLevelFromUsers < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20160705055809_remove_developers_can_push_from_protected_branches.rb b/db/migrate/20160705055809_remove_developers_can_push_from_protected_branches.rb index 52a9819c628..058bd539e65 100644 --- a/db/migrate/20160705055809_remove_developers_can_push_from_protected_branches.rb +++ b/db/migrate/20160705055809_remove_developers_can_push_from_protected_branches.rb @@ -1,3 +1,4 @@ +# rubocop:disable Migration/RemoveColumn # See http://doc.gitlab.com/ce/development/migration_style_guide.html # for more information on how to write migrations for GitLab. diff --git a/db/migrate/20160705055813_remove_developers_can_merge_from_protected_branches.rb b/db/migrate/20160705055813_remove_developers_can_merge_from_protected_branches.rb index 4a7bde7f9f3..d0e5da4d28b 100644 --- a/db/migrate/20160705055813_remove_developers_can_merge_from_protected_branches.rb +++ b/db/migrate/20160705055813_remove_developers_can_merge_from_protected_branches.rb @@ -1,3 +1,4 @@ +# rubocop:disable Migration/RemoveColumn # See http://doc.gitlab.com/ce/development/migration_style_guide.html # for more information on how to write migrations for GitLab. diff --git a/db/migrate/20160729173930_remove_project_id_from_spam_logs.rb b/db/migrate/20160729173930_remove_project_id_from_spam_logs.rb index e28ab31d629..baf254c3bcc 100644 --- a/db/migrate/20160729173930_remove_project_id_from_spam_logs.rb +++ b/db/migrate/20160729173930_remove_project_id_from_spam_logs.rb @@ -1,3 +1,4 @@ +# rubocop:disable Migration/RemoveColumn # See http://doc.gitlab.com/ce/development/migration_style_guide.html # for more information on how to write migrations for GitLab. diff --git a/db/migrate/20160831223750_remove_features_enabled_from_projects.rb b/db/migrate/20160831223750_remove_features_enabled_from_projects.rb index aec709aaf59..9eafd8b9477 100644 --- a/db/migrate/20160831223750_remove_features_enabled_from_projects.rb +++ b/db/migrate/20160831223750_remove_features_enabled_from_projects.rb @@ -1,3 +1,4 @@ +# rubocop:disable Migration/RemoveColumn # See http://doc.gitlab.com/ce/development/migration_style_guide.html # for more information on how to write migrations for GitLab. diff --git a/db/migrate/20160913162434_remove_projects_pushes_since_gc.rb b/db/migrate/20160913162434_remove_projects_pushes_since_gc.rb index df7d922b816..f32167037e0 100644 --- a/db/migrate/20160913162434_remove_projects_pushes_since_gc.rb +++ b/db/migrate/20160913162434_remove_projects_pushes_since_gc.rb @@ -1,3 +1,4 @@ +# rubocop:disable Migration/RemoveColumn # See http://doc.gitlab.com/ce/development/migration_style_guide.html # for more information on how to write migrations for GitLab. diff --git a/db/migrate/20161018024550_remove_priority_from_labels.rb b/db/migrate/20161018024550_remove_priority_from_labels.rb index b7416cca664..bc25a43526c 100644 --- a/db/migrate/20161018024550_remove_priority_from_labels.rb +++ b/db/migrate/20161018024550_remove_priority_from_labels.rb @@ -1,3 +1,4 @@ +# rubocop:disable Migration/RemoveColumn class RemovePriorityFromLabels < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20161201160452_migrate_project_statistics.rb b/db/migrate/20161201160452_migrate_project_statistics.rb index 82fbdf02444..a547409aaa5 100644 --- a/db/migrate/20161201160452_migrate_project_statistics.rb +++ b/db/migrate/20161201160452_migrate_project_statistics.rb @@ -1,3 +1,4 @@ +# rubocop:disable Migration/RemoveColumn class MigrateProjectStatistics < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20170222143500_remove_old_project_id_columns.rb b/db/migrate/20170222143500_remove_old_project_id_columns.rb index 268144a2552..9bed38a3444 100644 --- a/db/migrate/20170222143500_remove_old_project_id_columns.rb +++ b/db/migrate/20170222143500_remove_old_project_id_columns.rb @@ -1,3 +1,4 @@ +# rubocop:disable Migration/RemoveColumn # rubocop:disable RemoveIndex class RemoveOldProjectIdColumns < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20170301205639_remove_unused_ci_tables_and_columns.rb b/db/migrate/20170301205639_remove_unused_ci_tables_and_columns.rb index 1a77d5934a3..0535c2ddaf2 100644 --- a/db/migrate/20170301205639_remove_unused_ci_tables_and_columns.rb +++ b/db/migrate/20170301205639_remove_unused_ci_tables_and_columns.rb @@ -1,3 +1,4 @@ +# rubocop:disable Migration/RemoveColumn # rubocop:disable Migration/Datetime class RemoveUnusedCiTablesAndColumns < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20170315174634_revert_add_notified_of_own_activity_to_users.rb b/db/migrate/20170315174634_revert_add_notified_of_own_activity_to_users.rb index 807dfcb385d..9b9098d115d 100644 --- a/db/migrate/20170315174634_revert_add_notified_of_own_activity_to_users.rb +++ b/db/migrate/20170315174634_revert_add_notified_of_own_activity_to_users.rb @@ -1,3 +1,4 @@ +# rubocop:disable Migration/RemoveColumn # rubocop:disable Migration/UpdateLargeTable class RevertAddNotifiedOfOwnActivityToUsers < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20171106154015_remove_issues_branch_name.rb b/db/migrate/20171106154015_remove_issues_branch_name.rb index 3d08225c96d..162b6bafab4 100644 --- a/db/migrate/20171106154015_remove_issues_branch_name.rb +++ b/db/migrate/20171106154015_remove_issues_branch_name.rb @@ -1,3 +1,4 @@ +# rubocop:disable Migration/RemoveColumn # See http://doc.gitlab.com/ce/development/migration_style_guide.html # for more information on how to write migrations for GitLab. diff --git a/db/post_migrate/20170523073948_remove_assignee_id_from_issue.rb b/db/post_migrate/20170523073948_remove_assignee_id_from_issue.rb new file mode 100644 index 00000000000..006d17b4d62 --- /dev/null +++ b/db/post_migrate/20170523073948_remove_assignee_id_from_issue.rb @@ -0,0 +1,48 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class RemoveAssigneeIdFromIssue < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + # When a migration requires downtime you **must** uncomment the following + # constant and define a short and easy to understand explanation as to why the + # migration requires downtime. + # DOWNTIME_REASON = '' + + # When using the methods "add_concurrent_index", "remove_concurrent_index" or + # "add_column_with_default" you must disable the use of transactions + # as these methods can not run in an existing transaction. + # When using "add_concurrent_index" or "remove_concurrent_index" methods make sure + # that either of them is the _only_ method called in the migration, + # any other changes should go in a separate migration. + # This ensures that upon failure _only_ the index creation or removing fails + # and can be retried or reverted easily. + # + # To disable transactions uncomment the following line and remove these + # comments: + disable_ddl_transaction! + + class Issue < ActiveRecord::Base + self.table_name = 'issues' + + include ::EachBatch + end + + def up + remove_column :issues, :assignee_id + end + + def down + add_column :issues, :assignee_id, :integer + add_concurrent_index :issues, :assignee_id + + update_value = Arel.sql('(SELECT user_id FROM issue_assignees WHERE issue_assignees.issue_id = issues.id LIMIT 1)') + + # This is only used in the down step, so we can ignore the RuboCop warning + # about large tables, as this is very unlikely to be run on GitLab.com + update_column_in_batches(:issues, :assignee_id, update_value) # rubocop:disable Migration/UpdateLargeTable + end +end diff --git a/db/schema.rb b/db/schema.rb index c0a141885ad..f0b1da16d53 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -841,7 +841,6 @@ ActiveRecord::Schema.define(version: 20171206221519) do create_table "issues", force: :cascade do |t| t.string "title" - t.integer "assignee_id" t.integer "author_id" t.integer "project_id" t.datetime "created_at" @@ -867,7 +866,6 @@ ActiveRecord::Schema.define(version: 20171206221519) do t.datetime_with_timezone "closed_at" end - add_index "issues", ["assignee_id"], name: "index_issues_on_assignee_id", using: :btree add_index "issues", ["author_id"], name: "index_issues_on_author_id", using: :btree add_index "issues", ["confidential"], name: "index_issues_on_confidential", using: :btree add_index "issues", ["deleted_at"], name: "index_issues_on_deleted_at", using: :btree diff --git a/doc/administration/index.md b/doc/administration/index.md index c8d28d8485a..e6986a2b07f 100644 --- a/doc/administration/index.md +++ b/doc/administration/index.md @@ -35,7 +35,7 @@ Learn how to install, configure, update, and maintain your GitLab instance. - [Raketasks](../raketasks/README.md): Perform various tasks for maintenance, backups, automatic webhooks setup, etc. - [Backup and restore](../raketasks/backup_restore.md): Backup and restore your GitLab instance. -- [Operations](operations.md): Keeping GitLab up and running (clean up Redis sessions, moving repositories, Sidekiq Job throttling, Sidekiq MemoryKiller, Unicorn). +- [Operations](operations/index.md): Keeping GitLab up and running (clean up Redis sessions, moving repositories, Sidekiq Job throttling, Sidekiq MemoryKiller, Unicorn). - [Restart GitLab](restart_gitlab.md): Learn how to restart GitLab and its components. #### Updating GitLab diff --git a/doc/administration/monitoring/prometheus/gitlab_metrics.md b/doc/administration/monitoring/prometheus/gitlab_metrics.md index 11d5e077a36..f495990d9a4 100644 --- a/doc/administration/monitoring/prometheus/gitlab_metrics.md +++ b/doc/administration/monitoring/prometheus/gitlab_metrics.md @@ -45,8 +45,9 @@ In this experimental phase, only a few metrics are available: | redis_ping_success | Gauge | 9.4 | Whether or not the last redis ping succeeded | | redis_ping_latency_seconds | Gauge | 9.4 | Round trip time of the redis ping | | user_session_logins_total | Counter | 9.4 | Counter of how many users have logged in | -| filesystem_circuitbreaker_latency_seconds | Histogram | 9.5 | Latency of the stat check the circuitbreaker uses to probe a shard | +| filesystem_circuitbreaker_latency_seconds | Gauge | 9.5 | Time spent validating if a storage is accessible | | filesystem_circuitbreaker | Gauge | 9.5 | Wether or not the circuit for a certain shard is broken or not | +| circuitbreaker_storage_check_duration_seconds | Histogram | 10.3 | Time a single storage probe took | ## Metrics shared directory diff --git a/doc/administration/operations.md b/doc/administration/operations.md index 0daceb98d99..4797d2a3206 100644 --- a/doc/administration/operations.md +++ b/doc/administration/operations.md @@ -1,7 +1 @@ -# GitLab operations - -- [Sidekiq MemoryKiller](operations/sidekiq_memory_killer.md) -- [Sidekiq Job throttling](operations/sidekiq_job_throttling.md) -- [Cleaning up Redis sessions](operations/cleaning_up_redis_sessions.md) -- [Understanding Unicorn and unicorn-worker-killer](operations/unicorn.md) -- [Moving repositories to a new location](operations/moving_repositories.md) +This document was moved to [another location](operations/index.md). diff --git a/doc/administration/operations/index.md b/doc/administration/operations/index.md new file mode 100644 index 00000000000..320d71a9527 --- /dev/null +++ b/doc/administration/operations/index.md @@ -0,0 +1,16 @@ +# Performing Operations in GitLab + +Keep your GitLab instance up and running smoothly. + +- [Clean up Redis sessions](cleaning_up_redis_sessions.md): Prior to GitLab 7.3, +user sessions did not automatically expire from Redis. If +you have been running a large GitLab server (thousands of users) since before +GitLab 7.3 we recommend cleaning up stale sessions to compact the Redis +database after you upgrade to GitLab 7.3. +- [Moving repositories](moving_repositories.md): Moving all repositories managed +by GitLab to another file system or another server. +- [Sidekiq job throttling](sidekiq_job_throttling.md): Throttle Sidekiq queues +that to prioritize important jobs. +- [Sidekiq MemoryKiller](sidekiq_memory_killer.md): Configure Sidekiq MemoryKiller +to restart Sidekiq. +- [Unicorn](unicorn.md): Understand Unicorn and unicorn-worker-killer.
\ No newline at end of file diff --git a/doc/development/automatic_ce_ee_merge.md b/doc/development/automatic_ce_ee_merge.md index 9e59ddc8cce..4b9791c95bc 100644 --- a/doc/development/automatic_ce_ee_merge.md +++ b/doc/development/automatic_ce_ee_merge.md @@ -17,7 +17,7 @@ someone who is familiar with the code you touched. [`CE Upstream` merge requests]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests?label_name%5B%5D=CE+upstream -### Always merge EE merge requests before their CE counterparts +## Always merge EE merge requests before their CE counterparts **In order to avoid conflicts in the CE->EE merge, you should always merge the EE version of your CE merge request first, if present.** diff --git a/doc/operations/README.md b/doc/operations/README.md index 58f16aff7bd..d7a83948b87 100644 --- a/doc/operations/README.md +++ b/doc/operations/README.md @@ -1 +1 @@ -This document was moved to [administration/operations](../administration/operations.md). +This document was moved to [another location](../administration/operations/index.md). diff --git a/features/steps/groups.rb b/features/steps/groups.rb index a2d9a0332e0..753694a5392 100644 --- a/features/steps/groups.rb +++ b/features/steps/groups.rb @@ -138,7 +138,7 @@ class Spinach::Features::Groups < Spinach::FeatureSteps private def assigned_to_me(key) - project.send(key).where(assignee_id: current_user.id) + project.send(key).assigned_to(current_user) end def project diff --git a/lib/api/entities.rb b/lib/api/entities.rb index d96e7f2770f..928706dfda7 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -248,8 +248,21 @@ module API end class GroupDetail < Group - expose :projects, using: Entities::Project - expose :shared_projects, using: Entities::Project + expose :projects, using: Entities::Project do |group, options| + GroupProjectsFinder.new( + group: group, + current_user: options[:current_user], + options: { only_owned: true } + ).execute + end + + expose :shared_projects, using: Entities::Project do |group, options| + GroupProjectsFinder.new( + group: group, + current_user: options[:current_user], + options: { only_shared: true } + ).execute + end end class Commit < Grape::Entity diff --git a/lib/api/issues.rb b/lib/api/issues.rb index e60e00d7956..5f943ba27d1 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -161,6 +161,8 @@ module API use :issue_params end post ':id/issues' do + authorize! :create_issue, user_project + # Setting created_at time only allowed for admins and project owners unless current_user.admin? || user_project.owner == current_user params.delete(:created_at) diff --git a/lib/gitlab/git/storage/checker.rb b/lib/gitlab/git/storage/checker.rb index de63cb4b40c..d3c37f82101 100644 --- a/lib/gitlab/git/storage/checker.rb +++ b/lib/gitlab/git/storage/checker.rb @@ -5,6 +5,8 @@ module Gitlab include CircuitBreakerSettings attr_reader :storage_path, :storage, :hostname, :logger + METRICS_MUTEX = Mutex.new + STORAGE_TIMING_BUCKETS = [0.1, 0.15, 0.25, 0.33, 0.5, 1, 1.5, 2.5, 5, 10, 15].freeze def self.check_all(logger = Rails.logger) threads = Gitlab.config.repositories.storages.keys.map do |storage_name| @@ -19,6 +21,17 @@ module Gitlab end end + def self.check_histogram + @check_histogram ||= + METRICS_MUTEX.synchronize do + @check_histogram || Gitlab::Metrics.histogram(:circuitbreaker_storage_check_duration_seconds, + 'Storage check time in seconds', + {}, + STORAGE_TIMING_BUCKETS + ) + end + end + def initialize(storage, logger = Rails.logger) @storage = storage config = Gitlab.config.repositories.storages[@storage] @@ -45,7 +58,7 @@ module Gitlab end def check - if Gitlab::Git::Storage::ForkedStorageCheck.storage_available?(storage_path, storage_timeout, access_retries) + if perform_access_check track_storage_accessible true else @@ -57,6 +70,15 @@ module Gitlab private + def perform_access_check + start_time = Gitlab::Metrics::System.monotonic_time + + Gitlab::Git::Storage::ForkedStorageCheck.storage_available?(storage_path, storage_timeout, access_retries) + ensure + execution_time = Gitlab::Metrics::System.monotonic_time - start_time + self.class.check_histogram.observe({ storage: storage }, execution_time) + end + def track_storage_inaccessible first_failure = current_failure_info.first_failure || Time.now last_failure = Time.now diff --git a/lib/gitlab/metrics/samplers/influx_sampler.rb b/lib/gitlab/metrics/samplers/influx_sampler.rb index f4f9b5ca792..5a0f7f28fc8 100644 --- a/lib/gitlab/metrics/samplers/influx_sampler.rb +++ b/lib/gitlab/metrics/samplers/influx_sampler.rb @@ -27,7 +27,6 @@ module Gitlab def sample sample_memory_usage sample_file_descriptors - sample_objects sample_gc flush @@ -48,29 +47,6 @@ module Gitlab add_metric('file_descriptors', value: System.file_descriptor_count) end - if Metrics.mri? - def sample_objects - sample = Allocations.to_hash - counts = sample.each_with_object({}) do |(klass, count), hash| - name = klass.name - - next unless name - - hash[name] = count - end - - # Symbols aren't allocated so we'll need to add those manually. - counts['Symbol'] = Symbol.all_symbols.length - - counts.each do |name, count| - add_metric('object_counts', { count: count }, type: name) - end - end - else - def sample_objects - end - end - def sample_gc time = GC::Profiler.total_time * 1000.0 stats = GC.stat.merge(total_time: time) diff --git a/lib/gitlab/metrics/samplers/ruby_sampler.rb b/lib/gitlab/metrics/samplers/ruby_sampler.rb index f4901be9581..b68800417a2 100644 --- a/lib/gitlab/metrics/samplers/ruby_sampler.rb +++ b/lib/gitlab/metrics/samplers/ruby_sampler.rb @@ -48,7 +48,6 @@ module Gitlab def sample start_time = System.monotonic_time sample_gc - sample_objects metrics[:memory_usage].set(labels, System.memory_usage) metrics[:file_descriptors].set(labels, System.file_descriptor_count) @@ -68,32 +67,6 @@ module Gitlab end end - def sample_objects - list_objects.each do |name, count| - metrics[:objects_total].set(labels.merge(class: name), count) - end - end - - if Metrics.mri? - def list_objects - sample = Allocations.to_hash - counts = sample.each_with_object({}) do |(klass, count), hash| - name = klass.name - - next unless name - - hash[name] = count - end - - # Symbols aren't allocated so we'll need to add those manually. - counts['Symbol'] = Symbol.all_symbols.length - counts - end - else - def list_objects - end - end - def worker_label return {} unless defined?(Unicorn::Worker) diff --git a/rubocop/cop/migration/remove_column.rb b/rubocop/cop/migration/remove_column.rb new file mode 100644 index 00000000000..e53eb2e07b2 --- /dev/null +++ b/rubocop/cop/migration/remove_column.rb @@ -0,0 +1,30 @@ +require_relative '../../migration_helpers' + +module RuboCop + module Cop + module Migration + # Cop that checks if remove_column is used in a regular (not + # post-deployment) migration. + class RemoveColumn < RuboCop::Cop::Cop + include MigrationHelpers + + MSG = '`remove_column` must only be used in post-deployment migrations'.freeze + + def on_def(node) + def_method = node.children[0] + + return unless in_migration?(node) && !in_post_deployment_migration?(node) + return unless def_method == :change || def_method == :up + + node.each_descendant(:send) do |send_node| + send_method = send_node.children[1] + + if send_method == :remove_column + add_offense(send_node, :selector) + end + end + end + end + end + end +end diff --git a/rubocop/migration_helpers.rb b/rubocop/migration_helpers.rb index c3473771178..c066d424437 100644 --- a/rubocop/migration_helpers.rb +++ b/rubocop/migration_helpers.rb @@ -7,5 +7,11 @@ module RuboCop dirname.end_with?('db/migrate', 'db/post_migrate') end + + def in_post_deployment_migration?(node) + dirname = File.dirname(node.location.expression.source_buffer.name) + + dirname.end_with?('db/post_migrate') + end end end diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb index 7621ea50da9..eb52be3d731 100644 --- a/rubocop/rubocop.rb +++ b/rubocop/rubocop.rb @@ -14,6 +14,7 @@ require_relative 'cop/migration/add_index' require_relative 'cop/migration/add_timestamps' require_relative 'cop/migration/datetime' require_relative 'cop/migration/hash_index' +require_relative 'cop/migration/remove_column' require_relative 'cop/migration/remove_concurrent_index' require_relative 'cop/migration/remove_index' require_relative 'cop/migration/reversible_add_column_with_default' diff --git a/scripts/trigger-build-omnibus b/scripts/trigger-build-omnibus index 3c5c22c9372..4ff0e8e10b7 100755 --- a/scripts/trigger-build-omnibus +++ b/scripts/trigger-build-omnibus @@ -66,7 +66,7 @@ module Omnibus raise 'Pipeline timeout!' if timeout? case status - when :pending, :running + when :created, :pending, :running puts "Waiting another #{INTERVAL} seconds ..." sleep INTERVAL when :success diff --git a/spec/controllers/groups/group_members_controller_spec.rb b/spec/controllers/groups/group_members_controller_spec.rb index 9c6d584f59b..362d5cc4514 100644 --- a/spec/controllers/groups/group_members_controller_spec.rb +++ b/spec/controllers/groups/group_members_controller_spec.rb @@ -62,6 +62,25 @@ describe Groups::GroupMembersController do end end + describe 'PUT update' do + let(:requester) { create(:group_member, :access_request, group: group) } + + before do + group.add_owner(user) + sign_in(user) + end + + Gitlab::Access.options.each do |label, value| + it "can change the access level to #{label}" do + xhr :put, :update, group_member: { access_level: value }, + group_id: group, + id: requester + + expect(requester.reload.human_access).to eq(label) + end + end + end + describe 'DELETE destroy' do let(:member) { create(:group_member, :developer, group: group) } diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb index a34dc27a5ed..290dba0610a 100644 --- a/spec/controllers/projects/project_members_controller_spec.rb +++ b/spec/controllers/projects/project_members_controller_spec.rb @@ -66,6 +66,26 @@ describe Projects::ProjectMembersController do end end + describe 'PUT update' do + let(:requester) { create(:project_member, :access_request, project: project) } + + before do + project.add_master(user) + sign_in(user) + end + + Gitlab::Access.options.each do |label, value| + it "can change the access level to #{label}" do + xhr :put, :update, project_member: { access_level: value }, + namespace_id: project.namespace, + project_id: project, + id: requester + + expect(requester.reload.human_access).to eq(label) + end + end + end + describe 'DELETE destroy' do let(:member) { create(:project_member, :developer, project: project) } diff --git a/spec/factories/users.rb b/spec/factories/users.rb index 4000cd085b7..8ace424f8af 100644 --- a/spec/factories/users.rb +++ b/spec/factories/users.rb @@ -58,6 +58,10 @@ FactoryGirl.define do end end + trait :readme do + project_view :readme + end + factory :omniauth_user do transient do extern_uid '123456' diff --git a/spec/features/groups/members/manage_members.rb b/spec/features/groups/members/manage_members.rb index da1e17225db..21f7b4999ad 100644 --- a/spec/features/groups/members/manage_members.rb +++ b/spec/features/groups/members/manage_members.rb @@ -38,6 +38,27 @@ feature 'Groups > Members > Manage members' do end end + scenario 'do not disclose email addresses', :js do + group.add_owner(user1) + create(:user, email: 'undisclosed_email@gitlab.com', name: "Jane 'invisible' Doe") + + visit group_group_members_path(group) + + find('.select2-container').click + select_input = find('.select2-input') + + select_input.send_keys('@gitlab.com') + wait_for_requests + + expect(page).to have_content('No matches found') + + select_input.native.clear + select_input.send_keys('undisclosed_email@gitlab.com') + wait_for_requests + + expect(page).to have_content("Jane 'invisible' Doe") + end + scenario 'remove user from group', :js do group.add_owner(user1) group.add_developer(user2) diff --git a/spec/helpers/preferences_helper_spec.rb b/spec/helpers/preferences_helper_spec.rb index 8b8080563d3..749aa25e632 100644 --- a/spec/helpers/preferences_helper_spec.rb +++ b/spec/helpers/preferences_helper_spec.rb @@ -77,15 +77,6 @@ describe PreferencesHelper do end end - def stub_user(messages = {}) - if messages.empty? - allow(helper).to receive(:current_user).and_return(nil) - else - allow(helper).to receive(:current_user) - .and_return(double('user', messages)) - end - end - describe '#default_project_view' do context 'user not signed in' do before do @@ -125,5 +116,70 @@ describe PreferencesHelper do end end end + + context 'user signed in' do + let(:user) { create(:user, :readme) } + let(:project) { create(:project, :public, :repository) } + + before do + helper.instance_variable_set(:@project, project) + allow(helper).to receive(:current_user).and_return(user) + end + + context 'when the user is allowed to see the code' do + it 'returns the project view' do + allow(helper).to receive(:can?).with(user, :download_code, project).and_return(true) + + expect(helper.default_project_view).to eq('readme') + end + end + + context 'with wikis enabled and the right policy for the user' do + before do + project.project_feature.update_attribute(:issues_access_level, 0) + allow(helper).to receive(:can?).with(user, :download_code, project).and_return(false) + end + + it 'returns wiki if the user has the right policy' do + allow(helper).to receive(:can?).with(user, :read_wiki, project).and_return(true) + + expect(helper.default_project_view).to eq('wiki') + end + + it 'returns customize_workflow if the user does not have the right policy' do + allow(helper).to receive(:can?).with(user, :read_wiki, project).and_return(false) + + expect(helper.default_project_view).to eq('customize_workflow') + end + end + + context 'with issues as a feature available' do + it 'return issues' do + allow(helper).to receive(:can?).with(user, :download_code, project).and_return(false) + allow(helper).to receive(:can?).with(user, :read_wiki, project).and_return(false) + + expect(helper.default_project_view).to eq('projects/issues/issues') + end + end + + context 'with no activity, no wikies and no issues' do + it 'returns customize_workflow as default' do + project.project_feature.update_attribute(:issues_access_level, 0) + allow(helper).to receive(:can?).with(user, :download_code, project).and_return(false) + allow(helper).to receive(:can?).with(user, :read_wiki, project).and_return(false) + + expect(helper.default_project_view).to eq('customize_workflow') + end + end + end + end + + def stub_user(messages = {}) + if messages.empty? + allow(helper).to receive(:current_user).and_return(nil) + else + allow(helper).to receive(:current_user) + .and_return(double('user', messages)) + end end end diff --git a/spec/javascripts/fly_out_nav_spec.js b/spec/javascripts/fly_out_nav_spec.js index 4f20e31f511..a3fa07d5bc2 100644 --- a/spec/javascripts/fly_out_nav_spec.js +++ b/spec/javascripts/fly_out_nav_spec.js @@ -253,7 +253,7 @@ describe('Fly out sidebar navigation', () => { it('shows collapsed only sub-items if icon only sidebar', () => { const subItems = el.querySelector('.sidebar-sub-level-items'); const sidebar = document.createElement('div'); - sidebar.classList.add('sidebar-icons-only'); + sidebar.classList.add('sidebar-collapsed-desktop'); subItems.classList.add('is-fly-out-only'); setSidebar(sidebar); @@ -343,7 +343,7 @@ describe('Fly out sidebar navigation', () => { it('returns true when active & collapsed sidebar', () => { const sidebar = document.createElement('div'); - sidebar.classList.add('sidebar-icons-only'); + sidebar.classList.add('sidebar-collapsed-desktop'); el.classList.add('active'); setSidebar(sidebar); diff --git a/spec/javascripts/notes/components/issue_note_spec.js b/spec/javascripts/notes/components/issue_note_spec.js index 73fd188dbe5..bd888b2cbae 100644 --- a/spec/javascripts/notes/components/issue_note_spec.js +++ b/spec/javascripts/notes/components/issue_note_spec.js @@ -41,4 +41,19 @@ describe('issue_note', () => { it('should render issue body', () => { expect(vm.$el.querySelector('.note-text').innerHTML).toEqual(note.note_html); }); + + it('prevents note preview xss', (done) => { + const imgSrc = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'; + const noteBody = `<img src="${imgSrc}" onload="alert(1)" />`; + const alertSpy = spyOn(window, 'alert'); + vm.updateNote = () => new Promise($.noop); + + vm.formUpdateHandler(noteBody, null, $.noop); + + setTimeout(() => { + expect(alertSpy).not.toHaveBeenCalled(); + expect(vm.note.note_html).toEqual(_.escape(noteBody)); + done(); + }, 0); + }); }); diff --git a/spec/lib/gitlab/metrics/samplers/influx_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/influx_sampler_spec.rb index 667e4747897..f66451c5188 100644 --- a/spec/lib/gitlab/metrics/samplers/influx_sampler_spec.rb +++ b/spec/lib/gitlab/metrics/samplers/influx_sampler_spec.rb @@ -21,7 +21,6 @@ describe Gitlab::Metrics::Samplers::InfluxSampler do it 'samples various statistics' do expect(sampler).to receive(:sample_memory_usage) expect(sampler).to receive(:sample_file_descriptors) - expect(sampler).to receive(:sample_objects) expect(sampler).to receive(:sample_gc) expect(sampler).to receive(:flush) @@ -72,28 +71,6 @@ describe Gitlab::Metrics::Samplers::InfluxSampler do end end - if Gitlab::Metrics.mri? - describe '#sample_objects' do - it 'adds a metric containing the amount of allocated objects' do - expect(sampler).to receive(:add_metric) - .with(/object_counts/, an_instance_of(Hash), an_instance_of(Hash)) - .at_least(:once) - .and_call_original - - sampler.sample_objects - end - - it 'ignores classes without a name' do - expect(Allocations).to receive(:to_hash).and_return({ Class.new => 4 }) - - expect(sampler).not_to receive(:add_metric) - .with('object_counts', an_instance_of(Hash), type: nil) - - sampler.sample_objects - end - end - end - describe '#sample_gc' do it 'adds a metric containing garbage collection statistics' do expect(GC::Profiler).to receive(:total_time).and_return(0.24) diff --git a/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb index 53699327da1..375cbf8a9ca 100644 --- a/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb +++ b/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb @@ -11,7 +11,6 @@ describe Gitlab::Metrics::Samplers::RubySampler do it 'samples various statistics' do expect(Gitlab::Metrics::System).to receive(:memory_usage) expect(Gitlab::Metrics::System).to receive(:file_descriptor_count) - expect(sampler).to receive(:sample_objects) expect(sampler).to receive(:sample_gc) sampler.sample @@ -65,26 +64,4 @@ describe Gitlab::Metrics::Samplers::RubySampler do sampler.sample end end - - if Gitlab::Metrics.mri? - describe '#sample_objects' do - it 'adds a metric containing the amount of allocated objects' do - expect(sampler.metrics[:objects_total]).to receive(:set) - .with(include(class: anything), be > 0) - .at_least(:once) - .and_call_original - - sampler.sample - end - - it 'ignores classes without a name' do - expect(Allocations).to receive(:to_hash).and_return({ Class.new => 4 }) - - expect(sampler.metrics[:objects_total]).not_to receive(:set) - .with(include(class: 'object_counts'), anything) - - sampler.sample - end - end - end end diff --git a/spec/migrations/remove_assignee_id_from_issue_spec.rb b/spec/migrations/remove_assignee_id_from_issue_spec.rb new file mode 100644 index 00000000000..2c6f992d3ae --- /dev/null +++ b/spec/migrations/remove_assignee_id_from_issue_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20170523073948_remove_assignee_id_from_issue.rb') + +describe RemoveAssigneeIdFromIssue, :migration do + let(:issues) { table(:issues) } + let(:issue_assignees) { table(:issue_assignees) } + let(:users) { table(:users) } + + let!(:user_1) { users.create(email: 'email1@example.com') } + let!(:user_2) { users.create(email: 'email2@example.com') } + let!(:user_3) { users.create(email: 'email3@example.com') } + + def create_issue(assignees:) + issues.create.tap do |issue| + assignees.each do |assignee| + issue_assignees.create(issue_id: issue.id, user_id: assignee.id) + end + end + end + + let!(:issue_single_assignee) { create_issue(assignees: [user_1]) } + let!(:issue_no_assignee) { create_issue(assignees: []) } + let!(:issue_multiple_assignees) { create_issue(assignees: [user_2, user_3]) } + + describe '#down' do + it 'sets the assignee_id to a random matching assignee from the assignees table' do + migrate! + disable_migrations_output { described_class.new.down } + + expect(issue_single_assignee.reload.assignee_id).to eq(user_1.id) + expect(issue_no_assignee.reload.assignee_id).to be_nil + expect(issue_multiple_assignees.reload.assignee_id).to eq(user_2.id).or(user_3.id) + + disable_migrations_output { described_class.new.up } + end + end +end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index cdabd35b6ba..4687d9dfa00 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -913,11 +913,11 @@ describe User do describe 'email matching' do it 'returns users with a matching Email' do - expect(described_class.search(user.email)).to eq([user, user2]) + expect(described_class.search(user.email)).to eq([user]) end - it 'returns users with a partially matching Email' do - expect(described_class.search(user.email[0..2])).to eq([user, user2]) + it 'does not return users with a partially matching Email' do + expect(described_class.search(user.email[0..2])).not_to include(user, user2) end it 'returns users with a matching Email regardless of the casing' do @@ -973,8 +973,8 @@ describe User do expect(search_with_secondary_emails(user.email)).to eq([user]) end - it 'returns users with a partially matching email' do - expect(search_with_secondary_emails(user.email[0..2])).to eq([user]) + it 'does not return users with a partially matching email' do + expect(search_with_secondary_emails(user.email[0..2])).not_to include([user]) end it 'returns users with a matching email regardless of the casing' do @@ -997,29 +997,8 @@ describe User do expect(search_with_secondary_emails(email.email)).to eq([email.user]) end - it 'returns users with a matching part of secondary email' do - expect(search_with_secondary_emails(email.email[1..4])).to eq([email.user]) - end - - it 'return users with a matching part of secondary email regardless of case' do - expect(search_with_secondary_emails(email.email[1..4].upcase)).to eq([email.user]) - expect(search_with_secondary_emails(email.email[1..4].downcase)).to eq([email.user]) - expect(search_with_secondary_emails(email.email[1..4].capitalize)).to eq([email.user]) - end - - it 'returns multiple users with matching secondary emails' do - email1 = create(:email, email: '1_testemail@example.com') - email2 = create(:email, email: '2_testemail@example.com') - email3 = create(:email, email: 'other@email.com') - email3.user.update_attributes!(email: 'another@mail.com') - - expect( - search_with_secondary_emails('testemail@example.com').map(&:id) - ).to include(email1.user.id, email2.user.id) - - expect( - search_with_secondary_emails('testemail@example.com').map(&:id) - ).not_to include(email3.user.id) + it 'does not return users with a matching part of secondary email' do + expect(search_with_secondary_emails(email.email[1..4])).not_to include([email.user]) end end diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index 554723d6b1e..6330c140246 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -173,6 +173,28 @@ describe API::Groups do end describe "GET /groups/:id" do + # Given a group, create one project for each visibility level + # + # group - Group to add projects to + # share_with - If provided, each project will be shared with this Group + # + # Returns a Hash of visibility_level => Project pairs + def add_projects_to_group(group, share_with: nil) + projects = { + public: create(:project, :public, namespace: group), + internal: create(:project, :internal, namespace: group), + private: create(:project, :private, namespace: group) + } + + if share_with + create(:project_group_link, project: projects[:public], group: share_with) + create(:project_group_link, project: projects[:internal], group: share_with) + create(:project_group_link, project: projects[:private], group: share_with) + end + + projects + end + context 'when unauthenticated' do it 'returns 404 for a private group' do get api("/groups/#{group2.id}") @@ -183,6 +205,26 @@ describe API::Groups do get api("/groups/#{group1.id}") expect(response).to have_gitlab_http_status(200) end + + it 'returns only public projects in the group' do + public_group = create(:group, :public) + projects = add_projects_to_group(public_group) + + get api("/groups/#{public_group.id}") + + expect(json_response['projects'].map { |p| p['id'].to_i }) + .to contain_exactly(projects[:public].id) + end + + it 'returns only public projects shared with the group' do + public_group = create(:group, :public) + projects = add_projects_to_group(public_group, share_with: group1) + + get api("/groups/#{group1.id}") + + expect(json_response['shared_projects'].map { |p| p['id'].to_i }) + .to contain_exactly(projects[:public].id) + end end context "when authenticated as user" do @@ -222,6 +264,26 @@ describe API::Groups do expect(response).to have_gitlab_http_status(404) end + + it 'returns only public and internal projects in the group' do + public_group = create(:group, :public) + projects = add_projects_to_group(public_group) + + get api("/groups/#{public_group.id}", user2) + + expect(json_response['projects'].map { |p| p['id'].to_i }) + .to contain_exactly(projects[:public].id, projects[:internal].id) + end + + it 'returns only public and internal projects shared with the group' do + public_group = create(:group, :public) + projects = add_projects_to_group(public_group, share_with: group1) + + get api("/groups/#{group1.id}", user2) + + expect(json_response['shared_projects'].map { |p| p['id'].to_i }) + .to contain_exactly(projects[:public].id, projects[:internal].id) + end end context "when authenticated as admin" do diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 99525cd0a6a..3f5070a1fd2 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -860,6 +860,20 @@ describe API::Issues, :mailer do end end + context 'user does not have permissions to create issue' do + let(:not_member) { create(:user) } + + before do + project.project_feature.update(issues_access_level: ProjectFeature::PRIVATE) + end + + it 'renders 403' do + post api("/projects/#{project.id}/issues", not_member), title: 'new issue' + + expect(response).to have_gitlab_http_status(403) + end + end + it 'creates a new project issue' do post api("/projects/#{project.id}/issues", user), title: 'new issue', labels: 'label, label2', weight: 3, diff --git a/spec/rubocop/cop/migration/remove_column_spec.rb b/spec/rubocop/cop/migration/remove_column_spec.rb new file mode 100644 index 00000000000..89112f01723 --- /dev/null +++ b/spec/rubocop/cop/migration/remove_column_spec.rb @@ -0,0 +1,68 @@ +require 'spec_helper' + +require 'rubocop' +require 'rubocop/rspec/support' + +require_relative '../../../../rubocop/cop/migration/remove_column' + +describe RuboCop::Cop::Migration::RemoveColumn do + include CopHelper + + subject(:cop) { described_class.new } + + def source(meth = 'change') + "def #{meth}; remove_column :table, :column; end" + end + + context 'in a regular migration' do + before do + allow(cop).to receive(:in_migration?).and_return(true) + allow(cop).to receive(:in_post_deployment_migration?).and_return(false) + end + + it 'registers an offense when remove_column is used in the change method' do + inspect_source(cop, source('change')) + + aggregate_failures do + expect(cop.offenses.size).to eq(1) + expect(cop.offenses.map(&:line)).to eq([1]) + end + end + + it 'registers an offense when remove_column is used in the up method' do + inspect_source(cop, source('up')) + + aggregate_failures do + expect(cop.offenses.size).to eq(1) + expect(cop.offenses.map(&:line)).to eq([1]) + end + end + + it 'registers no offense when remove_column is used in the down method' do + inspect_source(cop, source('down')) + + expect(cop.offenses.size).to eq(0) + end + end + + context 'in a post-deployment migration' do + before do + allow(cop).to receive(:in_migration?).and_return(true) + allow(cop).to receive(:in_post_deployment_migration?).and_return(true) + end + + it 'registers no offense' do + inspect_source(cop, source) + + expect(cop.offenses.size).to eq(0) + end + end + + context 'outside of a migration' do + it 'registers no offense' do + inspect_source(cop, source) + + expect(cop.offenses.size).to eq(0) + end + end +end diff --git a/spec/services/members/authorized_destroy_service_spec.rb b/spec/services/members/authorized_destroy_service_spec.rb index 2d04d824180..d4ef31c0c74 100644 --- a/spec/services/members/authorized_destroy_service_spec.rb +++ b/spec/services/members/authorized_destroy_service_spec.rb @@ -45,7 +45,7 @@ describe Members::AuthorizedDestroyService do expect { described_class.new(member, member_user).execute } .to change { number_of_assigned_issuables(member_user) }.from(4).to(2) - expect(issue.reload.assignee_id).to be_nil + expect(issue.reload.assignee_ids).to be_empty expect(merge_request.reload.assignee_id).to be_nil end end diff --git a/spec/services/users/keys_count_service_spec.rb b/spec/services/users/keys_count_service_spec.rb index a188cf86772..bee8380e8b7 100644 --- a/spec/services/users/keys_count_service_spec.rb +++ b/spec/services/users/keys_count_service_spec.rb @@ -15,14 +15,12 @@ describe Users::KeysCountService, :use_clean_rails_memory_store_caching do expect(service.count).to eq(1) end - it 'caches the number of keys in Redis' do + it 'caches the number of keys in Redis', :request_store do + service.delete_cache + control_count = ActiveRecord::QueryRecorder.new { service.count }.count service.delete_cache - recorder = ActiveRecord::QueryRecorder.new do - 2.times { service.count } - end - - expect(recorder.count).to eq(1) + expect { 2.times { service.count } }.not_to exceed_query_limit(control_count) end end |