Commit 5c79256b authored by Matija Čupić's avatar Matija Čupić

Merge branch 'master' into 33697-pipelines-json-endpoint

parents a6866fbc c7c9f38d
...@@ -2,6 +2,14 @@ ...@@ -2,6 +2,14 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. entry.
## 10.7.2 (2018-04-25)
### Security (2 changes)
- Serve archive requests with the correct file in all cases.
- Sanitizes user name to avoid XSS attacks.
## 10.7.1 (2018-04-23) ## 10.7.1 (2018-04-23)
### Fixed (11 changes) ### Fixed (11 changes)
...@@ -237,6 +245,13 @@ entry. ...@@ -237,6 +245,13 @@ entry.
- Upgrade Gitaly to upgrade its charlock_holmes. - Upgrade Gitaly to upgrade its charlock_holmes.
## 10.6.5 (2018-04-24)
### Security (1 change)
- Sanitizes user name to avoid XSS attacks.
## 10.6.4 (2018-04-09) ## 10.6.4 (2018-04-09)
### Fixed (8 changes, 1 of them is from the community) ### Fixed (8 changes, 1 of them is from the community)
...@@ -478,6 +493,13 @@ entry. ...@@ -478,6 +493,13 @@ entry.
- Use host URL to build JIRA remote link icon. - Use host URL to build JIRA remote link icon.
## 10.5.8 (2018-04-24)
### Security (1 change)
- Sanitizes user name to avoid XSS attacks.
## 10.5.7 (2018-04-03) ## 10.5.7 (2018-04-03)
### Security (2 changes) ### Security (2 changes)
......
...@@ -184,6 +184,9 @@ gem 're2', '~> 1.1.1' ...@@ -184,6 +184,9 @@ gem 're2', '~> 1.1.1'
gem 'version_sorter', '~> 2.1.0' gem 'version_sorter', '~> 2.1.0'
# User agent parsing
gem 'device_detector'
# Cache # Cache
gem 'redis-rails', '~> 5.0.2' gem 'redis-rails', '~> 5.0.2'
......
...@@ -161,6 +161,7 @@ GEM ...@@ -161,6 +161,7 @@ GEM
activerecord (>= 3.2.0, < 5.1) activerecord (>= 3.2.0, < 5.1)
descendants_tracker (0.0.4) descendants_tracker (0.0.4)
thread_safe (~> 0.3, >= 0.3.1) thread_safe (~> 0.3, >= 0.3.1)
device_detector (1.0.0)
devise (4.2.0) devise (4.2.0)
bcrypt (~> 3.0) bcrypt (~> 3.0)
orm_adapter (~> 0.1) orm_adapter (~> 0.1)
...@@ -1026,6 +1027,7 @@ DEPENDENCIES ...@@ -1026,6 +1027,7 @@ DEPENDENCIES
database_cleaner (~> 1.5.0) database_cleaner (~> 1.5.0)
deckar01-task_list (= 2.0.0) deckar01-task_list (= 2.0.0)
default_value_for (~> 3.0.0) default_value_for (~> 3.0.0)
device_detector
devise (~> 4.2) devise (~> 4.2)
devise-two-factor (~> 3.0.0) devise-two-factor (~> 3.0.0)
diffy (~> 3.1.0) diffy (~> 3.1.0)
......
...@@ -304,12 +304,12 @@ GEM ...@@ -304,12 +304,12 @@ GEM
flowdock (~> 0.7) flowdock (~> 0.7)
gitlab-grit (>= 2.4.1) gitlab-grit (>= 2.4.1)
multi_json multi_json
gitlab-gollum-lib (4.2.7.1) gitlab-gollum-lib (4.2.7.2)
gemojione (~> 3.2) gemojione (~> 3.2)
github-markup (~> 1.6) github-markup (~> 1.6)
gollum-grit_adapter (~> 1.0) gollum-grit_adapter (~> 1.0)
nokogiri (>= 1.6.1, < 2.0) nokogiri (>= 1.6.1, < 2.0)
rouge (~> 2.1) rouge (~> 3.1)
sanitize (~> 2.1) sanitize (~> 2.1)
stringex (~> 2.6) stringex (~> 2.6)
gitlab-gollum-rugged_adapter (0.4.4) gitlab-gollum-rugged_adapter (0.4.4)
...@@ -602,8 +602,6 @@ GEM ...@@ -602,8 +602,6 @@ GEM
atomic (>= 1.0.0) atomic (>= 1.0.0)
mysql2 mysql2
peek peek
peek-performance_bar (1.3.1)
peek (>= 0.1.0)
peek-pg (1.3.0) peek-pg (1.3.0)
concurrent-ruby concurrent-ruby
concurrent-ruby-ext concurrent-ruby-ext
...@@ -752,7 +750,7 @@ GEM ...@@ -752,7 +750,7 @@ GEM
retriable (3.1.1) retriable (3.1.1)
rinku (2.0.4) rinku (2.0.4)
rotp (2.1.2) rotp (2.1.2)
rouge (2.2.1) rouge (3.1.1)
rqrcode (0.10.1) rqrcode (0.10.1)
chunky_png (~> 1.0) chunky_png (~> 1.0)
rqrcode-rails3 (0.1.7) rqrcode-rails3 (0.1.7)
...@@ -1134,7 +1132,6 @@ DEPENDENCIES ...@@ -1134,7 +1132,6 @@ DEPENDENCIES
peek (~> 1.0.1) peek (~> 1.0.1)
peek-gc (~> 0.0.2) peek-gc (~> 0.0.2)
peek-mysql2 (~> 1.1.0) peek-mysql2 (~> 1.1.0)
peek-performance_bar (~> 1.3.0)
peek-pg (~> 1.3.0) peek-pg (~> 1.3.0)
peek-rblineprof (~> 0.2.0) peek-rblineprof (~> 0.2.0)
peek-redis (~> 1.2.0) peek-redis (~> 1.2.0)
...@@ -1166,7 +1163,7 @@ DEPENDENCIES ...@@ -1166,7 +1163,7 @@ DEPENDENCIES
redis-rails (~> 5.0.2) redis-rails (~> 5.0.2)
request_store (~> 1.3) request_store (~> 1.3)
responders (~> 2.0) responders (~> 2.0)
rouge (~> 2.0) rouge (~> 3.1)
rqrcode-rails3 (~> 0.1.7) rqrcode-rails3 (~> 0.1.7)
rspec-parameterized rspec-parameterized
rspec-rails (~> 3.6.0) rspec-rails (~> 3.6.0)
......
...@@ -86,7 +86,7 @@ export default { ...@@ -86,7 +86,7 @@ export default {
v-html="resolveSvg" v-html="resolveSvg"
></span> ></span>
</span> </span>
<span class=".line-resolve-text"> <span class="line-resolve-text">
{{ resolvedDiscussionCount }}/{{ discussionCount }} {{ countText }} resolved {{ resolvedDiscussionCount }}/{{ discussionCount }} {{ countText }} resolved
</span> </span>
</div> </div>
......
export default {
name: 'time-tracking-no-tracking-pane',
template: `
<div class="time-tracking-no-tracking-pane">
<span class="no-value">
{{ __('No estimate or time spent') }}
</span>
</div>
`,
};
<script>
export default {
name: 'TimeTrackingNoTrackingPane',
};
</script>
<template>
<div class="time-tracking-no-tracking-pane">
<span class="no-value">
{{ __('No estimate or time spent') }}
</span>
</div>
</template>
<script>
import $ from 'jquery'; import $ from 'jquery';
import _ from 'underscore'; import _ from 'underscore';
...@@ -10,14 +11,17 @@ import Mediator from '../../sidebar_mediator'; ...@@ -10,14 +11,17 @@ import Mediator from '../../sidebar_mediator';
import eventHub from '../../event_hub'; import eventHub from '../../event_hub';
export default { export default {
components: {
IssuableTimeTracker,
},
data() { data() {
return { return {
mediator: new Mediator(), mediator: new Mediator(),
store: new Store(), store: new Store(),
}; };
}, },
components: { mounted() {
IssuableTimeTracker, this.listenForQuickActions();
}, },
methods: { methods: {
listenForQuickActions() { listenForQuickActions() {
...@@ -41,18 +45,17 @@ export default { ...@@ -41,18 +45,17 @@ export default {
} }
}, },
}, },
mounted() {
this.listenForQuickActions();
},
template: `
<div class="block">
<issuable-time-tracker
:time_estimate="store.timeEstimate"
:time_spent="store.totalTimeSpent"
:human_time_estimate="store.humanTimeEstimate"
:human_time_spent="store.humanTotalTimeSpent"
:rootPath="store.rootPath"
/>
</div>
`,
}; };
</script>
<template>
<div class="block">
<issuable-time-tracker
:time_estimate="store.timeEstimate"
:time_spent="store.totalTimeSpent"
:human_time_estimate="store.humanTimeEstimate"
:human_time_spent="store.humanTotalTimeSpent"
:root-path="store.rootPath"
/>
</div>
</template>
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
import TimeTrackingHelpState from './help_state.vue'; import TimeTrackingHelpState from './help_state.vue';
import TimeTrackingCollapsedState from './collapsed_state.vue'; import TimeTrackingCollapsedState from './collapsed_state.vue';
import timeTrackingSpentOnlyPane from './spent_only_pane'; import timeTrackingSpentOnlyPane from './spent_only_pane';
import timeTrackingNoTrackingPane from './no_tracking_pane'; import TimeTrackingNoTrackingPane from './no_tracking_pane.vue';
import TimeTrackingEstimateOnlyPane from './estimate_only_pane.vue'; import TimeTrackingEstimateOnlyPane from './estimate_only_pane.vue';
import TimeTrackingComparisonPane from './comparison_pane.vue'; import TimeTrackingComparisonPane from './comparison_pane.vue';
...@@ -14,7 +14,7 @@ export default { ...@@ -14,7 +14,7 @@ export default {
TimeTrackingCollapsedState, TimeTrackingCollapsedState,
TimeTrackingEstimateOnlyPane, TimeTrackingEstimateOnlyPane,
'time-tracking-spent-only-pane': timeTrackingSpentOnlyPane, 'time-tracking-spent-only-pane': timeTrackingSpentOnlyPane,
'time-tracking-no-tracking-pane': timeTrackingNoTrackingPane, TimeTrackingNoTrackingPane,
TimeTrackingComparisonPane, TimeTrackingComparisonPane,
TimeTrackingHelpState, TimeTrackingHelpState,
}, },
......
import $ from 'jquery'; import $ from 'jquery';
import Vue from 'vue'; import Vue from 'vue';
import SidebarTimeTracking from './components/time_tracking/sidebar_time_tracking'; import SidebarTimeTracking from './components/time_tracking/sidebar_time_tracking.vue';
import SidebarAssignees from './components/assignees/sidebar_assignees.vue'; import SidebarAssignees from './components/assignees/sidebar_assignees.vue';
import ConfidentialIssueSidebar from './components/confidential/confidential_issue_sidebar.vue'; import ConfidentialIssueSidebar from './components/confidential/confidential_issue_sidebar.vue';
import SidebarMoveIssue from './lib/sidebar_move_issue'; import SidebarMoveIssue from './lib/sidebar_move_issue';
......
<script>
import $ from 'jquery'; import $ from 'jquery';
import statusIcon from '../mr_widget_status_icon.vue'; import statusIcon from '../mr_widget_status_icon.vue';
import tooltip from '../../../vue_shared/directives/tooltip'; import tooltip from '../../../vue_shared/directives/tooltip';
import eventHub from '../../event_hub'; import eventHub from '../../event_hub';
export default { export default {
name: 'MRWidgetWIP', name: 'WorkInProgress',
props: { components: {
mr: { type: Object, required: true }, statusIcon,
service: { type: Object, required: true },
}, },
directives: { directives: {
tooltip, tooltip,
}, },
props: {
mr: { type: Object, required: true },
service: { type: Object, required: true },
},
data() { data() {
return { return {
isMakingRequest: false, isMakingRequest: false,
}; };
}, },
components: {
statusIcon,
},
methods: { methods: {
removeWIP() { removeWIP() {
this.isMakingRequest = true; this.isMakingRequest = true;
...@@ -36,32 +37,40 @@ export default { ...@@ -36,32 +37,40 @@ export default {
}); });
}, },
}, },
template: `
<div class="mr-widget-body media">
<status-icon status="warning" :show-disabled-button="Boolean(mr.removeWIPPath)" />
<div class="media-body space-children">
<span class="bold">
This is a Work in Progress
<i
v-tooltip
class="fa fa-question-circle"
title="When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
aria-label="When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged">
</i>
</span>
<button
v-if="mr.removeWIPPath"
@click="removeWIP"
:disabled="isMakingRequest"
type="button"
class="btn btn-default btn-xs js-remove-wip">
<i
v-if="isMakingRequest"
class="fa fa-spinner fa-spin"
aria-hidden="true" />
Resolve WIP status
</button>
</div>
</div>
`,
}; };
</script>
<template>
<div class="mr-widget-body media">
<status-icon
status="warning"
:show-disabled-button="Boolean(mr.removeWIPPath)"
/>
<div class="media-body space-children">
<span class="bold">
This is a Work in Progress
<i
v-tooltip
class="fa fa-question-circle"
title="When this merge request is ready,
remove the WIP: prefix from the title to allow it to be merged"
aria-label="When this merge request is ready,
remove the WIP: prefix from the title to allow it to be merged">
</i>
</span>
<button
v-if="mr.removeWIPPath"
@click="removeWIP"
:disabled="isMakingRequest"
type="button"
class="btn btn-default btn-xs js-remove-wip">
<i
v-if="isMakingRequest"
class="fa fa-spinner fa-spin"
aria-hidden="true">
</i>
Resolve WIP status
</button>
</div>
</div>
</template>
...@@ -21,7 +21,7 @@ export { default as MergedState } from './components/states/mr_widget_merged.vue ...@@ -21,7 +21,7 @@ export { default as MergedState } from './components/states/mr_widget_merged.vue
export { default as FailedToMerge } from './components/states/mr_widget_failed_to_merge.vue'; export { default as FailedToMerge } from './components/states/mr_widget_failed_to_merge.vue';
export { default as ClosedState } from './components/states/mr_widget_closed.vue'; export { default as ClosedState } from './components/states/mr_widget_closed.vue';
export { default as MergingState } from './components/states/mr_widget_merging.vue'; export { default as MergingState } from './components/states/mr_widget_merging.vue';
export { default as WipState } from './components/states/mr_widget_wip'; export { default as WorkInProgressState } from './components/states/work_in_progress.vue';
export { default as ArchivedState } from './components/states/mr_widget_archived.vue'; export { default as ArchivedState } from './components/states/mr_widget_archived.vue';
export { default as ConflictsState } from './components/states/mr_widget_conflicts.vue'; export { default as ConflictsState } from './components/states/mr_widget_conflicts.vue';
export { default as NothingToMergeState } from './components/states/nothing_to_merge.vue'; export { default as NothingToMergeState } from './components/states/nothing_to_merge.vue';
......
...@@ -12,7 +12,7 @@ import { ...@@ -12,7 +12,7 @@ import {
ClosedState, ClosedState,
MergingState, MergingState,
RebaseState, RebaseState,
WipState, WorkInProgressState,
ArchivedState, ArchivedState,
ConflictsState, ConflictsState,
NothingToMergeState, NothingToMergeState,
...@@ -220,7 +220,7 @@ export default { ...@@ -220,7 +220,7 @@ export default {
'mr-widget-closed': ClosedState, 'mr-widget-closed': ClosedState,
'mr-widget-merging': MergingState, 'mr-widget-merging': MergingState,
'mr-widget-failed-to-merge': FailedToMerge, 'mr-widget-failed-to-merge': FailedToMerge,
'mr-widget-wip': WipState, 'mr-widget-wip': WorkInProgressState,
'mr-widget-archived': ArchivedState, 'mr-widget-archived': ArchivedState,
'mr-widget-conflicts': ConflictsState, 'mr-widget-conflicts': ConflictsState,
'mr-widget-nothing-to-merge': NothingToMergeState, 'mr-widget-nothing-to-merge': NothingToMergeState,
......
...@@ -17,7 +17,7 @@ export default { ...@@ -17,7 +17,7 @@ export default {
}, },
computed: { computed: {
/** /**
* This method is based on app/helpers/application_helper.rb#project_identicon * This method is based on app/helpers/avatars_helper.rb#project_identicon
*/ */
identiconStyles() { identiconStyles() {
const allowedColors = [ const allowedColors = [
......
...@@ -46,7 +46,7 @@ ...@@ -46,7 +46,7 @@
} }
&.middle-block { &.middle-block {
margin-top: 0; margin-top: $gl-padding-24;
margin-bottom: 0; margin-bottom: 0;
} }
...@@ -61,7 +61,7 @@ ...@@ -61,7 +61,7 @@
} }
&.footer-block { &.footer-block {
margin-top: 0; margin-top: $gl-padding-24;
border-bottom: 0; border-bottom: 0;
margin-bottom: -$gl-padding; margin-bottom: -$gl-padding;
} }
......
...@@ -452,6 +452,7 @@ img.emoji { ...@@ -452,6 +452,7 @@ img.emoji {
/** COMMON CLASSES **/ /** COMMON CLASSES **/
.prepend-top-0 { margin-top: 0; } .prepend-top-0 { margin-top: 0; }
.prepend-top-2 { margin-top: 2px; }
.prepend-top-5 { margin-top: 5px; } .prepend-top-5 { margin-top: 5px; }
.prepend-top-8 { margin-top: $grid-size; } .prepend-top-8 { margin-top: $grid-size; }
.prepend-top-10 { margin-top: 10px; } .prepend-top-10 { margin-top: 10px; }
......
...@@ -39,35 +39,10 @@ ...@@ -39,35 +39,10 @@
svg { svg {
fill: currentColor; fill: currentColor;
&.s8 { $svg-sizes: 8 12 16 18 24 32 48 72;
@include svg-size(8px); @each $svg-size in $svg-sizes {
} &.s#{$svg-size} {
@include svg-size(#{$svg-size}px);
&.s12 { }
@include svg-size(12px);
}
&.s16 {
@include svg-size(16px);
}
&.s18 {
@include svg-size(18px);
}
&.s24 {
@include svg-size(24px);
}
&.s32 {
@include svg-size(32px);
}
&.s48 {
@include svg-size(48px);
}
&.s72 {
@include svg-size(72px);
} }
} }
...@@ -107,6 +107,16 @@ ...@@ -107,6 +107,16 @@
padding-top: 10px; padding-top: 10px;
} }
.referenced-commands {
background: $blue-50;
padding: $gl-padding-8 $gl-padding;
border-radius: $border-radius-default;
p {
margin: 0;
}
}
.md-preview-holder { .md-preview-holder {
min-height: 167px; min-height: 167px;
padding: 10px 0; padding: 10px 0;
......
...@@ -212,6 +212,7 @@ $tooltip-font-size: 12px; ...@@ -212,6 +212,7 @@ $tooltip-font-size: 12px;
/* /*
* Padding * Padding
*/ */
$gl-padding-24: 24px;
$gl-padding: 16px; $gl-padding: 16px;
$gl-padding-8: 8px; $gl-padding-8: 8px;
$gl-padding-4: 4px; $gl-padding-4: 4px;
......
...@@ -772,7 +772,3 @@ ul.notes { ...@@ -772,7 +772,3 @@ ul.notes {
height: auto; height: auto;
} }
} }
.line-resolve-text {
vertical-align: middle;
}
...@@ -110,7 +110,8 @@ class ApplicationController < ActionController::Base ...@@ -110,7 +110,8 @@ class ApplicationController < ActionController::Base
def log_exception(exception) def log_exception(exception)
Raven.capture_exception(exception) if sentry_enabled? Raven.capture_exception(exception) if sentry_enabled?
application_trace = ActionDispatch::ExceptionWrapper.new(env, exception).application_trace backtrace_cleaner = Gitlab.rails5? ? env["action_dispatch.backtrace_cleaner"] : env
application_trace = ActionDispatch::ExceptionWrapper.new(backtrace_cleaner, exception).application_trace
application_trace.map! { |t| " #{t}\n" } application_trace.map! { |t| " #{t}\n" }
logger.error "\n#{exception.class.name} (#{exception.message}):\n#{application_trace.join}" logger.error "\n#{exception.class.name} (#{exception.message}):\n#{application_trace.join}"
end end
......
...@@ -57,7 +57,7 @@ module IssuableCollections ...@@ -57,7 +57,7 @@ module IssuableCollections
out_of_range = @issuables.current_page > total_pages # rubocop:disable Gitlab/ModuleWithInstanceVariables out_of_range = @issuables.current_page > total_pages # rubocop:disable Gitlab/ModuleWithInstanceVariables
if out_of_range if out_of_range
redirect_to(url_for(params.merge(page: total_pages, only_path: true))) redirect_to(url_for(safe_params.merge(page: total_pages, only_path: true)))
end end
out_of_range out_of_range
......
...@@ -33,6 +33,6 @@ class Groups::ApplicationController < ApplicationController ...@@ -33,6 +33,6 @@ class Groups::ApplicationController < ApplicationController
def build_canonical_path(group) def build_canonical_path(group)
params[:group_id] = group.to_param params[:group_id] = group.to_param
url_for(params) url_for(safe_params)
end end
end end
...@@ -8,8 +8,8 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -8,8 +8,8 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
omniauth_flow(Gitlab::Auth::OAuth) omniauth_flow(Gitlab::Auth::OAuth)
end end
Gitlab.config.omniauth.providers.each do |provider| AuthHelper.providers_for_base_controller.each do |provider|
alias_method provider['name'], :handle_omniauth alias_method provider, :handle_omniauth
end end
# Extend the standard implementation to also increment # Extend the standard implementation to also increment
......
class Profiles::ActiveSessionsController < Profiles::ApplicationController
def index
@sessions = ActiveSession.list(current_user)
end
def destroy
ActiveSession.destroy(current_user, params[:id])
respond_to do |format|
format.html { redirect_to profile_active_sessions_url, status: 302 }
format.js { head :ok }
end
end
end
...@@ -25,7 +25,7 @@ class Projects::ApplicationController < ApplicationController ...@@ -25,7 +25,7 @@ class Projects::ApplicationController < ApplicationController
params[:namespace_id] = project.namespace.to_param params[:namespace_id] = project.namespace.to_param
params[:project_id] = project.to_param params[:project_id] = project.to_param
url_for(params) url_for(safe_params)
end end
def repository def repository
......
...@@ -77,8 +77,7 @@ class Projects::LfsStorageController < Projects::GitHttpClientController ...@@ -77,8 +77,7 @@ class Projects::LfsStorageController < Projects::GitHttpClientController
def link_to_project!(object) def link_to_project!(object)
if object && !object.projects.exists?(storage_project.id) if object && !object.projects.exists?(storage_project.id)
object.projects << storage_project object.lfs_objects_projects.create!(project: storage_project)
object.save!
end end
end end
end end
...@@ -33,9 +33,7 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -33,9 +33,7 @@ class Projects::NotesController < Projects::ApplicationController
def resolve def resolve
return render_404 unless note.resolvable? return render_404 unless note.resolvable?
note.resolve!(current_user) Notes::ResolveService.new(project, current_user).execute(note)
MergeRequests::ResolvedDiscussionNotificationService.new(project, current_user).execute(note.noteable)
discussion = note.discussion discussion = note.discussion
......
...@@ -39,7 +39,7 @@ class GroupsFinder < UnionFinder ...@@ -39,7 +39,7 @@ class GroupsFinder < UnionFinder
def all_groups def all_groups
return [owned_groups] if params[:owned] return [owned_groups] if params[:owned]
return [Group.all] if current_user&.full_private_access? return [Group.all] if current_user&.full_private_access? && all_available?
groups = [] groups = []
groups << Gitlab::GroupHierarchy.new(groups_for_ancestors, groups_for_descendants).all_groups if current_user groups << Gitlab::GroupHierarchy.new(groups_for_ancestors, groups_for_descendants).all_groups if current_user
...@@ -67,6 +67,10 @@ class GroupsFinder < UnionFinder ...@@ -67,6 +67,10 @@ class GroupsFinder < UnionFinder
end end
def include_public_groups? def include_public_groups?
current_user.nil? || params.fetch(:all_available, true) current_user.nil? || all_available?
end
def all_available?
params.fetch(:all_available, true)
end end
end end
module ActiveSessionsHelper
# Maps a device type as defined in `ActiveSession` to an svg icon name and
# outputs the icon html.
#
# see `DeviceDetector::Device::DEVICE_NAMES` about the available device types
def active_session_device_type_icon(active_session)
icon_name =
case active_session.device_type
when 'smartphone', 'feature phone', 'phablet'
'mobile'
when 'tablet'
'tablet'
when 'tv', 'smart display', 'camera', 'portable media player', 'console'
'media'
when 'car browser'
'car'
else
'monitor-o'
end
sprite_icon(icon_name, size: 16, css_class: 'prepend-top-2')
end
end
...@@ -32,80 +32,6 @@ module ApplicationHelper ...@@ -32,80 +32,6 @@ module ApplicationHelper
args.any? { |v| v.to_s.downcase == action_name } args.any? { |v| v.to_s.downcase == action_name }
end end
def project_icon(project_id, options = {})
project =
if project_id.respond_to?(:avatar_url)
project_id
else
Project.find_by_full_path(project_id)
end
if project.avatar_url
image_tag project.avatar_url, options
else # generated icon
project_identicon(project, options)
end
end
def project_identicon(project, options = {})
allowed_colors = {
red: 'FFEBEE',
purple: 'F3E5F5',
indigo: 'E8EAF6',
blue: 'E3F2FD',
teal: 'E0F2F1',
orange: 'FBE9E7',
gray: 'EEEEEE'
}
options[:class] ||= ''
options[:class] << ' identicon'
bg_key = project.id % 7
style = "background-color: ##{allowed_colors.values[bg_key]}; color: #555"
content_tag(:div, class: options[:class], style: style) do
project.name[0, 1].upcase
end
end
# Takes both user and email and returns the avatar_icon by
# user (preferred) or email.
def avatar_icon_for(user = nil, email = nil, size = nil, scale = 2, only_path: true)
if user
avatar_icon_for_user(user, size, scale, only_path: only_path)
elsif email
avatar_icon_for_email(email, size, scale, only_path: only_path)
else
default_avatar
end
end
def avatar_icon_for_email(email = nil, size = nil, scale = 2, only_path: true)
user = User.find_by_any_email(email.try(:downcase))
if user
avatar_icon_for_user(user, size, scale, only_path: only_path)
else
gravatar_icon(email, size, scale)
end
end
def avatar_icon_for_user(user = nil, size = nil, scale = 2, only_path: true)
if user
user.avatar_url(size: size, only_path: only_path) || default_avatar
else
gravatar_icon(nil, size, scale)
end
end
def gravatar_icon(user_email = '', size = nil, scale = 2)
GravatarService.new.execute(user_email, size, scale) ||
default_avatar
end
def default_avatar
asset_path('no_avatar.png')
end
def last_commit(project) def last_commit(project)
if project.repo_exists? if project.repo_exists?
time_ago_with_tooltip(project.repository.commit.committed_date) time_ago_with_tooltip(project.repository.commit.committed_date)
......
module AuthHelper module AuthHelper
PROVIDERS_WITH_ICONS = %w(twitter github gitlab bitbucket google_oauth2 facebook azure_oauth2 authentiq).freeze PROVIDERS_WITH_ICONS = %w(twitter github gitlab bitbucket google_oauth2 facebook azure_oauth2 authentiq).freeze
FORM_BASED_PROVIDERS = [/\Aldap/, 'crowd'].freeze LDAP_PROVIDER = /\Aldap/
def ldap_enabled? def ldap_enabled?
Gitlab::Auth::LDAP::Config.enabled? Gitlab::Auth::LDAP::Config.enabled?
...@@ -23,7 +23,7 @@ module AuthHelper ...@@ -23,7 +23,7 @@ module AuthHelper
end end
def form_based_provider?(name) def form_based_provider?(name)
FORM_BASED_PROVIDERS.any? { |pattern| pattern === name.to_s } [LDAP_PROVIDER, 'crowd'].any? { |pattern| pattern === name.to_s }
end end
def form_based_providers def form_based_providers
...@@ -38,6 +38,10 @@ module AuthHelper ...@@ -38,6 +38,10 @@ module AuthHelper
auth_providers.reject { |provider| form_based_provider?(provider) } auth_providers.reject { |provider| form_based_provider?(provider) }
end end
def providers_for_base_controller
auth_providers.reject { |provider| LDAP_PROVIDER === provider }
end
def enabled_button_based_providers def enabled_button_based_providers
disabled_providers = Gitlab::CurrentSettings.disabled_oauth_sign_in_sources || [] disabled_providers = Gitlab::CurrentSettings.disabled_oauth_sign_in_sources || []
......
module AvatarsHelper module AvatarsHelper
def project_icon(project_id, options = {})
project =
if project_id.respond_to?(:avatar_url)
project_id
else
Project.find_by_full_path(project_id)
end
if project.avatar_url
image_tag project.avatar_url, options
else # generated icon
project_identicon(project, options)
end
end
def project_identicon(project, options = {})
allowed_colors = {
red: 'FFEBEE',
purple: 'F3E5F5',
indigo: 'E8EAF6',
blue: 'E3F2FD',
teal: 'E0F2F1',
orange: 'FBE9E7',
gray: 'EEEEEE'
}
options[:class] ||= ''
options[:class] << ' identicon'
bg_key = project.id % 7
style = "background-color: ##{allowed_colors.values[bg_key]}; color: #555"
content_tag(:div, class: options[:class], style: style) do
project.name[0, 1].upcase
end
end
# Takes both user and email and returns the avatar_icon by
# user (preferred) or email.
def avatar_icon_for(user = nil, email = nil, size = nil, scale = 2, only_path: true)
if user
avatar_icon_for_user(user, size, scale, only_path: only_path)
elsif email
avatar_icon_for_email(email, size, scale, only_path: only_path)
else
default_avatar
end
end
def avatar_icon_for_email(email = nil, size = nil, scale = 2, only_path: true)
user = User.find_by_any_email(email.try(:downcase))
if user
avatar_icon_for_user(user, size, scale, only_path: only_path)
else
gravatar_icon(email, size, scale)
end
end
def avatar_icon_for_user(user = nil, size = nil, scale = 2, only_path: true)
if user
user.avatar_url(size: size, only_path: only_path) || default_avatar
else
gravatar_icon(nil, size, scale)
end
end
def gravatar_icon(user_email = '', size = nil, scale = 2)
GravatarService.new.execute(user_email, size, scale) ||
default_avatar
end
def default_avatar
ActionController::Base.helpers.image_path('no_avatar.png')
end
def author_avatar(commit_or_event, options = {}) def author_avatar(commit_or_event, options = {})
user_avatar(options.merge({ user_avatar(options.merge({
user: commit_or_event.author, user: commit_or_event.author,
......
...@@ -442,7 +442,7 @@ module ProjectsHelper ...@@ -442,7 +442,7 @@ module ProjectsHelper
visibilityHelpPath: help_page_path('public_access/public_access'), visibilityHelpPath: help_page_path('public_access/public_access'),
registryAvailable: Gitlab.config.registry.enabled, registryAvailable: Gitlab.config.registry.enabled,
registryHelpPath: help_page_path('user/project/container_registry'), registryHelpPath: help_page_path('user/project/container_registry'),
lfsAvailable: Gitlab.config.lfs.enabled && current_user.admin?, lfsAvailable: Gitlab.config.lfs.enabled,
lfsHelpPath: help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs') lfsHelpPath: help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
} }
......
module SystemNoteHelper module SystemNoteHelper
ICON_NAMES_BY_ACTION = { ICON_NAMES_BY_ACTION = {
'commit' => 'commit', 'commit' => 'commit',
'description' => 'pencil', 'description' => 'pencil-square',
'merge' => 'git-merge', 'merge' => 'git-merge',
'merged' => 'git-merge', 'merged' => 'git-merge',
'opened' => 'issue-open', 'opened' => 'issue-open',
'closed' => 'issue-close', 'closed' => 'issue-close',
'time_tracking' => 'timer', 'time_tracking' => 'timer',
'assignee' => 'user', 'assignee' => 'user',
'title' => 'pencil', 'title' => 'pencil-square',
'task' => 'task-done', 'task' => 'task-done',
'label' => 'label', 'label' => 'label',
'cross_reference' => 'comment-dots', 'cross_reference' => 'comment-dots',
...@@ -18,7 +18,7 @@ module SystemNoteHelper ...@@ -18,7 +18,7 @@ module SystemNoteHelper
'milestone' => 'clock', 'milestone' => 'clock',
'discussion' => 'comment', 'discussion' => 'comment',
'moved' => 'arrow-right', 'moved' => 'arrow-right',
'outdated' => 'pencil', 'outdated' => 'pencil-square',
'duplicate' => 'issue-duplicate', 'duplicate' => 'issue-duplicate',
'locked' => 'lock', 'locked' => 'lock',
'unlocked' => 'lock-open' 'unlocked' => 'lock-open'
......
...@@ -16,6 +16,7 @@ class Notify < BaseMailer ...@@ -16,6 +16,7 @@ class Notify < BaseMailer
helper BlobHelper helper BlobHelper
helper EmailsHelper helper EmailsHelper
helper MembersHelper helper MembersHelper
helper AvatarsHelper
helper GitlabRoutingHelper helper GitlabRoutingHelper
def test_email(recipient_email, subject, body) def test_email(recipient_email, subject, body)
......
class ActiveSession
include ActiveModel::Model
attr_accessor :created_at, :updated_at,
:session_id, :ip_address,
:browser, :os, :device_name, :device_type
def current?(session)
return false if session_id.nil? || session.id.nil?
session_id == session.id
end
def human_device_type
device_type&.titleize
end
def self.set(user, request)
Gitlab::Redis::SharedState.with do |redis|
session_id = request.session.id
client = DeviceDetector.new(request.user_agent)
timestamp = Time.current
active_user_session = new(
ip_address: request.ip,
browser: client.name,
os: client.os_name,
device_name: client.device_name,
device_type: client.device_type,
created_at: user.current_sign_in_at || timestamp,
updated_at: timestamp,
session_id: session_id
)
redis.pipelined do
redis.setex(
key_name(user.id, session_id),
Settings.gitlab['session_expire_delay'] * 60,
Marshal.dump(active_user_session)
)
redis.sadd(
lookup_key_name(user.id),
session_id
)
end
end
end
def self.list(user)
Gitlab::Redis::SharedState.with do |redis|
cleaned_up_lookup_entries(redis, user.id).map do |entry|
# rubocop:disable Security/MarshalLoad
Marshal.load(entry)
# rubocop:enable Security/MarshalLoad
end
end
end
def self.destroy(user, session_id)
Gitlab::Redis::SharedState.with do |redis|
redis.srem(lookup_key_name(user.id), session_id)
deleted_keys = redis.del(key_name(user.id, session_id))
# only allow deleting the devise session if we could actually find a
# related active session. this prevents another user from deleting
# someone else's session.
if deleted_keys > 0
redis.del("#{Gitlab::Redis::SharedState::SESSION_NAMESPACE}:#{session_id}")
end
end
end
def self.cleanup(user)
Gitlab::Redis::SharedState.with do |redis|
cleaned_up_lookup_entries(redis, user.id)
end
end
def self.key_name(user_id, session_id = '*')
"#{Gitlab::Redis::SharedState::USER_SESSIONS_NAMESPACE}:#{user_id}:#{session_id}"
end
def self.lookup_key_name(user_id)
"#{Gitlab::Redis::SharedState::USER_SESSIONS_LOOKUP_NAMESPACE}:#{user_id}"
end
def self.cleaned_up_lookup_entries(redis, user_id)
lookup_key = lookup_key_name(user_id)
session_ids = redis.smembers(lookup_key)
entry_keys = session_ids.map { |session_id| key_name(user_id, session_id) }
return [] if entry_keys.empty?
entries = redis.mget(entry_keys)
session_ids_and_entries = session_ids.zip(entries)
# remove expired keys.
# only the single key entries are automatically expired by redis, the
# lookup entries in the set need to be removed manually.
session_ids_and_entries.reject { |_session_id, entry| entry }.each do |session_id, _entry|
redis.srem(lookup_key, session_id)
end
session_ids_and_entries.select { |_session_id, entry| entry }.map { |_session_id, entry| entry }
end
end
...@@ -13,7 +13,7 @@ module Ci ...@@ -13,7 +13,7 @@ module Ci
after_save :update_project_statistics_after_save, if: :size_changed? after_save :update_project_statistics_after_save, if: :size_changed?
after_destroy :update_project_statistics_after_destroy, unless: :project_destroyed? after_destroy :update_project_statistics_after_destroy, unless: :project_destroyed?
after_save :update_file_store after_save :update_file_store, if: :file_changed?
scope :with_files_stored_locally, -> { where(file_store: [nil, ::JobArtifactUploader::Store::LOCAL]) } scope :with_files_stored_locally, -> { where(file_store: [nil, ::JobArtifactUploader::Store::LOCAL]) }
......
...@@ -530,6 +530,17 @@ module Ci ...@@ -530,6 +530,17 @@ module Ci
@latest_builds_with_artifacts ||= builds.latest.with_artifacts_archive.to_a @latest_builds_with_artifacts ||= builds.latest.with_artifacts_archive.to_a
end end
# Rails 5.0 autogenerated question mark enum methods return wrong result if enum value is nil.
# They always return `false`.
# These methods overwrite autogenerated ones to return correct results.
def unknown?
Gitlab.rails5? ? source.nil? : super
end
def unknown_source?
Gitlab.rails5? ? config_source.nil? : super
end
private private
def ci_yaml_from_repo def ci_yaml_from_repo
......
...@@ -13,14 +13,27 @@ module Ci ...@@ -13,14 +13,27 @@ module Ci
has_many :statuses, class_name: 'CommitStatus', foreign_key: :stage_id has_many :statuses, class_name: 'CommitStatus', foreign_key: :stage_id
has_many :builds, foreign_key: :stage_id has_many :builds, foreign_key: :stage_id
validates :project, presence: true, unless: :importing? with_options unless: :importing? do
validates :pipeline, presence: true, unless: :importing? validates :project, presence: true
validates :name, presence: true, unless: :importing? validates :pipeline, presence: true
validates :name, presence: true
validates :position, presence: true
end
after_initialize do |stage| after_initialize do
self.status = DEFAULT_STATUS if self.status.nil? self.status = DEFAULT_STATUS if self.status.nil?
end end
before_validation unless: :importing? do
next if position.present?
self.position = statuses.select(:stage_idx)
.where('stage_idx IS NOT NULL')
.group(:stage_idx)
.order('COUNT(*) DESC')
.first&.stage_idx.to_i
end
state_machine :status, initial: :created do state_machine :status, initial: :created do
event :enqueue do event :enqueue do
transition created: :pending transition created: :pending
......
...@@ -105,6 +105,10 @@ class Commit ...@@ -105,6 +105,10 @@ class Commit
end end
end end
end end
def parent_class
::Project
end
end end
attr_accessor :raw attr_accessor :raw
...@@ -420,6 +424,12 @@ class Commit ...@@ -420,6 +424,12 @@ class Commit
# no-op but needs to be defined since #persisted? is defined # no-op but needs to be defined since #persisted? is defined
end end
def touch_later
# No-op.
# This method is called by ActiveRecord.
# We don't want to do anything for `Commit` model, so this is empty.
end
WIP_REGEX = /\A\s*(((?i)(\[WIP\]|WIP:|WIP)\s|WIP$))|(fixup!|squash!)\s/.freeze WIP_REGEX = /\A\s*(((?i)(\[WIP\]|WIP:|WIP)\s|WIP$))|(fixup!|squash!)\s/.freeze
def work_in_progress? def work_in_progress?
......
...@@ -189,4 +189,11 @@ class CommitStatus < ActiveRecord::Base ...@@ -189,4 +189,11 @@ class CommitStatus < ActiveRecord::Base
v =~ /\d+/ ? v.to_i : v v =~ /\d+/ ? v.to_i : v
end end
end end
# Rails 5.0 autogenerated question mark enum methods return wrong result if enum value is nil.
# They always return `false`.
# This method overwrites the autogenerated one to return correct result.
def unknown_failure?
Gitlab.rails5? ? failure_reason.nil? : super
end
end end
...@@ -54,7 +54,20 @@ class DiffNote < Note ...@@ -54,7 +54,20 @@ class DiffNote < Note
end end
def diff_file def diff_file
@diff_file ||= self.original_position.diff_file(self.project.repository) @diff_file ||=
begin
if created_at_diff?(noteable.diff_refs)
# We're able to use the already persisted diffs (Postgres) if we're
# presenting a "current version" of the MR discussion diff.
# So no need to make an extra Gitaly diff request for it.
# As an extra benefit, the returned `diff_file` already
# has `highlighted_diff_lines` data set from Redis on
# `Diff::FileCollection::MergeRequestDiff`.
noteable.diffs(paths: original_position.paths, expanded: true).diff_files.first
else
original_position.diff_file(self.project.repository)
end
end
end end
def diff_line def diff_line
......
...@@ -11,7 +11,7 @@ class LfsObject < ActiveRecord::Base ...@@ -11,7 +11,7 @@ class LfsObject < ActiveRecord::Base
mount_uploader :file, LfsObjectUploader mount_uploader :file, LfsObjectUploader
after_save :update_file_store after_save :update_file_store, if: :file_changed?
def update_file_store def update_file_store
# The file.object_store is set during `uploader.store!` # The file.object_store is set during `uploader.store!`
......
...@@ -910,7 +910,7 @@ class User < ActiveRecord::Base ...@@ -910,7 +910,7 @@ class User < ActiveRecord::Base
def delete_async(deleted_by:, params: {}) def delete_async(deleted_by:, params: {})
block if params[:hard_delete] block if params[:hard_delete]
DeleteUserWorker.perform_async(deleted_by.id, id, params) DeleteUserWorker.perform_async(deleted_by.id, id, params.to_h)
end end
def notification_service def notification_service
......
...@@ -42,6 +42,7 @@ module Ci ...@@ -42,6 +42,7 @@ module Ci
def create_stage def create_stage
Ci::Stage.create!(name: @build.stage, Ci::Stage.create!(name: @build.stage,
position: @build.stage_idx,
pipeline: @build.pipeline, pipeline: @build.pipeline,
project: @build.project) project: @build.project)
end end
......
...@@ -50,21 +50,30 @@ module MergeRequests ...@@ -50,21 +50,30 @@ module MergeRequests
end end
def commit def commit
message = params[:commit_message] || merge_request.merge_commit_message
log_info("Git merge started on JID #{merge_jid}") log_info("Git merge started on JID #{merge_jid}")
commit_id = repository.merge(current_user, source, merge_request, message) commit_id = try_merge
log_info("Git merge finished on JID #{merge_jid} commit #{commit_id}")
if commit_id
log_info("Git merge finished on JID #{merge_jid} commit #{commit_id}")
else
raise MergeError, 'Conflicts detected during merge'
end
raise MergeError, 'Conflicts detected during merge' unless commit_id merge_request.update!(merge_commit_sha: commit_id)
end
def try_merge
message = params[:commit_message] || merge_request.merge_commit_message
merge_request.update(merge_commit_sha: commit_id) repository.merge(current_user, source, merge_request, message)
rescue Gitlab::Git::HooksService::PreReceiveError => e rescue Gitlab::Git::HooksService::PreReceiveError => e
raise MergeError, e.message handle_merge_error(log_message: e.message)
rescue StandardError => e raise MergeError, 'Something went wrong during merge pre-receive hook'
raise MergeError, "Something went wrong during merge: #{e.message}" rescue => e
handle_merge_error(log_message: e.message)
raise MergeError, 'Something went wrong during merge'
ensure ensure
merge_request.update(in_progress_merge_commit_sha: nil) merge_request.update!(in_progress_merge_commit_sha: nil)
end end
def after_merge def after_merge
......
module Notes
class ResolveService < ::BaseService
def execute(note)
note.resolve!(current_user)
::MergeRequests::ResolvedDiscussionNotificationService.new(project, current_user).execute(note.noteable)
end
end
end
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
.panel .panel
.panel-heading.alert.alert-danger .panel-heading.alert.alert-danger
Last repository check Last repository check
= "(#{time_ago_in_words(@project.last_repository_check_at)} ago)" = "(#{time_ago_with_tooltip(@project.last_repository_check_at)})"
failed. See failed. See
= link_to 'repocheck.log', admin_logs_path = link_to 'repocheck.log', admin_logs_path
for error messages. for error messages.
......
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
= tag = tag
%td %td
- if runner.contacted_at - if runner.contacted_at
#{time_ago_in_words(runner.contacted_at)} ago = time_ago_with_tooltip runner.contacted_at
- else - else
Never Never
%td.admin-runner-btn-group-cell %td.admin-runner-btn-group-cell
......
...@@ -108,4 +108,4 @@ ...@@ -108,4 +108,4 @@
%td.timestamp %td.timestamp
- if build.finished_at - if build.finished_at
%span #{time_ago_in_words build.finished_at} ago %span= time_ago_with_tooltip build.finished_at
...@@ -20,5 +20,4 @@ ...@@ -20,5 +20,4 @@
%td %td
= service.description = service.description
%td.light %td.light
= time_ago_in_words service.updated_at = time_ago_with_tooltip service.updated_at
ago
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
= email_default_heading("Hello, #{@resource.name}!") = email_default_heading("Hello, #{@resource.name}!")
%p %p
Your GitLab account has been locked due to an excessive amount of unsuccessful Your GitLab account has been locked due to an excessive amount of unsuccessful
sign in attempts. Your account will automatically unlock in #{time_ago_in_words(Devise.unlock_in.from_now)} sign in attempts. Your account will automatically unlock in #{distance_of_time_in_words(Devise.unlock_in)}
or you may click the link below to unlock now. or you may click the link below to unlock now.
#cta #cta
= link_to('Unlock account', unlock_url(@resource, unlock_token: @token)) = link_to('Unlock account', unlock_url(@resource, unlock_token: @token))
Hello, <%= @resource.name %>! Hello, <%= @resource.name %>!
Your GitLab account has been locked due to an excessive amount of unsuccessful Your GitLab account has been locked due to an excessive amount of unsuccessful
sign in attempts. Your account will automatically unlock in <%= time_ago_in_words(Devise.unlock_in.from_now) %> sign in attempts. Your account will automatically unlock in <%= distance_of_time_in_words(Devise.unlock_in) %>
or you may click the link below to unlock now. or you may click the link below to unlock now.
<%= unlock_url(@resource, unlock_token: @token) %> <%= unlock_url(@resource, unlock_token: @token) %>
- if current_user.admin? .form-group
.form-group = f.label :lfs_enabled, 'Large File Storage', class: 'control-label'
= f.label :lfs_enabled, 'Large File Storage', class: 'control-label' .col-sm-10
.col-sm-10 .checkbox
.checkbox = f.label :lfs_enabled do
= f.label :lfs_enabled do = f.check_box :lfs_enabled, checked: @group.lfs_enabled?
= f.check_box :lfs_enabled, checked: @group.lfs_enabled? %strong
%strong Allow projects within this group to use Git LFS
Allow projects within this group to use Git LFS = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
= link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs') %br/
%br/ %span.descr This setting can be overridden in each project.
%span.descr This setting can be overridden in each project.
- if can? current_user, :admin_group, @group .form-group
.form-group = f.label :require_two_factor_authentication, 'Two-factor authentication', class: 'control-label col-sm-2'
= f.label :require_two_factor_authentication, 'Two-factor authentication', class: 'control-label col-sm-2' .col-sm-10
.col-sm-10 .checkbox
.checkbox = f.label :require_two_factor_authentication do
= f.label :require_two_factor_authentication do = f.check_box :require_two_factor_authentication
= f.check_box :require_two_factor_authentication %strong
%strong Require all users in this group to setup Two-factor authentication
Require all users in this group to setup Two-factor authentication = link_to icon('question-circle'), help_page_path('security/two_factor_authentication', anchor: 'enforcing-2fa-for-all-users-in-a-group')
= link_to icon('question-circle'), help_page_path('security/two_factor_authentication', anchor: 'enforcing-2fa-for-all-users-in-a-group') .form-group
.form-group .col-sm-offset-2.col-sm-10
.col-sm-offset-2.col-sm-10 .checkbox
.checkbox = f.text_field :two_factor_grace_period, class: 'form-control'
= f.text_field :two_factor_grace_period, class: 'form-control' .help-block Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication
.help-block Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication
...@@ -129,6 +129,17 @@ ...@@ -129,6 +129,17 @@
= link_to profile_preferences_path do = link_to profile_preferences_path do
%strong.fly-out-top-item-name %strong.fly-out-top-item-name
#{ _('Preferences') } #{ _('Preferences') }
= nav_link(controller: :active_sessions) do
= link_to profile_active_sessions_path do
.nav-icon-container
= sprite_icon('monitor-lines')
%span.nav-item-name
Active Sessions
%ul.sidebar-sub-level-items.is-fly-out-only
= nav_link(controller: :active_sessions, html_options: { class: "fly-out-top-item" } ) do
= link_to profile_active_sessions_path do
%strong.fly-out-top-item-name
#{ _('Active Sessions') }
= nav_link(path: 'profiles#audit_log') do = nav_link(path: 'profiles#audit_log') do
= link_to audit_log_profile_path do = link_to audit_log_profile_path do
.nav-icon-container .nav-icon-container
......
...@@ -3,5 +3,5 @@ ...@@ -3,5 +3,5 @@
#js-peek{ data: { env: Peek.env, #js-peek{ data: { env: Peek.env,
request_id: Peek.request_id, request_id: Peek.request_id,
peek_url: peek_routes.results_url, peek_url: peek_routes.results_url,
profile_url: url_for(params.merge(lineprofiler: 'true')) }, profile_url: url_for(safe_params.merge(lineprofiler: 'true')) },
class: Peek.env } class: Peek.env }
- is_current_session = active_session.current?(session)
%li
.pull-left.append-right-10{ data: { toggle: 'tooltip' }, title: active_session.human_device_type }
= active_session_device_type_icon(active_session)
.description.pull-left
%div
%strong= active_session.ip_address
- if is_current_session
%div This is your current session
- else
%div
Last accessed on
= l(active_session.updated_at, format: :short)
%div
%strong= active_session.browser
on
%strong= active_session.os
%div
%strong Signed in
on
= l(active_session.created_at, format: :short)
- unless is_current_session
.pull-right
= link_to profile_active_session_path(active_session.session_id), data: { confirm: 'Are you sure? The device will be signed out of GitLab.' }, method: :delete, class: "btn btn-danger prepend-left-10" do
%span.sr-only Revoke
Revoke
- page_title 'Active Sessions'
- @content_class = "limit-container-width" unless fluid_layout
.row.prepend-top-default
.col-lg-4.profile-settings-sidebar
%h4.prepend-top-0
= page_title
%p
This is a list of devices that have logged into your account. Revoke any sessions that you do not recognize.
.col-lg-8
.append-bottom-default
%ul.well-list
= render partial: 'profiles/active_sessions/active_session', collection: @sessions
...@@ -8,18 +8,17 @@ ...@@ -8,18 +8,17 @@
%li{ class: "branch-item js-branch-#{branch.name}" } %li{ class: "branch-item js-branch-#{branch.name}" }
.branch-info .branch-info
.branch-title .branch-title
= link_to project_tree_path(@project, branch.name), class: 'item-title str-truncated-100 ref-name' do = sprite_icon('fork', size: 12)
= sprite_icon('fork', size: 12) = link_to project_tree_path(@project, branch.name), class: 'item-title str-truncated-100 ref-name prepend-left-8' do
= branch.name = branch.name
&nbsp;
- if branch.name == @repository.root_ref - if branch.name == @repository.root_ref
%span.label.label-primary default %span.label.label-primary.prepend-left-5 default
- elsif merged - elsif merged
%span.label.label-info.has-tooltip{ title: s_('Branches|Merged into %{default_branch}') % { default_branch: @repository.root_ref } } %span.label.label-info.has-tooltip.prepend-left-5{ title: s_('Branches|Merged into %{default_branch}') % { default_branch: @repository.root_ref } }
= s_('Branches|merged') = s_('Branches|merged')
- if protected_branch?(@project, branch) - if protected_branch?(@project, branch)
%span.label.label-success %span.label.label-success.prepend-left-5
= s_('Branches|protected') = s_('Branches|protected')
.block-truncated .block-truncated
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
.files-changed-inner .files-changed-inner
.inline-parallel-buttons.hidden-xs.hidden-sm .inline-parallel-buttons.hidden-xs.hidden-sm
- if !diffs_expanded? && diff_files.any? { |diff_file| diff_file.collapsed? } - if !diffs_expanded? && diff_files.any? { |diff_file| diff_file.collapsed? }
= link_to 'Expand all', url_for(params.merge(expanded: 1, format: nil)), class: 'btn btn-default' = link_to 'Expand all', url_for(safe_params.merge(expanded: 1, format: nil)), class: 'btn btn-default'
- if show_whitespace_toggle - if show_whitespace_toggle
- if current_controller?(:commit) - if current_controller?(:commit)
= commit_diff_whitespace_link(diffs.project, @commit, class: 'hidden-xs') = commit_diff_whitespace_link(diffs.project, @commit, class: 'hidden-xs')
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
- elsif @build.has_expiring_artifacts? - elsif @build.has_expiring_artifacts?
%p.build-detail-row %p.build-detail-row
The artifacts will be removed in The artifacts will be removed in
%span= time_ago_in_words @build.artifacts_expire_at %span= time_ago_with_tooltip @build.artifacts_expire_at
- if @build.artifacts? - if @build.artifacts?
.btn-group.btn-group-justified{ role: :group } .btn-group.btn-group-justified{ role: :group }
......
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
\- \-
%td %td
- if tag.created_at - if tag.created_at
= time_ago_in_words(tag.created_at) = time_ago_with_tooltip tag.created_at
- else - else
.light .light
\- \-
......
...@@ -62,6 +62,6 @@ ...@@ -62,6 +62,6 @@
%td Last contact %td Last contact
%td %td
- if @runner.contacted_at - if @runner.contacted_at
#{time_ago_in_words(@runner.contacted_at)} ago = time_ago_with_tooltip @runner.contacted_at
- else - else
Never Never
...@@ -27,5 +27,4 @@ ...@@ -27,5 +27,4 @@
= service.description = service.description
%td.light %td.light
- if service.updated_at.present? - if service.updated_at.present?
= time_ago_in_words service.updated_at = time_ago_with_tooltip service.updated_at
ago
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
%td %td
- if trigger.last_used - if trigger.last_used
#{time_ago_in_words(trigger.last_used)} ago = time_ago_with_tooltip trigger.last_used
- else - else
Never Never
......
...@@ -35,5 +35,4 @@ ...@@ -35,5 +35,4 @@
%span.light %span.light
#{t('sherlock.finished_at')}: #{t('sherlock.finished_at')}:
%strong %strong
= time_ago_in_words(@transaction.finished_at) = time_ago_with_tooltip @transaction.finished_at
= t('sherlock.ago')
...@@ -35,8 +35,7 @@ ...@@ -35,8 +35,7 @@
= t('sherlock.seconds') = t('sherlock.seconds')
%td= trans.queries.length %td= trans.queries.length
%td %td
= time_ago_in_words(trans.finished_at) = time_ago_with_tooltip trans.finished_at
= t('sherlock.ago')
%td %td
= link_to(sherlock_transaction_path(trans), class: 'btn btn-xs') do = link_to(sherlock_transaction_path(trans), class: 'btn btn-xs') do
= t('sherlock.view') = t('sherlock.view')
---
title: Replace time_ago_in_words with JS-based one
merge_request: 18607
author: Takuya Noguchi
type: performance
---
title: Replace the `project/source/markdown_render.feature` spinach test with an rspec analog
merge_request: 18525
author: "@blackst0ne"
type: other
---
title: Fix commit trailer rendering when Gravatar is disabled
merge_request:
author:
type: fixed
---
title: Display active sessions and allow the user to revoke any of it
merge_request: 17867
author: Alexis Reigel
type: added
---
title: For group dashboard, we no longer show groups which the visitor is not a member of (this applies to admins and auditors)
merge_request: 17884
author: Roger Rüttimann
type: changed
---
title: Fix file_store for artifacts and lfs when saving
merge_request:
author:
type: fixed
---
title: Fixed inconsistent protected branch pill baseline
merge_request:
author:
type: fixed
---
title: Increase cluster applications installer availability using alpine linux mirrors
merge_request:
author:
type: performance
---
title: Improve quick actions summary preview
merge_request: 18659
author: George Tsiolis
type: changed
---
title: Increase new issue metadata form margin
merge_request: 18630
author: George Tsiolis
type: fixed
---
title: Add discussion API for merge requests and commits
merge_request:
author:
type: added
---
title: Display only generic message on merge error to avoid exposing any potentially
sensitive or user unfriendly backend messages.
merge_request:
author:
type: fixed
---
title: Show group and project LFS settings in the interface to Owners and Masters
merge_request: 18562
author:
type: changed
---
title: Use persisted diff data instead fetching Git on discussions
merge_request:
author:
type: performance
---
title: Move WorkInProgress vue component
merge_request: 17536
author: George Tsiolis
type: performance
---
title: Move TimeTrackingNoTrackingPane vue component
merge_request: 18676
author: George Tsiolis
type: performance
---
title: Move SidebarTimeTracking vue component
merge_request: 18677
author: George Tsiolis
type: performance
---
title: Revert discussion counter height
merge_request: 18656
author: George Tsiolis
type: changed
---
title: Update timeline icon for description edit
merge_request: 18633
author: George Tsiolis
type: changed
---
title: Finish NamespaceService migration to Gitaly
merge_request:
author:
type: performance
...@@ -15,19 +15,15 @@ cookie_key = if Rails.env.development? ...@@ -15,19 +15,15 @@ cookie_key = if Rails.env.development?
"_gitlab_session" "_gitlab_session"
end end
if Rails.env.test? sessions_config = Gitlab::Redis::SharedState.params
Gitlab::Application.config.session_store :cookie_store, key: "_gitlab_session" sessions_config[:namespace] = Gitlab::Redis::SharedState::SESSION_NAMESPACE
else
sessions_config = Gitlab::Redis::SharedState.params
sessions_config[:namespace] = Gitlab::Redis::SharedState::SESSION_NAMESPACE
Gitlab::Application.config.session_store( Gitlab::Application.config.session_store(
:redis_store, # Using the cookie_store would enable session replay attacks. :redis_store, # Using the cookie_store would enable session replay attacks.
servers: sessions_config, servers: sessions_config,
key: cookie_key, key: cookie_key,
secure: Gitlab.config.gitlab.https, secure: Gitlab.config.gitlab.https,
httponly: true, httponly: true,
expires_in: Settings.gitlab['session_expire_delay'] * 60, expires_in: Settings.gitlab['session_expire_delay'] * 60,
path: Rails.application.config.relative_url_root.nil? ? '/' : Gitlab::Application.config.relative_url_root path: Rails.application.config.relative_url_root.nil? ? '/' : Gitlab::Application.config.relative_url_root
) )
end
...@@ -6,4 +6,16 @@ Rails.application.configure do |config| ...@@ -6,4 +6,16 @@ Rails.application.configure do |config|
Warden::Manager.before_failure do |env, opts| Warden::Manager.before_failure do |env, opts|
Gitlab::Auth::BlockedUserTracker.log_if_user_blocked(env) Gitlab::Auth::BlockedUserTracker.log_if_user_blocked(env)
end end
Warden::Manager.after_authentication do |user, auth, opts|
ActiveSession.cleanup(user)
end
Warden::Manager.after_set_user only: :fetch do |user, auth, opts|
ActiveSession.set(user, auth.request)
end
Warden::Manager.before_logout do |user, auth, opts|
ActiveSession.destroy(user || auth.user, auth.request.session.id)
end
end end
var path = require('path'); const path = require('path');
var webpack = require('webpack'); const glob = require('glob');
var argumentsParser = require('commander'); const chalk = require('chalk');
var webpackConfig = require('./webpack.config.js'); const webpack = require('webpack');
var ROOT_PATH = path.resolve(__dirname, '..'); const argumentsParser = require('commander');
const webpackConfig = require('./webpack.config.js');
const ROOT_PATH = path.resolve(__dirname, '..');
function fatalError(message) {
console.error(chalk.red(`\nError: ${message}\n`));
process.exit(1);
}
// remove problematic plugins // remove problematic plugins
if (webpackConfig.plugins) { if (webpackConfig.plugins) {
...@@ -15,33 +23,70 @@ if (webpackConfig.plugins) { ...@@ -15,33 +23,70 @@ if (webpackConfig.plugins) {
}); });
} }
var testFiles = argumentsParser const specFilters = argumentsParser
.option( .option(
'-f, --filter-spec [filter]', '-f, --filter-spec [filter]',
'Filter run spec files by path. Multiple filters are like a logical OR.', 'Filter run spec files by path. Multiple filters are like a logical OR.',
(val, memo) => { (filter, memo) => {
memo.push(val); memo.push(filter, filter.replace(/\/?$/, '/**/*.js'));
return memo; return memo;
}, },
[] []
) )
.parse(process.argv).filterSpec; .parse(process.argv).filterSpec;
webpackConfig.plugins.push( if (specFilters.length) {
new webpack.DefinePlugin({ const specsPath = /^(?:\.[\\\/])?spec[\\\/]javascripts[\\\/]/;
'process.env.TEST_FILES': JSON.stringify(testFiles),
}) // resolve filters
); let filteredSpecFiles = specFilters.map(filter =>
glob
.sync(filter, {
root: ROOT_PATH,
matchBase: true,
})
.filter(path => path.endsWith('spec.js'))
);
// flatten
filteredSpecFiles = Array.prototype.concat.apply([], filteredSpecFiles);
// remove duplicates
filteredSpecFiles = [...new Set(filteredSpecFiles)];
if (filteredSpecFiles.length < 1) {
fatalError('Your filter did not match any test files.');
}
if (!filteredSpecFiles.every(file => specsPath.test(file))) {
fatalError('Test files must be located within /spec/javascripts.');
}
const newContext = filteredSpecFiles.reduce((context, file) => {
const relativePath = file.replace(specsPath, '');
context[file] = `./${relativePath}`;
return context;
}, {});
webpackConfig.plugins.push(
new webpack.ContextReplacementPlugin(
/spec[\\\/]javascripts$/,
path.join(ROOT_PATH, 'spec/javascripts'),
newContext
)
);
}
webpackConfig.devtool = process.env.BABEL_ENV !== 'coverage' && 'cheap-inline-source-map'; webpackConfig.entry = undefined;
webpackConfig.devtool = 'cheap-inline-source-map';
// Karma configuration // Karma configuration
module.exports = function(config) { module.exports = function(config) {
process.env.TZ = 'Etc/UTC'; process.env.TZ = 'Etc/UTC';
var progressReporter = process.env.CI ? 'mocha' : 'progress'; const progressReporter = process.env.CI ? 'mocha' : 'progress';
var karmaConfig = { const karmaConfig = {
basePath: ROOT_PATH, basePath: ROOT_PATH,
browsers: ['ChromeHeadlessCustom'], browsers: ['ChromeHeadlessCustom'],
customLaunchers: { customLaunchers: {
......
...@@ -30,6 +30,7 @@ resource :profile, only: [:show, :update] do ...@@ -30,6 +30,7 @@ resource :profile, only: [:show, :update] do
put :revoke put :revoke
end end
end end
resources :active_sessions, only: [:index, :destroy]
resources :emails, only: [:index, :create, :destroy] do resources :emails, only: [:index, :create, :destroy] do
member do member do
put :resend_confirmation_instructions put :resend_confirmation_instructions
......
...@@ -69,6 +69,9 @@ const config = { ...@@ -69,6 +69,9 @@ const config = {
test: /\.js$/, test: /\.js$/,
exclude: /(node_modules|vendor\/assets)/, exclude: /(node_modules|vendor\/assets)/,
loader: 'babel-loader', loader: 'babel-loader',
options: {
cacheDirectory: path.join(ROOT_PATH, 'tmp/cache/babel-loader'),
},
}, },
{ {
test: /\.vue$/, test: /\.vue$/,
......
class AddTmpStagePriorityIndexToCiBuilds < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_index(:ci_builds, [:stage_id, :stage_idx],
where: 'stage_idx IS NOT NULL', name: 'tmp_build_stage_position_index')
end
def down
remove_concurrent_index_by_name(:ci_builds, 'tmp_build_stage_position_index')
end
end
class AddIndexToCiStage < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
add_column :ci_stages, :position, :integer
end
end
class ScheduleStagesIndexMigration < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
MIGRATION = 'MigrateStageIndex'.freeze
BATCH_SIZE = 10000
disable_ddl_transaction!
class Stage < ActiveRecord::Base
include EachBatch
self.table_name = 'ci_stages'
end
def up
disable_statement_timeout
Stage.all.tap do |relation|
queue_background_migration_jobs_by_range_at_intervals(relation,
MIGRATION,
5.minutes,
batch_size: BATCH_SIZE)
end
end
def down
# noop
end
end
...@@ -322,6 +322,7 @@ ActiveRecord::Schema.define(version: 20180425131009) do ...@@ -322,6 +322,7 @@ ActiveRecord::Schema.define(version: 20180425131009) do
add_index "ci_builds", ["project_id", "id"], name: "index_ci_builds_on_project_id_and_id", using: :btree add_index "ci_builds", ["project_id", "id"], name: "index_ci_builds_on_project_id_and_id", using: :btree
add_index "ci_builds", ["protected"], name: "index_ci_builds_on_protected", using: :btree add_index "ci_builds", ["protected"], name: "index_ci_builds_on_protected", using: :btree
add_index "ci_builds", ["runner_id"], name: "index_ci_builds_on_runner_id", using: :btree add_index "ci_builds", ["runner_id"], name: "index_ci_builds_on_runner_id", using: :btree
add_index "ci_builds", ["stage_id", "stage_idx"], name: "tmp_build_stage_position_index", where: "(stage_idx IS NOT NULL)", using: :btree
add_index "ci_builds", ["stage_id"], name: "index_ci_builds_on_stage_id", using: :btree add_index "ci_builds", ["stage_id"], name: "index_ci_builds_on_stage_id", using: :btree
add_index "ci_builds", ["status", "type", "runner_id"], name: "index_ci_builds_on_status_and_type_and_runner_id", using: :btree add_index "ci_builds", ["status", "type", "runner_id"], name: "index_ci_builds_on_status_and_type_and_runner_id", using: :btree
add_index "ci_builds", ["status"], name: "index_ci_builds_on_status", using: :btree add_index "ci_builds", ["status"], name: "index_ci_builds_on_status", using: :btree
...@@ -486,6 +487,7 @@ ActiveRecord::Schema.define(version: 20180425131009) do ...@@ -486,6 +487,7 @@ ActiveRecord::Schema.define(version: 20180425131009) do
t.string "name" t.string "name"
t.integer "status" t.integer "status"
t.integer "lock_version" t.integer "lock_version"
t.integer "position"
end end
add_index "ci_stages", ["pipeline_id", "name"], name: "index_ci_stages_on_pipeline_id_and_name", unique: true, using: :btree add_index "ci_stages", ["pipeline_id", "name"], name: "index_ci_stages_on_pipeline_id_and_name", unique: true, using: :btree
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment