Commit 1e76f289 authored by Shinya Maeda's avatar Shinya Maeda

Merge branch 'master' into per-project-pipeline-iid

parents a74184eb 40683268
......@@ -6,7 +6,7 @@ end
gem_versions = {}
gem_versions['activerecord_sane_schema_dumper'] = rails5? ? '1.0' : '0.2'
gem_versions['default_value_for'] = rails5? ? '~> 3.0.5' : '~> 3.0.0'
gem_versions['rails'] = rails5? ? '5.0.6' : '4.2.10'
gem_versions['rails'] = rails5? ? '5.0.7' : '4.2.10'
gem_versions['rails-i18n'] = rails5? ? '~> 5.1' : '~> 4.0.9'
# --- The end of special code for migrating to Rails 5.0 ---
......
......@@ -4,43 +4,43 @@ GEM
RedCloth (4.3.2)
abstract_type (0.0.7)
ace-rails-ap (4.1.4)
actioncable (5.0.6)
actionpack (= 5.0.6)
actioncable (5.0.7)
actionpack (= 5.0.7)
nio4r (>= 1.2, < 3.0)
websocket-driver (~> 0.6.1)
actionmailer (5.0.6)
actionpack (= 5.0.6)
actionview (= 5.0.6)
activejob (= 5.0.6)
actionmailer (5.0.7)
actionpack (= 5.0.7)
actionview (= 5.0.7)
activejob (= 5.0.7)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
actionpack (5.0.6)
actionview (= 5.0.6)
activesupport (= 5.0.6)
actionpack (5.0.7)
actionview (= 5.0.7)
activesupport (= 5.0.7)
rack (~> 2.0)
rack-test (~> 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
actionview (5.0.6)
activesupport (= 5.0.6)
actionview (5.0.7)
activesupport (= 5.0.7)
builder (~> 3.1)
erubis (~> 2.7.0)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.3)
activejob (5.0.6)
activesupport (= 5.0.6)
activejob (5.0.7)
activesupport (= 5.0.7)
globalid (>= 0.3.6)
activemodel (5.0.6)
activesupport (= 5.0.6)
activerecord (5.0.6)
activemodel (= 5.0.6)
activesupport (= 5.0.6)
activemodel (5.0.7)
activesupport (= 5.0.7)
activerecord (5.0.7)
activemodel (= 5.0.7)
activesupport (= 5.0.7)
arel (~> 7.0)
activerecord_sane_schema_dumper (1.0)
rails (>= 5, < 6)
activesupport (5.0.6)
activesupport (5.0.7)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (~> 0.7)
i18n (>= 0.7, < 2)
minitest (~> 5.1)
tzinfo (~> 1.1)
acts-as-taggable-on (5.0.0)
......@@ -62,13 +62,13 @@ GEM
asciidoctor (1.5.6.1)
asciidoctor-plantuml (0.0.8)
asciidoctor (~> 1.5)
asset_sync (2.2.0)
asset_sync (2.4.0)
activemodel (>= 4.1.0)
fog-core
mime-types (>= 2.99)
unf
ast (2.4.0)
atomic (1.1.100)
atomic (1.1.99)
attr_encrypted (3.1.0)
encryptor (~> 3.0.0)
attr_required (1.0.1)
......@@ -144,12 +144,10 @@ GEM
connection_pool (2.2.1)
crack (0.4.3)
safe_yaml (~> 1.0.0)
crass (1.0.3)
crass (1.0.4)
creole (0.5.0)
css_parser (1.6.0)
addressable
d3_rails (3.5.17)
railties (>= 3.1.0)
daemons (1.2.6)
database_cleaner (1.5.3)
debug_inspector (0.0.3)
......@@ -292,7 +290,7 @@ GEM
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
gherkin-ruby (0.3.2)
gitaly-proto (0.97.0)
gitaly-proto (0.99.0)
google-protobuf (~> 3.1)
grpc (~> 1.10)
github-linguist (5.3.3)
......@@ -335,9 +333,8 @@ GEM
activesupport (>= 4.2.0)
gollum-grit_adapter (1.0.1)
gitlab-grit (~> 2.7, >= 2.7.1)
gon (6.1.0)
gon (6.2.0)
actionpack (>= 3.0)
json
multi_json
request_store (>= 1.0)
google-api-client (0.19.8)
......@@ -367,8 +364,8 @@ GEM
rack (>= 1.3.0)
rack-accept
virtus (>= 1.0.0)
grape-entity (0.6.1)
activesupport (>= 5.0.0)
grape-entity (0.7.1)
activesupport (>= 4.0)
multi_json (>= 1.3.2)
grape-route-helpers (2.1.0)
activesupport
......@@ -420,7 +417,7 @@ GEM
json (~> 1.8)
multi_xml (>= 0.5.2)
httpclient (2.8.3)
i18n (0.9.5)
i18n (1.0.1)
concurrent-ruby (~> 1.0)
ice_nine (0.11.2)
influxdb (0.5.3)
......@@ -515,7 +512,7 @@ GEM
net-ldap (0.16.1)
net-ssh (4.2.0)
netrc (0.11.0)
nio4r (2.2.0)
nio4r (2.3.1)
nokogiri (1.8.2)
mini_portile2 (~> 2.3.0)
numerizer (0.1.1)
......@@ -545,9 +542,9 @@ GEM
omniauth (~> 1.2)
omniauth-facebook (4.0.0)
omniauth-oauth2 (~> 1.2)
omniauth-github (1.1.2)
omniauth (~> 1.0)
omniauth-oauth2 (~> 1.1)
omniauth-github (1.3.0)
omniauth (~> 1.5)
omniauth-oauth2 (>= 1.4.0, < 2.0)
omniauth-gitlab (1.0.3)
omniauth (~> 1.0)
omniauth-oauth2 (~> 1.0)
......@@ -633,7 +630,7 @@ GEM
parser
unparser
procto (0.0.3)
prometheus-client-mmap (0.9.1)
prometheus-client-mmap (0.9.2)
pry (0.11.3)
coderay (~> 1.1.0)
method_source (~> 0.9.0)
......@@ -644,7 +641,7 @@ GEM
pry (>= 0.10.4)
public_suffix (3.0.2)
pyu-ruby-sasl (0.0.3.3)
rack (2.0.4)
rack (2.0.5)
rack-accept (0.4.5)
rack (>= 0.4)
rack-attack (4.4.1)
......@@ -662,17 +659,17 @@ GEM
rack
rack-test (0.6.3)
rack (>= 1.0)
rails (5.0.6)
actioncable (= 5.0.6)
actionmailer (= 5.0.6)
actionpack (= 5.0.6)
actionview (= 5.0.6)
activejob (= 5.0.6)
activemodel (= 5.0.6)
activerecord (= 5.0.6)
activesupport (= 5.0.6)
rails (5.0.7)
actioncable (= 5.0.7)
actionmailer (= 5.0.7)
actionpack (= 5.0.7)
actionview (= 5.0.7)
activejob (= 5.0.7)
activemodel (= 5.0.7)
activerecord (= 5.0.7)
activesupport (= 5.0.7)
bundler (>= 1.3.0)
railties (= 5.0.6)
railties (= 5.0.7)
sprockets-rails (>= 2.0.0)
rails-controller-testing (1.0.2)
actionpack (~> 5.x, >= 5.0.1)
......@@ -683,21 +680,21 @@ GEM
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
nokogiri (>= 1.6)
rails-html-sanitizer (1.0.3)
loofah (~> 2.0)
rails-html-sanitizer (1.0.4)
loofah (~> 2.2, >= 2.2.2)
rails-i18n (5.1.1)
i18n (>= 0.7, < 2)
railties (>= 5.0, < 6)
railties (5.0.6)
actionpack (= 5.0.6)
activesupport (= 5.0.6)
railties (5.0.7)
actionpack (= 5.0.7)
activesupport (= 5.0.7)
method_source
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
rainbow (2.2.2)
rake
raindrops (0.19.0)
rake (12.3.0)
rake (12.3.1)
rb-fsevent (0.10.3)
rb-inotify (0.9.10)
ffi (>= 0.5.0, < 2)
......@@ -1001,7 +998,7 @@ DEPENDENCIES
asana (~> 0.6.0)
asciidoctor (~> 1.5.6)
asciidoctor-plantuml (= 0.0.8)
asset_sync (~> 2.2.0)
asset_sync (~> 2.4)
attr_encrypted (~> 3.1.0)
awesome_print (~> 1.2.0)
babosa (~> 1.0.2)
......@@ -1027,12 +1024,11 @@ DEPENDENCIES
concurrent-ruby (~> 1.0.5)
connection_pool (~> 2.0)
creole (~> 0.5.0)
d3_rails (~> 3.5.0)
database_cleaner (~> 1.5.0)
deckar01-task_list (= 2.0.0)
default_value_for (~> 3.0.5)
device_detector
devise (~> 4.2)
devise (~> 4.4)
devise-two-factor (~> 3.0.0)
diffy (~> 3.1.0)
doorkeeper (~> 4.3)
......@@ -1063,7 +1059,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3)
gitaly-proto (~> 0.97.0)
gitaly-proto (~> 0.99.0)
github-linguist (~> 5.3.3)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-gollum-lib (~> 4.2)
......@@ -1071,12 +1067,12 @@ DEPENDENCIES
gitlab-markup (~> 1.6.2)
gitlab-styles (~> 2.3)
gitlab_omniauth-ldap (~> 2.0.4)
gon (~> 6.1.0)
gon (~> 6.2)
google-api-client (~> 0.19.8)
google-protobuf (= 3.5.1)
gpgme
grape (~> 1.0)
grape-entity (~> 0.6.0)
grape-entity (~> 0.7.1)
grape-route-helpers (~> 2.1.0)
grape_logging (~> 1.7)
grpc (~> 1.11.0)
......@@ -1117,7 +1113,7 @@ DEPENDENCIES
omniauth-azure-oauth2 (~> 0.0.9)
omniauth-cas3 (~> 1.1.4)
omniauth-facebook (~> 4.0.0)
omniauth-github (~> 1.1.1)
omniauth-github (~> 1.3)
omniauth-gitlab (~> 1.0.2)
omniauth-google-oauth2 (~> 0.5.3)
omniauth-kerberos (~> 0.3.0)
......@@ -1136,14 +1132,14 @@ DEPENDENCIES
peek-sidekiq (~> 1.0.3)
pg (~> 0.18.2)
premailer-rails (~> 1.9.7)
prometheus-client-mmap (~> 0.9.1)
prometheus-client-mmap (~> 0.9.2)
pry-byebug (~> 3.4.1)
pry-rails (~> 0.3.4)
rack-attack (~> 4.4.1)
rack-cors (~> 1.0.0)
rack-oauth2 (~> 1.2.1)
rack-proxy (~> 0.6.0)
rails (= 5.0.6)
rails (= 5.0.7)
rails-controller-testing
rails-deprecated_sanitizer (~> 1.0.3)
rails-i18n (~> 5.1)
......
......@@ -140,7 +140,7 @@ export default {
this.file.staged && this.file.key.indexOf('unstaged-') === 0 ? head : null,
);
if (this.viewer === viewerTypes.mr) {
if (this.viewer === viewerTypes.mr && this.file.mrChange) {
this.editor.attachMergeRequestModel(this.model);
} else {
this.editor.attachModel(this.model);
......
......@@ -44,6 +44,7 @@ export const dataStructure = () => ({
size: 0,
parentPath: null,
lastOpenedAt: 0,
mrChange: null,
});
export const decorateData = entity => {
......
<script>
/* eslint-disable no-alert */
import eventHub from '../event_hub';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import icon from '../../vue_shared/components/icon.vue';
import tooltip from '../../vue_shared/directives/tooltip';
export default {
directives: {
tooltip,
},
components: {
loadingIcon,
icon,
},
props: {
endpoint: {
type: String,
required: true,
},
title: {
type: String,
required: true,
},
icon: {
type: String,
required: true,
},
cssClass: {
type: String,
required: true,
},
pipelineId: {
type: Number,
required: true,
},
type: {
type: String,
required: true,
},
},
data() {
return {
isLoading: false,
};
},
computed: {
buttonClass() {
return `btn ${this.cssClass}`;
},
},
created() {
// We're using eventHub to listen to the modal here instead of
// using props because it would would make the parent components
// much more complex to keep track of the loading state of each button
eventHub.$on('postAction', this.setLoading);
},
beforeDestroy() {
eventHub.$off('postAction', this.setLoading);
},
methods: {
onClick() {
eventHub.$emit('openConfirmationModal', {
pipelineId: this.pipelineId,
endpoint: this.endpoint,
type: this.type,
});
},
setLoading(endpoint) {
if (endpoint === this.endpoint) {
this.isLoading = true;
}
},
},
};
</script>
<template>
<button
v-tooltip
type="button"
@click="onClick"
:class="buttonClass"
:title="title"
:aria-label="title"
data-container="body"
data-placement="top"
:disabled="isLoading">
<icon
:name="icon"
/>
<loading-icon v-if="isLoading" />
</button>
</template>
<script>
import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue';
import Modal from '~/vue_shared/components/gl_modal.vue';
import { s__, sprintf } from '~/locale';
import pipelinesTableRowComponent from './pipelines_table_row.vue';
import PipelinesTableRowComponent from './pipelines_table_row.vue';
import eventHub from '../event_hub';
/**
......@@ -11,8 +11,8 @@
*/
export default {
components: {
pipelinesTableRowComponent,
DeprecatedModal,
PipelinesTableRowComponent,
Modal,
},
props: {
pipelines: {
......@@ -37,31 +37,19 @@
return {
pipelineId: '',
endpoint: '',
type: '',
};
},
computed: {
modalTitle() {
return this.type === 'stop' ?
sprintf(s__('Pipeline|Stop pipeline #%{pipelineId}?'), {
pipelineId: `'${this.pipelineId}'`,
}, false) :
sprintf(s__('Pipeline|Retry pipeline #%{pipelineId}?'), {
pipelineId: `'${this.pipelineId}'`,
return sprintf(s__('Pipeline|Stop pipeline #%{pipelineId}?'), {
pipelineId: `${this.pipelineId}`,
}, false);
},
modalText() {
return this.type === 'stop' ?
sprintf(s__('Pipeline|You’re about to stop pipeline %{pipelineId}.'), {
pipelineId: `<strong>#${this.pipelineId}</strong>`,
}, false) :
sprintf(s__('Pipeline|You’re about to retry pipeline %{pipelineId}.'), {
return sprintf(s__('Pipeline|You’re about to stop pipeline %{pipelineId}.'), {
pipelineId: `<strong>#${this.pipelineId}</strong>`,
}, false);
},
primaryButtonLabel() {
return this.type === 'stop' ? s__('Pipeline|Stop pipeline') : s__('Pipeline|Retry pipeline');
},
},
created() {
eventHub.$on('openConfirmationModal', this.setModalData);
......@@ -73,7 +61,6 @@
setModalData(data) {
this.pipelineId = data.pipelineId;
this.endpoint = data.endpoint;
this.type = data.type;
},
onSubmit() {
eventHub.$emit('postAction', this.endpoint);
......@@ -120,20 +107,16 @@
:auto-devops-help-path="autoDevopsHelpPath"
:view-type="viewType"
/>
<deprecated-modal
<modal
id="confirmation-modal"
:title="modalTitle"
:text="modalText"
kind="danger"
:primary-button-label="primaryButtonLabel"
:header-title-text="modalTitle"
footer-primary-button-variant="danger"
:footer-primary-button-text="s__('Pipeline|Stop pipeline')"
@submit="onSubmit"
>
<template
slot="body"
slot-scope="props"
>
<p v-html="props.text"></p>
</template>
</deprecated-modal>
<span v-html="modalText"></span>
</modal>
</div>
</template>
<script>
/* eslint-disable no-param-reassign */
import asyncButtonComponent from './async_button.vue';
import pipelinesActionsComponent from './pipelines_actions.vue';
import pipelinesArtifactsComponent from './pipelines_artifacts.vue';
import ciBadge from '../../vue_shared/components/ci_badge_link.vue';
import pipelineStage from './stage.vue';
import pipelineUrl from './pipeline_url.vue';
import pipelinesTimeago from './time_ago.vue';
import commitComponent from '../../vue_shared/components/commit.vue';
import eventHub from '../event_hub';
import PipelinesActionsComponent from './pipelines_actions.vue';
import PipelinesArtifactsComponent from './pipelines_artifacts.vue';
import CiBadge from '../../vue_shared/components/ci_badge_link.vue';
import PipelineStage from './stage.vue';
import PipelineUrl from './pipeline_url.vue';
import PipelinesTimeago from './time_ago.vue';
import CommitComponent from '../../vue_shared/components/commit.vue';
import LoadingButton from '../../vue_shared/components/loading_button.vue';
import Icon from '../../vue_shared/components/icon.vue';
/**
* Pipeline table row.
......@@ -16,14 +17,15 @@
*/
export default {
components: {
asyncButtonComponent,
pipelinesActionsComponent,
pipelinesArtifactsComponent,
commitComponent,
pipelineStage,
pipelineUrl,
ciBadge,
pipelinesTimeago,
PipelinesActionsComponent,
PipelinesArtifactsComponent,
CommitComponent,
PipelineStage,
PipelineUrl,
CiBadge,
PipelinesTimeago,
LoadingButton,
Icon,
},
props: {
pipeline: {
......@@ -44,6 +46,12 @@
required: true,
},
},
data() {
return {
isRetrying: false,
isCancelling: false,
};
},
computed: {
/**
* If provided, returns the commit tag.
......@@ -119,8 +127,10 @@
if (this.pipeline.ref) {
return Object.keys(this.pipeline.ref).reduce((accumulator, prop) => {
if (prop === 'path') {
// eslint-disable-next-line no-param-reassign
accumulator.ref_url = this.pipeline.ref[prop];
} else {
// eslint-disable-next-line no-param-reassign
accumulator[prop] = this.pipeline.ref[prop];
}
return accumulator;
......@@ -216,6 +226,21 @@
return this.viewType === 'child';
},
},
methods: {
handleCancelClick() {
this.isCancelling = true;
eventHub.$emit('openConfirmationModal', {
pipelineId: this.pipeline.id,
endpoint: this.pipeline.cancel_path,
});
},
handleRetryClick() {
this.isRetrying = true;
eventHub.$emit('retryPipeline', this.pipeline.retry_path);
},
},
};
</script>
<template>
......@@ -287,7 +312,8 @@
<div
v-if="displayPipelineActions"
class="table-section section-20 table-button-footer pipeline-actions">
class="table-section section-20 table-button-footer pipeline-actions"
>
<div class="btn-group table-action-buttons">
<pipelines-actions-component
v-if="pipeline.details.manual_actions.length"
......@@ -300,29 +326,27 @@
:artifacts="pipeline.details.artifacts"
/>
<async-button-component
<loading-button
v-if="pipeline.flags.retryable"
:endpoint="pipeline.retry_path"
css-class="js-pipelines-retry-button btn-default btn-retry"
title="Retry"
icon="repeat"
:pipeline-id="pipeline.id"
data-toggle="modal"
data-target="#confirmation-modal"
type="retry"
/>
@click="handleRetryClick"
container-class="js-pipelines-retry-button btn btn-default btn-retry"
:loading="isRetrying"
:disabled="isRetrying"
>
<icon name="repeat" />
</loading-button>
<async-button-component
<loading-button
v-if="pipeline.flags.cancelable"
:endpoint="pipeline.cancel_path"
css-class="js-pipelines-cancel-button btn-remove"
title="Stop"
icon="close"
:pipeline-id="pipeline.id"
@click="handleCancelClick"
data-toggle="modal"
data-target="#confirmation-modal"
type="stop"
/>
container-class="js-pipelines-cancel-button btn btn-remove"
:loading="isCancelling"
:disabled="isCancelling"
>
<icon name="close" />
</loading-button>
</div>
</div>
</div>
......
......@@ -53,10 +53,12 @@ export default {
});
eventHub.$on('postAction', this.postAction);
eventHub.$on('retryPipeline', this.postAction);
eventHub.$on('clickedDropdown', this.updateTable);
},
beforeDestroy() {
eventHub.$off('postAction', this.postAction);
eventHub.$off('retryPipeline', this.postAction);
eventHub.$off('clickedDropdown', this.updateTable);
},
destroyed() {
......
......@@ -111,7 +111,13 @@
</td>
<td>
<span
v-tooltip
:title="tooltipTitle(item.createdAt)"
data-placement="bottom"
>
{{ timeFormated(item.createdAt) }}
</span>
</td>
<td class="content">
......
......@@ -15,7 +15,7 @@ export default class UserCallout {
init() {
if (!this.isCalloutDismissed || this.isCalloutDismissed === 'false') {
$('.js-close-callout').on('click', e => this.dismissCallout(e));
this.userCalloutBody.find('.js-close-callout').on('click', e => this.dismissCallout(e));
}
}
......@@ -23,12 +23,15 @@ export default class UserCallout {
const $currentTarget = $(e.currentTarget);
if (this.options.setCalloutPerProject) {
Cookies.set(this.cookieName, 'true', { expires: 365, path: this.userCalloutBody.data('projectPath') });
Cookies.set(this.cookieName, 'true', {
expires: 365,
path: this.userCalloutBody.data('projectPath'),
});
} else {
Cookies.set(this.cookieName, 'true', { expires: 365 });
}
if ($currentTarget.hasClass('close')) {
if ($currentTarget.hasClass('close') || $currentTarget.hasClass('js-close')) {
this.userCalloutBody.remove();
}
}
......
......@@ -109,12 +109,12 @@ export default {
rel="noopener noreferrer nofollow"
class="deploy-link js-deploy-url"
>
{{ deployment.external_url_formatted }}
<i
class="fa fa-external-link"
aria-hidden="true"
>
</i>
{{ deployment.external_url_formatted }}
</a>
</template>
<span
......
......@@ -70,12 +70,14 @@
/>
</transition>
<transition name="fade">
<slot>
<span
v-if="label"
class="js-loading-button-label"
>
{{ label }}
</span>
</slot>
</transition>
</button>
</template>
......@@ -17,6 +17,7 @@
display: flex;
align-items: center;
justify-content: space-between;
line-height: $line-height-base;
.title {
display: flex;
......@@ -33,10 +34,14 @@
.navbar-collapse {
padding-right: 0;
.navbar-nav {
margin: 0;
}
}
.nav li a {
color: $theme-gray-700;
.nav li {
float: none;
}
}
......
......@@ -13,8 +13,7 @@ class ApplicationController < ActionController::Base
before_action :authenticate_sessionless_user!
before_action :authenticate_user!
before_action :enforce_terms!, if: -> { Gitlab::CurrentSettings.current_application_settings.enforce_terms },
unless: :peek_request?
before_action :enforce_terms!, if: :should_enforce_terms?
before_action :validate_user_service_ticket!
before_action :check_password_expiration
before_action :ldap_security_check
......@@ -373,4 +372,10 @@ class ApplicationController < ActionController::Base
def peek_request?
request.path.start_with?('/-/peek')
end
def should_enforce_terms?
return false unless Gitlab::CurrentSettings.current_application_settings.enforce_terms
!(peek_request? || devise_controller?)
end
end
......@@ -11,13 +11,20 @@ class Groups::GroupMembersController < Groups::ApplicationController
:override
def index
can_manage_members = can?(current_user, :admin_group_member, @group)
@sort = params[:sort].presence || sort_value_name
@project = @group.projects.find(params[:project_id]) if params[:project_id]
@members = GroupMembersFinder.new(@group).execute
@members = @members.non_invite unless can?(current_user, :admin_group, @group)
@members = @members.non_invite unless can_manage_members
@members = @members.search(params[:search]) if params[:search].present?
@members = @members.sort_by_attribute(@sort)
if can_manage_members && params[:two_factor].present?
@members = @members.filter_by_2fa(params[:two_factor])
end
@members = @members.page(params[:page]).per(50)
@members = present_members(@members.includes(:user))
......
......@@ -3,6 +3,10 @@ module Users
include InternalRedirect
skip_before_action :enforce_terms!
skip_before_action :check_password_expiration
skip_before_action :check_two_factor_requirement
skip_before_action :require_email
before_action :terms
layout 'terms'
......
......@@ -42,22 +42,11 @@ module UsersHelper
items << :sign_out if current_user
# TODO: Remove these conditions when the permissions are prevented in
# https://gitlab.com/gitlab-org/gitlab-ce/issues/45849
terms_not_enforced = !Gitlab::CurrentSettings
.current_application_settings
.enforce_terms?
required_terms_accepted = terms_not_enforced || current_user.terms_accepted?
return items if current_user&.required_terms_not_accepted?
items << :help if required_terms_accepted
if can?(current_user, :read_user, current_user) && required_terms_accepted
items << :profile
end
if can?(current_user, :update_user, current_user) && required_terms_accepted
items << :settings
end
items << :help
items << :profile if can?(current_user, :read_user, current_user)
items << :settings if can?(current_user, :update_user, current_user)
items
end
......
......@@ -108,7 +108,13 @@ module Ci
end
def assign_to(project, current_user = nil)
if shared?
self.is_shared = false if shared?
self.runner_type = :project_type
elsif group_type?
raise ArgumentError, 'Transitioning a group runner to a project runner is not supported'
end
self.save
project.runner_projects.create(runner_id: self.id)
end
......
......@@ -4,7 +4,9 @@ module Routable
extend ActiveSupport::Concern
included do
has_one :route, as: :source, autosave: true, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
# Remove `inverse_of: source` when upgraded to rails 5.2
# See https://github.com/rails/rails/pull/28808
has_one :route, as: :source, autosave: true, dependent: :destroy, inverse_of: :source # rubocop:disable Cop/ActiveRecordDependent
has_many :redirect_routes, as: :source, autosave: true, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
validates :route, presence: true
......
......@@ -96,6 +96,17 @@ class Member < ActiveRecord::Base
joins(:user).merge(User.search(query))
end
def filter_by_2fa(value)
case value
when 'enabled'
left_join_users.merge(User.with_two_factor_indistinct)
when 'disabled'
left_join_users.merge(User.without_two_factor)
else
all
end
end
def sort_by_attribute(method)
case method.to_s
when 'access_level_asc' then reorder(access_level: :asc)
......
......@@ -237,14 +237,18 @@ class User < ActiveRecord::Base
scope :order_recent_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('current_sign_in_at', 'DESC')) }
scope :order_oldest_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('current_sign_in_at', 'ASC')) }
def self.with_two_factor
def self.with_two_factor_indistinct
joins("LEFT OUTER JOIN u2f_registrations AS u2f ON u2f.user_id = users.id")
.where("u2f.id IS NOT NULL OR otp_required_for_login = ?", true).distinct(arel_table[:id])
.where("u2f.id IS NOT NULL OR users.otp_required_for_login = ?", true)
end
def self.with_two_factor
with_two_factor_indistinct.distinct(arel_table[:id])
end
def self.without_two_factor
joins("LEFT OUTER JOIN u2f_registrations AS u2f ON u2f.user_id = users.id")
.where("u2f.id IS NULL AND otp_required_for_login = ?", false)
.where("u2f.id IS NULL AND users.otp_required_for_login = ?", false)
end
#
......@@ -1193,6 +1197,11 @@ class User < ActiveRecord::Base
accepted_term_id.present?
end
def required_terms_not_accepted?
Gitlab::CurrentSettings.current_application_settings.enforce_terms? &&
!terms_accepted?
end
protected
# override, from Devise::Validatable
......
class GlobalPolicy < BasePolicy
desc "User is blocked"
with_options scope: :user, score: 0
condition(:blocked) { @user.blocked? }
condition(:blocked) { @user&.blocked? }
desc "User is an internal user"
with_options scope: :user, score: 0
condition(:internal) { @user.internal? }
condition(:internal) { @user&.internal? }
desc "User's access has been locked"
with_options scope: :user, score: 0
condition(:access_locked) { @user.access_locked? }
condition(:access_locked) { @user&.access_locked? }
condition(:can_create_fork, scope: :user) { @user.manageable_namespaces.any? { |namespace| @user.can?(:create_projects, namespace) } }
condition(:can_create_fork, scope: :user) { @user && @user.manageable_namespaces.any? { |namespace| @user.can?(:create_projects, namespace) } }
condition(:required_terms_not_accepted, scope: :user, score: 0) do
@user&.required_terms_not_accepted?
end
rule { anonymous }.policy do
prevent :log_in
prevent :access_api
prevent :access_git
prevent :receive_notifications
prevent :use_quick_actions
prevent :create_group
......@@ -38,6 +40,11 @@ class GlobalPolicy < BasePolicy
prevent :use_quick_actions
end
rule { required_terms_not_accepted }.policy do
prevent :access_api
prevent :access_git
end
rule { can_create_group }.policy do
enable :create_group
end
......
- page_title "Members"
- can_manage_members = can?(current_user, :admin_group_member, @group)
.project-members-page.prepend-top-default
%h4
Members
%hr
- if can?(current_user, :admin_group_member, @group)
- if can_manage_members
.project-members-new.append-bottom-default
%p.clearfix
Add new member to
......@@ -13,20 +14,23 @@
= render 'shared/members/requests', membership_source: @group, requesters: @requesters
.append-bottom-default.clearfix
.clearfix
%h5.member.existing-title
Existing members
= form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do
.panel.panel-default
.panel-heading.flex-project-members-panel
%span.flex-project-title
Members with access to
%strong= @group.name
%span.badge= @members.total_count
= form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form flex-project-members-form' do
.form-group
= search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false }
%button.member-search-btn{ type: "submit", "aria-label" => "Submit search" }
= icon("search")
- if can_manage_members
= render 'shared/members/filter_2fa_dropdown'
= render 'shared/members/sort_dropdown'
.panel.panel-default
.panel-heading
Members with access to
%strong= @group.name
%span.badge= @members.total_count
%ul.content-list.members-list
= render partial: 'shared/members/member', collection: @members, as: :member
= paginate @members, theme: 'gitlab'
......@@ -8,7 +8,7 @@
.top-area
= render 'shared/issuable/nav', type: :issues
.nav-controls
= link_to params.merge(rss_url_options), class: 'btn' do
= link_to safe_params.merge(rss_url_options), class: 'btn' do
= icon('rss')
%span.icon-label
Subscribe
......
......@@ -20,10 +20,10 @@
= brand_header_logo
- logo_text = brand_header_logo_type
- if logo_text.present?
%span.logo-text.hidden-xs.prepend-left-8
%span.logo-text.prepend-left-8
= logo_text
- if header_link?(:user_dropdown)
.navbar-collapse.collapse
.navbar-collapse
%ul.nav.navbar-nav
%li.header-user.dropdown
= link_to current_user, class: user_dropdown_class, data: { toggle: "dropdown" } do
......
- filter = params[:two_factor] || 'everyone'
- filter_options = { 'everyone' => 'Everyone', 'enabled' => 'Enabled', 'disabled' => 'Disabled' }
.dropdown.inline.member-filter-2fa-dropdown
= dropdown_toggle('2FA: ' + filter_options[filter], { toggle: 'dropdown' })
%ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-selectable
%li.dropdown-header
Filter by two-factor authentication
- filter_options.each do |value, title|
%li
= link_to filter_group_project_member_path(two_factor: value), class: ("is-active" if filter == value) do
= title
......@@ -20,6 +20,10 @@
%label.label.label-danger
%strong Blocked
- if user.two_factor_enabled?
%label.label.label-info
2FA
- if source.instance_of?(Group) && source != @group
&middot;
= link_to source.full_name, source, class: "member-group-link"
......
---
title: Moves MR widget external link icon to the right
merge_request: 18828
author: Jacopo Beschi @jacopo-beschi
type: changed
---
title: Remove modalbox confirmation when retrying a pipeline
merge_request: 18879
author:
type: changed
---
title: 46210 Display logo and user dropdown on mobile for terms page and fix styling
merge_request:
author:
type: fixed
---
title: 'Replace the `project/merge_requests/references.feature` spinach test with an rspec analog'
merge_request: 18794
author: '@blackst0ne'
type: other
---
title: Block access to the API & git for users that did not accept enforced Terms
of Service
merge_request: 18816
author:
type: other
---
title: Expand documentation for Runners API
merge_request: 16484
author:
type: other
---
title: Add 2FA filter to the group members page
merge_request: 18483
author:
type: changed
---
title: 'Add missing tooltip to creation date on container registry overview'
merge_request: 18767
author: Lars Greiss
type: fixed
---
title: Finding a wiki page is done by Gitaly by default
merge_request:
author:
type: other
......@@ -26,17 +26,6 @@ def validate_storages_config
Gitlab.config.repositories.storages.each do |name, repository_storage|
storage_validation_error("\"#{name}\" is not a valid storage name") unless storage_name_valid?(name)
if repository_storage.is_a?(String)
raise "#{name} is not a valid storage, because it has no `path` key. " \
"It may be configured as:\n\n#{name}:\n path: #{repository_storage}\n\n" \
"For source installations, update your config/gitlab.yml Refer to gitlab.yml.example for an updated example.\n\n" \
"If you're using the Gitlab Development Kit, you can update your configuration running `gdk reconfigure`.\n"
end
if !repository_storage.is_a?(Gitlab::GitalyClient::StorageSettings) || repository_storage.legacy_disk_path.nil?
storage_validation_error("#{name} is not a valid storage, because it has no `path` key. Refer to gitlab.yml.example for an updated example")
end
%w(failure_count_threshold failure_reset_time storage_timeout).each do |setting|
# Falling back to the defaults is fine!
next if repository_storage[setting].nil?
......
......@@ -411,3 +411,86 @@ DELETE /projects/:id/runners/:runner_id
```
curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/9/runners/9"
```
## Register a new Runner
Register a new Runner for the instance.
```
POST /runners
```
| Attribute | Type | Required | Description |
|-------------|---------|----------|---------------------|
| `token` | string | yes | Registration token ([Read how to obtain a token](../ci/runners/README.md)) |
| `description`| string | no | Runner's description|
| `info` | hash | no | Runner's metadata |
| `active` | boolean| no | Whether the Runner is active |
| `locked` | boolean| no | Whether the Runner should be locked for current project |
| `run_untagged` | boolean | no | Whether the Runner should handle untagged jobs |
| `tag_list` | Array[String] | no | List of Runner's tags |
| `maximum_timeout` | integer | no | Maximum timeout set when this Runner will handle the job |
```
curl --request POST "https://gitlab.example.com/api/v4/runners" --form "token=ipzXrMhuyyJPifUt6ANz" --form "description=test-1-20150125-test" --form "tag_list=ruby,mysql,tag1,tag2"
```
Response:
| Status | Description |
|-----------|---------------------------------|
| 201 | Runner was created |
Example response:
```json
{
"id": "12345",
"token": "6337ff461c94fd3fa32ba3b1ff4125"
}
```
## Delete a registered Runner
Deletes a registed Runner.
```
DELETE /runners
```
| Attribute | Type | Required | Description |
|-------------|---------|----------|---------------------|
| `token` | string | yes | Runner's authentication token |
```
curl --request DELETE "https://gitlab.example.com/api/v4/runners" --form "token=ebb6fc00521627750c8bb750f2490e"
```
Response:
| Status | Description |
|-----------|---------------------------------|
| 204 | Runner was deleted |
## Verify authentication for a registered Runner
Validates authentication credentials for a registered Runner.
```
POST /runners/verify
```
| Attribute | Type | Required | Description |
|-------------|---------|----------|---------------------|
| `token` | string | yes | Runner's authentication token |
```
curl --request POST "https://gitlab.example.com/api/v4/runners/verify" --form "token=ebb6fc00521627750c8bb750f2490e"
```
Response:
| Status | Description |
|-----------|---------------------------------|
| 200 | Credentials are valid |
| 403 | Credentials are invalid |
......@@ -968,7 +968,7 @@ Group Chat Software
Set Microsoft Teams service for a project.
```
PUT /projects/:id/services/microsoft_teams
PUT /projects/:id/services/microsoft-teams
```
Parameters:
......@@ -982,7 +982,7 @@ Parameters:
Delete Microsoft Teams service for a project.
```
DELETE /projects/:id/services/microsoft_teams
DELETE /projects/:id/services/microsoft-teams
```
### Get Microsoft Teams service settings
......@@ -990,7 +990,7 @@ DELETE /projects/:id/services/microsoft_teams
Get Microsoft Teams service settings for a project.
```
GET /projects/:id/services/microsoft_teams
GET /projects/:id/services/microsoft-teams
```
## Mattermost notifications
......
......@@ -129,8 +129,8 @@ You may see a temporary error message `SchedulerPredicates failed due to Persist
Add the GitLab Helm repository and initialize Helm:
```bash
helm repo add gitlab https://charts.gitlab.io
helm init
helm repo add gitlab https://charts.gitlab.io
```
Once you have reviewed the [configuration settings](#configuring-and-installing-gitlab) you can install the chart. We recommending saving your configuration options in a `values.yaml` file for easier upgrades in the future.
......
@project_merge_requests
Feature: Project Merge Requests References
Background:
Given I sign in as "John Doe"
And public project "Community"
And "John Doe" owns public project "Community"
And project "Community" has "Community fix" open merge request
And I logout
And I sign in as "Mary Jane"
And private project "Enterprise"
And "Mary Jane" owns private project "Enterprise"
And project "Enterprise" has "Enterprise issue" open issue
And project "Enterprise" has "Enterprise fix" open merge request
And I visit issue page "Enterprise issue"
And I leave a comment referencing issue "Community fix"
And I visit merge request page "Enterprise fix"
And I leave a comment referencing issue "Community fix"
And I logout
@javascript
Scenario: Viewing the public issue as a "John Doe"
Given I sign in as "John Doe"
When I visit issue page "Community fix"
Then I should see no notes at all
@javascript
Scenario: Viewing the public issue as "Mary Jane"
Given I sign in as "Mary Jane"
When I visit issue page "Community fix"
And I should see a note linking to "Enterprise fix" merge request
And I should see a note linking to "Enterprise issue" issue
......@@ -22,22 +22,10 @@ module SharedAuthentication
sign_in(@user)
end
step 'I sign in as "John Doe"' do
gitlab_sign_in(user_exists("John Doe"))
end
step 'I sign in as "Mary Jane"' do
gitlab_sign_in(user_exists("Mary Jane"))
end
step 'I should be redirected to sign in page' do
expect(current_path).to eq new_user_session_path
end
step "I logout" do
gitlab_sign_out
end
step "I logout directly" do
gitlab_sign_out
end
......
......@@ -5,29 +5,6 @@ module SharedIssuable
find('.js-issuable-edit', visible: true).click
end
step 'project "Community" has "Community fix" open merge request' do
create_issuable_for_project(
project_name: 'Community',
type: :merge_request,
title: 'Community fix'
)
end
step 'project "Enterprise" has "Enterprise issue" open issue' do
create_issuable_for_project(
project_name: 'Enterprise',
title: 'Enterprise issue'
)
end
step 'project "Enterprise" has "Enterprise fix" open merge request' do
create_issuable_for_project(
project_name: 'Enterprise',
type: :merge_request,
title: 'Enterprise fix'
)
end
step 'I leave a comment referencing issue "Community issue"' do
leave_reference_comment(
issuable: Issue.find_by(title: 'Community issue'),
......@@ -35,44 +12,6 @@ module SharedIssuable
)
end
step 'I leave a comment referencing issue "Community fix"' do
leave_reference_comment(
issuable: MergeRequest.find_by(title: 'Community fix'),
from_project_name: 'Enterprise'
)
end
step 'I visit issue page "Enterprise issue"' do
issue = Issue.find_by(title: 'Enterprise issue')
visit project_issue_path(issue.project, issue)
end
step 'I visit merge request page "Enterprise fix"' do
mr = MergeRequest.find_by(title: 'Enterprise fix')
visit project_merge_request_path(mr.target_project, mr)
end
step 'I visit issue page "Community fix"' do
mr = MergeRequest.find_by(title: 'Community fix')
visit project_merge_request_path(mr.target_project, mr)
end
step 'I should see a note linking to "Enterprise fix" merge request' do
visible_note(
issuable: MergeRequest.find_by(title: 'Enterprise fix'),
from_project_name: 'Community',
user_name: 'Mary Jane'
)
end
step 'I should see a note linking to "Enterprise issue" issue' do
visible_note(
issuable: Issue.find_by(title: 'Enterprise issue'),
from_project_name: 'Community',
user_name: 'Mary Jane'
)
end
step 'I click link "Edit" for the merge request' do
edit_issuable
end
......
......@@ -18,8 +18,4 @@ module SharedNote
expect(find('.js-md-preview')).to have_content('Nothing to preview.')
end
end
step 'I should see no notes at all' do
expect(page).not_to have_css('.note')
end
end
......@@ -42,10 +42,6 @@ module SharedProject
# Visibility level
# ----------------------------------------
step 'private project "Enterprise"' do
create(:project, :private, :repository, name: 'Enterprise')
end
step 'I should see project "Enterprise"' do
expect(page).to have_content "Enterprise"
end
......@@ -70,10 +66,6 @@ module SharedProject
end
end
step 'public project "Community"' do
create(:project, :public, :repository, name: 'Community')
end
step 'I should see project "Community"' do
expect(page).to have_content "Community"
end
......@@ -89,13 +81,6 @@ module SharedProject
)
end
step '"Mary Jane" owns private project "Enterprise"' do
user_owns_project(
user_name: 'Mary Jane',
project_name: 'Enterprise'
)
end
step '"John Doe" owns internal project "Internal"' do
user_owns_project(
user_name: 'John Doe',
......@@ -104,14 +89,6 @@ module SharedProject
)
end
step '"John Doe" owns public project "Community"' do
user_owns_project(
user_name: 'John Doe',
project_name: 'Community',
visibility: :public
)
end
step 'public empty project "Empty Public Project"' do
create :project_empty_repo, :public, name: "Empty Public Project"
end
......
......@@ -45,7 +45,9 @@ module API
user = find_user_from_sources
return unless user
forbidden!('User is blocked') unless Gitlab::UserAccess.new(user).allowed? && user.can?(:access_api)
unless api_access_allowed?(user)
forbidden!(api_access_denied_message(user))
end
user
end
......@@ -72,6 +74,14 @@ module API
end
end
end
def api_access_allowed?(user)
Gitlab::UserAccess.new(user).allowed? && user.can?(:access_api)
end
def api_access_denied_message(user)
Gitlab::Auth::UserAccessDeniedReason.new(user).rejection_message
end
end
module ClassMethods
......
module Gitlab
module Auth
class UserAccessDeniedReason
def initialize(user)
@user = user
end
def rejection_message
case rejection_type
when :internal
'This action cannot be performed by internal users'
when :terms_not_accepted
'You must accept the Terms of Service in order to perform this action. '\
'Please access GitLab from a web browser to accept these terms.'
else
'Your account has been blocked.'
end
end
private
def rejection_type
if @user.internal?
:internal
elsif @user.required_terms_not_accepted?
:terms_not_accepted
else
:blocked
end
end
end
end
end
module Gitlab
class BuildAccess < UserAccess
attr_accessor :user, :project
# This bypasses the `can?(:access_git)`-check we normally do in `UserAccess`
# for CI. That way if a user was able to trigger a pipeline, then the
# build is allowed to clone the project.
def can_access_git?
true
end
end
end
......@@ -579,11 +579,6 @@ module Gitlab
count_commits(from: from, to: to, **options)
end
# Counts the amount of commits between `from` and `to`.
def count_commits_between(from, to, options = {})
count_commits(from: from, to: to, **options)
end
# old_rev and new_rev are commit ID's
# the result of this method is an array of Gitlab::Git::RawDiffChange
def raw_changes_between(old_rev, new_rev)
......
......@@ -67,7 +67,8 @@ module Gitlab
end
def page(title:, version: nil, dir: nil)
@repository.gitaly_migrate(:wiki_find_page) do |is_enabled|
@repository.gitaly_migrate(:wiki_find_page,
status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
if is_enabled
gitaly_find_page(title: title, version: version, dir: dir)
else
......
......@@ -2,8 +2,6 @@
# class return an instance of `GitlabAccessStatus`
module Gitlab
class GitAccess
include Gitlab::Utils::StrongMemoize
UnauthorizedError = Class.new(StandardError)
NotFoundError = Class.new(StandardError)
ProjectCreationError = Class.new(StandardError)
......@@ -17,7 +15,6 @@ module Gitlab
deploy_key_upload: 'This deploy key does not have write access to this project.',
no_repo: 'A repository for this project does not exist yet.',
project_not_found: 'The project you were looking for could not be found.',
account_blocked: 'Your account has been blocked.',
command_not_allowed: "The command you're trying to execute is not allowed.",
upload_pack_disabled_over_http: 'Pulling over HTTP is not allowed.',
receive_pack_disabled_over_http: 'Pushing over HTTP is not allowed.',
......@@ -108,8 +105,11 @@ module Gitlab
end
def check_active_user!
if user && !user_access.allowed?
raise UnauthorizedError, ERROR_MESSAGES[:account_blocked]
return unless user
unless user_access.allowed?
message = Gitlab::Auth::UserAccessDeniedReason.new(user).rejection_message
raise UnauthorizedError, message
end
end
......@@ -340,6 +340,8 @@ module Gitlab
def user_access
@user_access ||= if ci?
CiAccess.new
elsif user && request_from_ci_build?
BuildAccess.new(user, project: project)
else
UserAccess.new(user, project: project)
end
......
......@@ -5,6 +5,14 @@ module Gitlab
# directly.
class StorageSettings
DirectPathAccessError = Class.new(StandardError)
InvalidConfigurationError = Class.new(StandardError)
INVALID_STORAGE_MESSAGE = <<~MSG.freeze
Storage is invalid because it has no `path` key.
For source installations, update your config/gitlab.yml Refer to gitlab.yml.example for an updated example.
If you're using the Gitlab Development Kit, you can update your configuration running `gdk reconfigure`.
MSG
# This class will give easily recognizable NoMethodErrors
Deprecated = Class.new
......@@ -12,7 +20,8 @@ module Gitlab
attr_reader :legacy_disk_path
def initialize(storage)
raise "expected a Hash, got a #{storage.class.name}" unless storage.is_a?(Hash)
raise InvalidConfigurationError, "expected a Hash, got a #{storage.class.name}" unless storage.is_a?(Hash)
raise InvalidConfigurationError, INVALID_STORAGE_MESSAGE unless storage.has_key?('path')
# Support a nil 'path' field because some of the circuit breaker tests use it.
@legacy_disk_path = File.expand_path(storage['path'], Rails.root) if storage['path']
......
......@@ -5,7 +5,7 @@ module Gitlab
def initialize(*collections, per_page: nil)
raise ArgumentError.new('Only 2 collections are supported') if collections.size != 2
@per_page = per_page || Kaminari.config.default_per_page
@per_page = (per_page || Kaminari.config.default_per_page).to_i
@first_collection, @second_collection = collections
end
......
......@@ -4,7 +4,8 @@ module Gitlab
def self.parse(repo_path)
wiki = false
project_path = strip_storage_path(repo_path.sub(/\.git\z/, ''), fail_on_not_found: false)
project_path = repo_path.sub(/\.git\z/, '').sub(%r{\A/}, '')
project, was_redirected = find_project(project_path)
if project_path.end_with?('.wiki') && project.nil?
......@@ -17,22 +18,6 @@ module Gitlab
[project, wiki, redirected_path]
end
def self.strip_storage_path(repo_path, fail_on_not_found: true)
result = repo_path
storage = Gitlab.config.repositories.storages.values.find do |params|
repo_path.start_with?(params.legacy_disk_path)
end
if storage
result = result.sub(storage.legacy_disk_path, '')
elsif fail_on_not_found
raise NotFoundError.new("No known storage path matches #{repo_path.inspect}")
end
result.sub(%r{\A/*}, '')
end
def self.find_project(project_path)
project = Project.find_by_full_path(project_path, follow_redirects: true)
was_redirected = project && project.full_path.casecmp(project_path) != 0
......
......@@ -490,43 +490,43 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
id: job.id
end
context 'when job has a trace artifact' do
context "when job has a trace artifact" do
let(:job) { create(:ci_build, :trace_artifact, pipeline: pipeline) }
it 'returns a trace' do
response = subject
expect(response).to have_gitlab_http_status(:ok)
expect(response.content_type).to eq 'text/plain; charset=utf-8'
expect(response.body).to eq job.job_artifacts_trace.open.read
expect(response.headers["Content-Type"]).to eq("text/plain; charset=utf-8")
expect(response.body).to eq(job.job_artifacts_trace.open.read)
end
end
context 'when job has a trace file' do
context "when job has a trace file" do
let(:job) { create(:ci_build, :trace_live, pipeline: pipeline) }
it 'send a trace file' do
it "send a trace file" do
response = subject
expect(response).to have_gitlab_http_status(:ok)
expect(response.content_type).to eq 'text/plain; charset=utf-8'
expect(response.body).to eq 'BUILD TRACE'
expect(response.headers["Content-Type"]).to eq("text/plain; charset=utf-8")
expect(response.body).to eq("BUILD TRACE")
end
end
context 'when job has a trace in database' do
context "when job has a trace in database" do
let(:job) { create(:ci_build, pipeline: pipeline) }
before do
job.update_column(:trace, 'Sample trace')
job.update_column(:trace, "Sample trace")
end
it 'send a trace file' do
it "send a trace file" do
response = subject
expect(response).to have_gitlab_http_status(:ok)
expect(response.content_type).to eq 'text/plain; charset=utf-8'
expect(response.body).to eq 'Sample trace'
expect(response.headers["Content-Type"]).to eq("text/plain; charset=utf-8")
expect(response.body).to eq("Sample trace")
end
end
......
require 'spec_helper'
feature 'Groups > Members > Filter members' do
let(:user) { create(:user) }
let(:user_with_2fa) { create(:user, :two_factor_via_otp) }
let(:group) { create(:group) }
background do
group.add_owner(user)
group.add_master(user_with_2fa)
sign_in(user)
end
scenario 'shows all members' do
visit_members_list
expect(first_member).to include(user.name)
expect(second_member).to include(user_with_2fa.name)
expect(page).to have_css('.member-filter-2fa-dropdown .dropdown-toggle-text', text: '2FA: Everyone')
end
scenario 'shows only 2FA members' do
visit_members_list(two_factor: 'enabled')
expect(first_member).to include(user_with_2fa.name)
expect(members_list.size).to eq(1)
expect(page).to have_css('.member-filter-2fa-dropdown .dropdown-toggle-text', text: '2FA: Enabled')
end
scenario 'shows only non 2FA members' do
visit_members_list(two_factor: 'disabled')
expect(first_member).to include(user.name)
expect(members_list.size).to eq(1)
expect(page).to have_css('.member-filter-2fa-dropdown .dropdown-toggle-text', text: '2FA: Disabled')
end
def visit_members_list(options = {})
visit group_group_members_path(group.to_param, options)
end
def members_list
page.all('ul.content-list > li')
end
def first_member
members_list.first.text
end
def second_member
members_list.last.text
end
end
......@@ -10,6 +10,7 @@ describe "Internal references", :js do
let(:public_project_user) { public_project.owner }
let(:public_project) { create(:project, :public, :repository) }
let(:public_project_issue) { create(:issue, project: public_project) }
let(:public_project_merge_request) { create(:merge_request, source_project: public_project) }
context "when referencing to open issue" do
context "from private project" do
......@@ -77,4 +78,63 @@ describe "Internal references", :js do
end
end
end
context "when referencing to open merge request" do
context "from private project" do
context "from issue" do
before do
sign_in(private_project_user)
visit(project_issue_path(private_project, private_project_issue))
add_note("##{public_project_merge_request.to_reference(private_project)}")
end
context "when user doesn't have access to private project" do
before do
sign_in(public_project_user)
visit(project_merge_request_path(public_project, public_project_merge_request))
end
it { expect(page).not_to have_css(".note") }
end
end
context "from merge request" do
before do
sign_in(private_project_user)
visit(project_merge_request_path(private_project, private_project_merge_request))
add_note("##{public_project_merge_request.to_reference(private_project)}")
end
context "when user doesn't have access to private project" do
before do
sign_in(public_project_user)
visit(project_merge_request_path(public_project, public_project_merge_request))
end
it "doesn't show any references" do
page.within(".merge-request-details") do
expect(page).not_to have_content("#merge-requests .merge-requests-title")
end
end
end
context "when user has access to private project" do
before do
visit(project_merge_request_path(public_project, public_project_merge_request))
end
it "shows references" do
expect(page).to have_content("mentioned in merge request #{private_project_merge_request.to_reference(public_project)}")
.and have_content(private_project_user.name)
end
end
end
end
end
end
......@@ -125,7 +125,7 @@ describe 'Pipelines', :js do
context 'when canceling' do
before do
find('.js-pipelines-cancel-button').click
find('.js-primary-button').click
find('.js-modal-primary-action').click
wait_for_requests
end
......@@ -156,7 +156,6 @@ describe 'Pipelines', :js do
context 'when retrying' do
before do
find('.js-pipelines-retry-button').click
find('.js-primary-button').click
wait_for_requests
end
......@@ -256,7 +255,7 @@ describe 'Pipelines', :js do
context 'when canceling' do
before do
find('.js-pipelines-cancel-button').click
find('.js-primary-button').click
find('.js-modal-primary-action').click
end
it 'indicates that pipeline was canceled' do
......
......@@ -437,5 +437,107 @@ feature 'Login' do
expect(current_path).to eq(root_path)
end
context 'when 2FA is required for the user' do
before do
group = create(:group, require_two_factor_authentication: true)
group.add_developer(user)
end
context 'when the user did not enable 2FA' do
it 'asks to set 2FA before asking to accept the terms' do
visit new_user_session_path
fill_in 'user_login', with: user.email
fill_in 'user_password', with: '12345678'
click_button 'Sign in'
expect_to_be_on_terms_page
click_button 'Accept terms'
expect(current_path).to eq(profile_two_factor_auth_path)
fill_in 'pin_code', with: user.reload.current_otp
click_button 'Register with two-factor app'
click_link 'Proceed'
expect(current_path).to eq(profile_account_path)
end
end
context 'when the user already enabled 2FA' do
before do
user.update!(otp_required_for_login: true,
otp_secret: User.generate_otp_secret(32))
end
it 'asks the user to accept the terms' do
visit new_user_session_path
fill_in 'user_login', with: user.email
fill_in 'user_password', with: '12345678'
click_button 'Sign in'
fill_in 'user_otp_attempt', with: user.reload.current_otp
click_button 'Verify code'
expect_to_be_on_terms_page
click_button 'Accept terms'
expect(current_path).to eq(root_path)
end
end
end
context 'when the users password is expired' do
before do
user.update!(password_expires_at: Time.parse('2018-05-08 11:29:46 UTC'))
end
it 'asks the user to accept the terms before setting a new password' do
visit new_user_session_path
fill_in 'user_login', with: user.email
fill_in 'user_password', with: '12345678'
click_button 'Sign in'
expect_to_be_on_terms_page
click_button 'Accept terms'
expect(current_path).to eq(new_profile_password_path)
fill_in 'user_current_password', with: '12345678'
fill_in 'user_password', with: 'new password'
fill_in 'user_password_confirmation', with: 'new password'
click_button 'Set new password'
expect(page).to have_content('Password successfully changed')
end
end
context 'when the user does not have an email configured' do
let(:user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'saml', email: 'temp-email-for-oauth-user@gitlab.localhost') }
before do
stub_omniauth_saml_config(enabled: true, auto_link_saml_user: true, allow_single_sign_on: ['saml'], providers: [mock_saml_config])
end
it 'asks the user to accept the terms before setting an email' do
gitlab_sign_in_via('saml', user, 'my-uid')
expect_to_be_on_terms_page
click_button 'Accept terms'
expect(current_path).to eq(profile_path)
fill_in 'Email', with: 'hello@world.com'
click_button 'Update profile settings'
expect(page).to have_content('Profile was successfully updated')
end
end
end
end
......@@ -81,4 +81,22 @@ describe 'Users > Terms' do
expect(find_field('issue_description').value).to eq("We don't want to lose what the user typed")
end
end
context 'when the terms are enforced' do
before do
enforce_terms
end
context 'signing out', :js do
it 'allows the user to sign out without a response' do
visit terms_path
find('.header-user-dropdown-toggle').click
click_link('Sign out')
expect(page).to have_content('Sign in')
expect(page).to have_content('Register')
end
end
end
end
......@@ -42,26 +42,6 @@ describe '6_validations' do
expect { validate_storages_config }.to raise_error('"name with spaces" is not a valid storage name. Please fix this in your gitlab.yml before starting GitLab.')
end
end
context 'with incomplete settings' do
before do
mock_storages('foo' => {})
end
it 'throws an error suggesting the user to update its settings' do
expect { validate_storages_config }.to raise_error('foo is not a valid storage, because it has no `path` key. Refer to gitlab.yml.example for an updated example. Please fix this in your gitlab.yml before starting GitLab.')
end
end
context 'with deprecated settings structure' do
before do
mock_storages('foo' => 'tmp/tests/paths/a/b/c')
end
it 'throws an error suggesting the user to update its settings' do
expect { validate_storages_config }.to raise_error("foo is not a valid storage, because it has no `path` key. It may be configured as:\n\nfoo:\n path: tmp/tests/paths/a/b/c\n\nFor source installations, update your config/gitlab.yml Refer to gitlab.yml.example for an updated example.\n\nIf you're using the Gitlab Development Kit, you can update your configuration running `gdk reconfigure`.\n")
end
end
end
describe 'validate_storages_paths' do
......
......@@ -24,7 +24,7 @@ describe('RepoEditor', () => {
f.active = true;
f.tempFile = true;
vm.$store.state.openFiles.push(f);
vm.$store.state.entries[f.path] = f;
Vue.set(vm.$store.state.entries, f.path, f);
vm.monaco = true;
vm.$mount();
......@@ -215,6 +215,30 @@ describe('RepoEditor', () => {
expect(vm.editor.attachModel).toHaveBeenCalledWith(vm.model);
});
it('attaches model to merge request editor', () => {
vm.$store.state.viewer = 'mrdiff';
vm.file.mrChange = true;
spyOn(vm.editor, 'attachMergeRequestModel');
Editor.editorInstance.modelManager.dispose();
vm.setupEditor();
expect(vm.editor.attachMergeRequestModel).toHaveBeenCalledWith(vm.model);
});
it('does not attach model to merge request editor when not a MR change', () => {
vm.$store.state.viewer = 'mrdiff';
vm.file.mrChange = false;
spyOn(vm.editor, 'attachMergeRequestModel');
Editor.editorInstance.modelManager.dispose();
vm.setupEditor();
expect(vm.editor.attachMergeRequestModel).not.toHaveBeenCalledWith(vm.model);
});
it('adds callback methods', () => {
spyOn(vm.editor, 'onPositionChange').and.callThrough();
......
import Vue from 'vue';
import asyncButtonComp from '~/pipelines/components/async_button.vue';
import eventHub from '~/pipelines/event_hub';
describe('Pipelines Async Button', () => {
let component;
let AsyncButtonComponent;
beforeEach(() => {
AsyncButtonComponent = Vue.extend(asyncButtonComp);
component = new AsyncButtonComponent({
propsData: {
endpoint: '/foo',
title: 'Foo',
icon: 'repeat',
cssClass: 'bar',
pipelineId: 123,
type: 'explode',
},
}).$mount();
});
it('should render a button', () => {
expect(component.$el.tagName).toEqual('BUTTON');
});
it('should render svg icon', () => {
expect(component.$el.querySelector('svg')).not.toBeNull();
});
it('should render the provided title', () => {
expect(component.$el.getAttribute('data-original-title')).toContain('Foo');
expect(component.$el.getAttribute('aria-label')).toContain('Foo');
});
it('should render the provided cssClass', () => {
expect(component.$el.getAttribute('class')).toContain('bar');
});
describe('With confirm dialog', () => {
it('should call the service when confimation is positive', () => {
eventHub.$on('openConfirmationModal', (data) => {
expect(data.pipelineId).toEqual(123);
expect(data.type).toEqual('explode');
});
component = new AsyncButtonComponent({
propsData: {
endpoint: '/foo',
title: 'Foo',
icon: 'fa fa-foo',
cssClass: 'bar',
pipelineId: 123,
type: 'explode',
},
}).$mount();
component.$el.click();
});
});
});
import Vue from 'vue';
import tableRowComp from '~/pipelines/components/pipelines_table_row.vue';
import eventHub from '~/pipelines/event_hub';
describe('Pipelines Table Row', () => {
const jsonFixtureName = 'pipelines/pipelines.json';
......@@ -151,13 +152,37 @@ describe('Pipelines Table Row', () => {
describe('actions column', () => {
beforeEach(() => {
component = buildComponent(pipeline);
const withActions = Object.assign({}, pipeline);
withActions.flags.cancelable = true;
withActions.flags.retryable = true;
withActions.cancel_path = '/cancel';
withActions.retry_path = '/retry';
component = buildComponent(withActions);
});
it('should render the provided actions', () => {
expect(
component.$el.querySelectorAll('.table-section:nth-child(6) ul li').length,
).toEqual(pipeline.details.manual_actions.length);
expect(component.$el.querySelector('.js-pipelines-retry-button')).not.toBeNull();
expect(component.$el.querySelector('.js-pipelines-cancel-button')).not.toBeNull();
});
it('emits `retryPipeline` event when retry button is clicked and toggles loading', () => {
eventHub.$on('retryPipeline', (endpoint) => {
expect(endpoint).toEqual('/retry');
});
component.$el.querySelector('.js-pipelines-retry-button').click();
expect(component.isRetrying).toEqual(true);
});
it('emits `openConfirmationModal` event when cancel button is clicked and toggles loading', () => {
eventHub.$on('openConfirmationModal', (data) => {
expect(data.endpoint).toEqual('/cancel');
expect(data.pipelineId).toEqual(pipeline.id);
});
component.$el.querySelector('.js-pipelines-cancel-button').click();
expect(component.isCancelling).toEqual(true);
});
});
});
require 'spec_helper'
describe Gitlab::Auth::UserAccessDeniedReason do
include TermsHelper
let(:user) { build(:user) }
let(:reason) { described_class.new(user) }
describe '#rejection_message' do
subject { reason.rejection_message }
context 'when a user is blocked' do
before do
user.block!
end
it { is_expected.to match /blocked/ }
end
context 'a user did not accept the enforced terms' do
before do
enforce_terms
end
it { is_expected.to match /You must accept the Terms of Service/ }
end
context 'when the user is internal' do
let(:user) { User.ghost }
it { is_expected.to match /This action cannot be performed by internal users/ }
end
end
end
require 'spec_helper'
describe Gitlab::BuildAccess do
let(:user) { create(:user) }
let(:project) { create(:project) }
describe '#can_do_action' do
subject { described_class.new(user, project: project).can_do_action?(:download_code) }
context 'when the user can do an action on the project but cannot access git' do
before do
user.block!
project.add_developer(user)
end
it { is_expected.to be(true) }
end
context 'when the user cannot do an action on the project' do
it { is_expected.to be(false) }
end
end
end
require 'spec_helper'
describe Gitlab::GitAccess do
set(:user) { create(:user) }
include TermsHelper
let(:user) { create(:user) }
let(:actor) { user }
let(:project) { create(:project, :repository) }
......@@ -1040,6 +1042,96 @@ describe Gitlab::GitAccess do
end
end
context 'terms are enforced' do
before do
enforce_terms
end
shared_examples 'access after accepting terms' do
let(:actions) do
[-> { pull_access_check },
-> { push_access_check }]
end
it 'blocks access when the user did not accept terms', :aggregate_failures do
actions.each do |action|
expect { action.call }.to raise_unauthorized(/You must accept the Terms of Service in order to perform this action/)
end
end
it 'allows access when the user accepted the terms', :aggregate_failures do
accept_terms(user)
actions.each do |action|
expect { action.call }.not_to raise_error
end
end
end
describe 'as an anonymous user to a public project' do
let(:actor) { nil }
let(:project) { create(:project, :public, :repository) }
it { expect { pull_access_check }.not_to raise_error }
end
describe 'as a guest to a public project' do
let(:project) { create(:project, :public, :repository) }
it_behaves_like 'access after accepting terms' do
let(:actions) { [-> { pull_access_check }] }
end
end
describe 'as a reporter to the project' do
before do
project.add_reporter(user)
end
it_behaves_like 'access after accepting terms' do
let(:actions) { [-> { pull_access_check }] }
end
end
describe 'as a developer of the project' do
before do
project.add_developer(user)
end
it_behaves_like 'access after accepting terms'
end
describe 'as a master of the project' do
before do
project.add_master(user)
end
it_behaves_like 'access after accepting terms'
end
describe 'as an owner of the project' do
let(:project) { create(:project, :repository, namespace: user.namespace) }
it_behaves_like 'access after accepting terms'
end
describe 'when a ci build clones the project' do
let(:protocol) { 'http' }
let(:authentication_abilities) { [:build_download_code] }
let(:auth_result_type) { :build }
before do
project.add_developer(user)
end
it "doesn't block http pull" do
aggregate_failures do
expect { pull_access_check }.not_to raise_error
end
end
end
end
private
def raise_unauthorized(message)
......
require 'spec_helper'
describe Gitlab::GitalyClient::StorageSettings do
describe "#initialize" do
context 'when the storage contains no path' do
it 'raises an error' do
expect do
described_class.new("foo" => {})
end.to raise_error(described_class::InvalidConfigurationError)
end
end
context "when the argument isn't a hash" do
it 'raises an error' do
expect do
described_class.new("test")
end.to raise_error("expected a Hash, got a String")
end
end
context 'when the storage is valid' do
it 'raises no error' do
expect do
described_class.new("path" => Rails.root)
end.not_to raise_error
end
end
end
end
......@@ -45,25 +45,6 @@ describe ::Gitlab::RepoPath do
end
end
describe '.strip_storage_path' do
before do
allow(Gitlab.config.repositories).to receive(:storages).and_return({
'storage1' => Gitlab::GitalyClient::StorageSettings.new('path' => '/foo'),
'storage2' => Gitlab::GitalyClient::StorageSettings.new('path' => '/bar')
})
end
it 'strips the storage path' do
expect(described_class.strip_storage_path('/bar/foo/qux/baz.git')).to eq('foo/qux/baz.git')
end
it 'raises NotFoundError if no storage matches the path' do
expect { described_class.strip_storage_path('/doesnotexist/foo.git') }.to raise_error(
described_class::NotFoundError
)
end
end
describe '.find_project' do
let(:project) { create(:project) }
let(:redirect) { project.route.create_redirect('foo/bar/baz') }
......
......@@ -200,15 +200,29 @@ describe Ci::Runner do
describe '#assign_to' do
let!(:project) { FactoryBot.create(:project) }
let!(:shared_runner) { FactoryBot.create(:ci_runner, :shared) }
before do
shared_runner.assign_to(project)
subject { runner.assign_to(project) }
context 'with shared_runner' do
let!(:runner) { FactoryBot.create(:ci_runner, :shared) }
it 'transitions shared runner to project runner and assigns project' do
subject
expect(runner).to be_specific
expect(runner).to be_project_type
expect(runner.projects).to eq([project])
expect(runner.only_for?(project)).to be_truthy
end
end
context 'with group runner' do
let!(:runner) { FactoryBot.create(:ci_runner, runner_type: :group_type) }
it { expect(shared_runner).to be_specific }
it { expect(shared_runner.projects).to eq([project]) }
it { expect(shared_runner.only_for?(project)).to be_truthy }
it 'raises an error' do
expect { subject }
.to raise_error(ArgumentError, 'Transitioning a group runner to a project runner is not supported')
end
end
end
describe '.online' do
......
......@@ -55,13 +55,9 @@ describe Clusters::Applications::Runner do
context 'without a runner' do
let(:project) { create(:project) }
let(:cluster) { create(:cluster) }
let(:cluster) { create(:cluster, projects: [project]) }
let(:gitlab_runner) { create(:clusters_applications_runner, cluster: cluster) }
before do
cluster.projects << project
end
it 'creates a runner' do
expect do
subject
......
......@@ -239,17 +239,19 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
it { is_expected.to be_nil }
end
context 'when kubernetes responds with valid pods' do
context 'when kubernetes responds with valid pods and deployments' do
before do
stub_kubeclient_pods
stub_kubeclient_deployments
end
it { is_expected.to eq(pods: [kube_pod]) }
it { is_expected.to include(pods: [kube_pod]) }
end
context 'when kubernetes responds with 500s' do
before do
stub_kubeclient_pods(status: 500)
stub_kubeclient_deployments(status: 500)
end
it { expect { subject }.to raise_error(Kubeclient::HttpError) }
......@@ -258,9 +260,10 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
context 'when kubernetes responds with 404s' do
before do
stub_kubeclient_pods(status: 404)
stub_kubeclient_deployments(status: 404)
end
it { is_expected.to eq(pods: []) }
it { is_expected.to include(pods: []) }
end
end
end
......@@ -2,6 +2,7 @@ require 'spec_helper'
describe User do
include ProjectForksHelper
include TermsHelper
describe 'modules' do
subject { described_class }
......@@ -2728,4 +2729,30 @@ describe User do
.to change { RedirectRoute.where(path: 'foo').count }.by(-1)
end
end
describe '#required_terms_not_accepted?' do
let(:user) { build(:user) }
subject { user.required_terms_not_accepted? }
context "when terms are not enforced" do
it { is_expected.to be_falsy }
end
context "when terms are enforced and accepted by the user" do
before do
enforce_terms
accept_terms(user)
end
it { is_expected.to be_falsy }
end
context "when terms are enforced but the user has not accepted" do
before do
enforce_terms
end
it { is_expected.to be_truthy }
end
end
end
......@@ -90,4 +90,94 @@ describe GlobalPolicy do
it { is_expected.to be_allowed(:update_custom_attribute) }
end
end
shared_examples 'access allowed when terms accepted' do |ability|
it { is_expected.not_to be_allowed(ability) }
it "allows #{ability} when the user accepted the terms" do
accept_terms(current_user)
is_expected.to be_allowed(ability)
end
end
describe 'API access' do
context 'regular user' do
it { is_expected.to be_allowed(:access_api) }
end
context 'admin' do
let(:current_user) { create(:admin) }
it { is_expected.to be_allowed(:access_api) }
end
context 'anonymous' do
let(:current_user) { nil }
it { is_expected.to be_allowed(:access_api) }
end
context 'when terms are enforced' do
before do
enforce_terms
end
context 'regular user' do
it_behaves_like 'access allowed when terms accepted', :access_api
end
context 'admin' do
let(:current_user) { create(:admin) }
it_behaves_like 'access allowed when terms accepted', :access_api
end
context 'anonymous' do
let(:current_user) { nil }
it { is_expected.to be_allowed(:access_api) }
end
end
end
describe 'git access' do
describe 'regular user' do
it { is_expected.to be_allowed(:access_git) }
end
describe 'admin' do
let(:current_user) { create(:admin) }
it { is_expected.to be_allowed(:access_git) }
end
describe 'anonymous' do
let(:current_user) { nil }
it { is_expected.to be_allowed(:access_git) }
end
context 'when terms are enforced' do
before do
enforce_terms
end
context 'regular user' do
it_behaves_like 'access allowed when terms accepted', :access_git
end
context 'admin' do
let(:current_user) { create(:admin) }
it_behaves_like 'access allowed when terms accepted', :access_git
end
context 'anonymous' do
let(:current_user) { nil }
it { is_expected.to be_allowed(:access_git) }
end
end
end
end
......@@ -6,6 +6,7 @@ describe API::Helpers do
include API::APIGuard::HelperMethods
include described_class
include SentryHelper
include TermsHelper
let(:user) { create(:user) }
let(:admin) { create(:admin) }
......@@ -163,6 +164,23 @@ describe API::Helpers do
expect { current_user }.to raise_error /403/
end
context 'when terms are enforced' do
before do
enforce_terms
env[Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token
end
it 'returns a 403 when a user has not accepted the terms' do
expect { current_user }.to raise_error /You must accept the Terms of Service/
end
it 'sets the current user when the user accepted the terms' do
accept_terms(user)
expect(current_user).to eq(user)
end
end
it "sets current_user" do
env[Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token
expect(current_user).to eq(user)
......
require "spec_helper"
describe 'Git HTTP requests' do
include TermsHelper
include GitHttpHelpers
include WorkhorseHelpers
include UserActivitiesHelpers
......@@ -824,4 +825,56 @@ describe 'Git HTTP requests' do
end
end
end
context 'when terms are enforced' do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
let(:path) { "#{project.full_path}.git" }
let(:env) { { user: user.username, password: user.password } }
before do
project.add_master(user)
enforce_terms
end
it 'blocks git access when the user did not accept terms', :aggregate_failures do
clone_get(path, env) do |response|
expect(response).to have_gitlab_http_status(:forbidden)
end
download(path, env) do |response|
expect(response).to have_gitlab_http_status(:forbidden)
end
upload(path, env) do |response|
expect(response).to have_gitlab_http_status(:forbidden)
end
end
context 'when the user accepted the terms' do
before do
accept_terms(user)
end
it 'allows clones' do
clone_get(path, env) do |response|
expect(response).to have_gitlab_http_status(:ok)
end
end
it_behaves_like 'pulls are allowed'
it_behaves_like 'pushes are allowed'
end
context 'from CI' do
let(:build) { create(:ci_build, :running) }
let(:env) { { user: 'gitlab-ci-token', password: build.token } }
before do
build.update!(user: user, project: project)
end
it_behaves_like 'pulls are allowed'
end
end
end
......@@ -9,8 +9,13 @@ module KubernetesHelpers
kube_response(kube_pods_body)
end
def kube_deployments_response
kube_response(kube_deployments_body)
end
def stub_kubeclient_discover(api_url)
WebMock.stub_request(:get, api_url + '/api/v1').to_return(kube_response(kube_v1_discovery_body))
WebMock.stub_request(:get, api_url + '/apis/extensions/v1beta1').to_return(kube_response(kube_v1beta1_discovery_body))
end
def stub_kubeclient_pods(response = nil)
......@@ -20,6 +25,13 @@ module KubernetesHelpers
WebMock.stub_request(:get, pods_url).to_return(response || kube_pods_response)
end
def stub_kubeclient_deployments(response = nil)
stub_kubeclient_discover(service.api_url)
deployments_url = service.api_url + "/apis/extensions/v1beta1/namespaces/#{service.actual_namespace}/deployments"
WebMock.stub_request(:get, deployments_url).to_return(response || kube_deployments_response)
end
def stub_kubeclient_get_secrets(api_url, **options)
WebMock.stub_request(:get, api_url + '/api/v1/secrets')
.to_return(kube_response(kube_v1_secrets_body(options)))
......@@ -53,6 +65,18 @@ module KubernetesHelpers
"kind" => "APIResourceList",
"resources" => [
{ "name" => "pods", "namespaced" => true, "kind" => "Pod" },
{ "name" => "deployments", "namespaced" => true, "kind" => "Deployment" },
{ "name" => "secrets", "namespaced" => true, "kind" => "Secret" }
]
}
end
def kube_v1beta1_discovery_body
{
"kind" => "APIResourceList",
"resources" => [
{ "name" => "pods", "namespaced" => true, "kind" => "Pod" },
{ "name" => "deployments", "namespaced" => true, "kind" => "Deployment" },
{ "name" => "secrets", "namespaced" => true, "kind" => "Secret" }
]
}
......@@ -65,14 +89,25 @@ module KubernetesHelpers
}
end
def kube_deployments_body
{
"kind" => "DeploymentList",
"items" => [kube_deployment]
}
end
# This is a partial response, it will have many more elements in reality but
# these are the ones we care about at the moment
def kube_pod(name: "kube-pod", app: "valid-pod-label")
def kube_pod(name: "kube-pod", app: "valid-pod-label", status: "Running", track: nil)
{
"metadata" => {
"name" => name,
"generate_name" => "generated-name-with-suffix",
"creationTimestamp" => "2016-11-25T19:55:19Z",
"labels" => { "app" => app }
"labels" => {
"app" => app,
"track" => track
}
},
"spec" => {
"containers" => [
......@@ -80,7 +115,27 @@ module KubernetesHelpers
{ "name" => "container-1" }
]
},
"status" => { "phase" => "Running" }
"status" => { "phase" => status }
}
end
def kube_deployment(name: "kube-deployment", app: "valid-deployment-label", track: nil)
{
"metadata" => {
"name" => name,
"generation" => 4,
"labels" => {
"app" => app,
"track" => track
}.compact
},
"spec" => { "replicas" => 3 },
"status" => {
"observedGeneration" => 4,
"replicas" => 3,
"updatedReplicas" => 3,
"availableReplicas" => 3
}
}
end
......@@ -101,4 +156,12 @@ module KubernetesHelpers
terminal
end
end
def kube_deployment_rollout_status
::Gitlab::Kubernetes::RolloutStatus.from_deployments(kube_deployment)
end
def empty_deployment_rollout_status
::Gitlab::Kubernetes::RolloutStatus.from_deployments()
end
end
......@@ -12,8 +12,10 @@
# AUTO_DEVOPS_DOMAIN must also be set as a variable at the group or project
# level, or manually added below.
#
# If you want to deploy to staging first, or enable canary deploys,
# uncomment the relevant jobs in the pipeline below.
# Continuous deployment to production is enabled by default.
# If you want to deploy to staging first, or enable incremental rollouts,
# set STAGING_ENABLED or INCREMENTAL_ROLLOUT_ENABLED environment variables.
# If you want to use canary deployments, uncomment the canary job.
#
# If Auto DevOps fails to detect the proper buildpack, or if you want to
# specify a custom buildpack, set a project variable `BUILDPACK_URL` to the
......@@ -88,14 +90,6 @@ codequality:
artifacts:
paths: [codeclimate.json]
license_management:
image: registry.gitlab.com/gitlab-org/security-products/license-management:latest
allow_failure: true
script:
- license_management
artifacts:
paths: [gl-license-report.json]
performance:
stage: performance
image: docker:stable
......@@ -223,8 +217,8 @@ stop_review:
# Staging deploys are disabled by default since
# continuous deployment to production is enabled by default
# If you prefer to automatically deploy to staging and
# only manually promote to production, enable this job by removing the dot (.),
# and uncomment the `when: manual` line in the `production` job.
# only manually promote to production, enable this job by setting
# STAGING_ENABLED.
staging:
stage: staging
......@@ -245,13 +239,9 @@ staging:
kubernetes: active
variables:
- $STAGING_ENABLED
except:
variables:
- $INCREMENTAL_ROLLOUT_ENABLED
# Canaries are disabled by default, but if you want them,
# and know what the downsides are, enable this job by removing the dot (.),
# and uncomment the `when: manual` line in the `production` job.
# and know what the downsides are, enable this job by removing the dot (.).
.canary:
stage: canary
......@@ -272,11 +262,6 @@ staging:
- master
kubernetes: active
# This job continuously deploys to production on every push to `master`.
# To make this a manual process, either because you're enabling `staging`
# or `canary` deploys, or you simply want more control over when you deploy
# to production, uncomment the `when: manual` line in the `production` job.
.production: &production_template
stage: production
script:
......@@ -310,6 +295,7 @@ production:
production_manual:
<<: *production_template
when: manual
allow_failure: false
only:
refs:
- master
......@@ -345,6 +331,7 @@ rollout 10%:
<<: *rollout_template
variables:
ROLLOUT_PERCENTAGE: 10
when: manual
only:
refs:
- master
......@@ -379,6 +366,7 @@ rollout 50%:
rollout 100%:
<<: *production_template
when: manual
allow_failure: false
only:
refs:
- master
......@@ -428,14 +416,6 @@ rollout 100%:
"registry.gitlab.com/gitlab-org/security-products/codequality:$SP_VERSION" /code
}
function license_management() {
if echo $GITLAB_FEATURES |grep license_management > /dev/null ; then
/run.sh .
else
echo "License management is not available in your subscription"
fi
}
function sast() {
case "$CI_SERVER_VERSION" in
*-ee)
......@@ -562,12 +542,14 @@ rollout 100%:
replicas=$(get_replicas "$track" "$percentage")
if [[ -n "$(helm ls -q "^$name$")" ]]; then
helm upgrade --reuse-values \
--wait \
--set replicaCount="$replicas" \
--namespace="$KUBE_NAMESPACE" \
"$name" \
chart/
fi
}
function install_dependencies() {
......
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