Commit 3c633844 authored by Stan Hu's avatar Stan Hu

Merge branch 'master' into sh-add-object-storage-qa

parents b041dc20 ba99dfcd
......@@ -4,7 +4,9 @@ entry.
## 11.2.3 (2018-08-28)
- No changes.
### Fixed (1 change)
- Fixed cache invalidation issue with diff lines from 11.2.2.
## 11.2.2 (2018-08-27)
......@@ -269,7 +271,9 @@ entry.
## 11.1.6 (2018-08-28)
- No changes.
### Fixed (1 change)
- Fixed cache invalidation issue with diff lines from 11.2.2.
## 11.1.5 (2018-08-27)
......
......@@ -195,6 +195,9 @@ gem 're2', '~> 1.1.1'
gem 'version_sorter', '~> 2.1.0'
# Export Ruby Regex to Javascript
gem 'js_regex', '~> 2.2.1'
# User agent parsing
gem 'device_detector'
......@@ -365,7 +368,7 @@ group :development, :test do
gem 'benchmark-ips', '~> 2.3.0', require: false
gem 'license_finder', '~> 3.1', require: false
gem 'license_finder', '~> 5.4', require: false
gem 'knapsack', '~> 1.16'
gem 'activerecord_sane_schema_dumper', gem_versions['activerecord_sane_schema_dumper']
......
......@@ -86,7 +86,6 @@ GEM
bindata (2.4.3)
binding_of_caller (0.7.2)
debug_inspector (>= 0.0.1)
blankslate (2.1.2.4)
bootsnap (1.3.1)
msgpack (~> 1.0)
bootstrap_form (2.7.0)
......@@ -428,6 +427,8 @@ GEM
multipart-post
oauth (~> 0.5, >= 0.5.0)
jquery-atwho-rails (1.3.2)
js_regex (2.2.1)
regexp_parser (>= 0.4.11, <= 0.5.0)
json (1.8.6)
json-jwt (1.9.4)
activesupport
......@@ -463,13 +464,12 @@ GEM
actionmailer (>= 3.2)
letter_opener (~> 1.0)
railties (>= 3.2)
license_finder (3.1.1)
license_finder (5.4.0)
bundler
httparty
rubyzip
thor
toml (= 0.1.2)
with_env (> 1.0)
toml (= 0.2.0)
with_env (= 1.1.0)
xml-simple
licensee (8.9.2)
rugged (~> 0.24)
......@@ -587,8 +587,7 @@ GEM
parallel (1.12.1)
parser (2.5.1.0)
ast (~> 2.4.0)
parslet (1.5.0)
blankslate (~> 2.0)
parslet (1.8.2)
path_expander (1.0.2)
peek (1.0.1)
concurrent-ruby (>= 0.9.0)
......@@ -726,6 +725,7 @@ GEM
redis-store (>= 1.2, < 2)
redis-store (1.4.1)
redis (>= 2.2, < 5)
regexp_parser (0.5.0)
representable (3.0.4)
declarative (< 0.1.0)
declarative-option (< 0.2.0)
......@@ -907,8 +907,8 @@ GEM
tilt (2.0.8)
timecop (0.8.1)
timfel-krb5-auth (0.8.3)
toml (0.1.2)
parslet (~> 1.5.0)
toml (0.2.0)
parslet (~> 1.8.0)
toml-rb (1.0.0)
citrus (~> 3.0, > 3.0)
trollop (2.1.3)
......@@ -1074,13 +1074,14 @@ DEPENDENCIES
influxdb (~> 0.2)
jira-ruby (~> 1.4)
jquery-atwho-rails (~> 1.3.2)
js_regex (~> 2.2.1)
json-schema (~> 2.8.0)
jwt (~> 1.5.6)
kaminari (~> 1.0)
knapsack (~> 1.16)
kubeclient (~> 3.1.0)
letter_opener_web (~> 1.3.0)
license_finder (~> 3.1)
license_finder (~> 5.4)
licensee (~> 8.9)
lograge (~> 0.5)
loofah (~> 2.2)
......@@ -1201,4 +1202,4 @@ DEPENDENCIES
wikicloth (= 0.8.1)
BUNDLED WITH
1.16.3
1.16.4
......@@ -89,7 +89,6 @@ GEM
bindata (2.4.3)
binding_of_caller (0.7.2)
debug_inspector (>= 0.0.1)
blankslate (2.1.2.4)
bootsnap (1.3.1)
msgpack (~> 1.0)
bootstrap_form (2.7.0)
......@@ -431,6 +430,8 @@ GEM
multipart-post
oauth (~> 0.5, >= 0.5.0)
jquery-atwho-rails (1.3.2)
js_regex (2.2.1)
regexp_parser (>= 0.4.11, <= 0.5.0)
json (1.8.6)
json-jwt (1.9.4)
activesupport
......@@ -466,13 +467,12 @@ GEM
actionmailer (>= 3.2)
letter_opener (~> 1.0)
railties (>= 3.2)
license_finder (3.1.1)
license_finder (5.4.0)
bundler
httparty
rubyzip
thor
toml (= 0.1.2)
with_env (> 1.0)
toml (= 0.2.0)
with_env (= 1.1.0)
xml-simple
licensee (8.9.2)
rugged (~> 0.24)
......@@ -591,8 +591,7 @@ GEM
parallel (1.12.1)
parser (2.5.1.0)
ast (~> 2.4.0)
parslet (1.5.0)
blankslate (~> 2.0)
parslet (1.8.2)
path_expander (1.0.2)
peek (1.0.1)
concurrent-ruby (>= 0.9.0)
......@@ -735,6 +734,7 @@ GEM
redis-store (>= 1.2, < 2)
redis-store (1.4.1)
redis (>= 2.2, < 5)
regexp_parser (0.5.0)
representable (3.0.4)
declarative (< 0.1.0)
declarative-option (< 0.2.0)
......@@ -914,8 +914,8 @@ GEM
tilt (2.0.8)
timecop (0.8.1)
timfel-krb5-auth (0.8.3)
toml (0.1.2)
parslet (~> 1.5.0)
toml (0.2.0)
parslet (~> 1.8.0)
toml-rb (1.0.0)
citrus (~> 3.0, > 3.0)
trollop (2.1.3)
......@@ -1084,13 +1084,14 @@ DEPENDENCIES
influxdb (~> 0.2)
jira-ruby (~> 1.4)
jquery-atwho-rails (~> 1.3.2)
js_regex (~> 2.2.1)
json-schema (~> 2.8.0)
jwt (~> 1.5.6)
kaminari (~> 1.0)
knapsack (~> 1.16)
kubeclient (~> 3.1.0)
letter_opener_web (~> 1.3.0)
license_finder (~> 3.1)
license_finder (~> 5.4)
licensee (~> 8.9)
lograge (~> 0.5)
loofah (~> 2.2)
......@@ -1211,4 +1212,4 @@ DEPENDENCIES
wikicloth (= 0.8.1)
BUNDLED WITH
1.16.3
1.16.4
......@@ -10,6 +10,7 @@ const hideFlash = (flashEl, fadeTransition = true) => {
flashEl.addEventListener('transitionend', () => {
flashEl.remove();
window.dispatchEvent(new Event('resize'));
if (document.body.classList.contains('flash-shown')) document.body.classList.remove('flash-shown');
}, {
once: true,
......
......@@ -78,13 +78,13 @@ export default {
</script>
<template>
<article class="ide">
<article class="ide position-relative d-flex flex-column align-items-stretch">
<error-message
v-if="errorMessage"
:message="errorMessage"
/>
<div
class="ide-view"
class="ide-view flex-grow d-flex"
>
<find-file
v-show="fileFindVisible"
......
......@@ -24,12 +24,6 @@ export default {
default: null,
},
},
mounted() {
this.$refs.fileUpload.addEventListener('change', this.openFile);
},
beforeDestroy() {
this.$refs.fileUpload.removeEventListener('change', this.openFile);
},
methods: {
createFile(target, file, isText) {
const { name } = file;
......@@ -85,6 +79,8 @@ export default {
ref="fileUpload"
type="file"
class="hidden"
multiple
@change="openFile"
/>
</div>
</template>
......@@ -95,16 +95,18 @@ export default {
return this.file.changed || this.file.tempFile || this.file.staged;
},
},
watch: {
'file.active': function fileActiveWatch(active) {
if (this.file.type === 'blob' && active) {
this.scrollIntoView();
}
},
},
mounted() {
if (this.hasPathAtCurrentRoute()) {
this.scrollIntoView(true);
}
},
updated() {
if (this.file.type === 'blob' && this.file.active) {
this.scrollIntoView();
}
},
methods: {
...mapActions(['toggleTreeOpen']),
clickFile() {
......
import { __ } from '~/locale';
export const PLACEHOLDER_USER_EXTERNAL_DEFAULT_TRUE = __('Regex pattern');
export const PLACEHOLDER_USER_EXTERNAL_DEFAULT_FALSE = __('To define internal users, first enable new users set to external');
function setUserInternalRegexPlaceholder(checkbox) {
const userInternalRegex = document.getElementById('application_setting_user_default_internal_regex');
if (checkbox && userInternalRegex) {
if (checkbox.checked) {
userInternalRegex.readOnly = false;
userInternalRegex.placeholder = PLACEHOLDER_USER_EXTERNAL_DEFAULT_TRUE;
} else {
userInternalRegex.readOnly = true;
userInternalRegex.placeholder = PLACEHOLDER_USER_EXTERNAL_DEFAULT_FALSE;
}
}
}
export default function initUserInternalRegexPlaceholder() {
const checkbox = document.getElementById('application_setting_user_default_external');
setUserInternalRegexPlaceholder(checkbox);
checkbox.addEventListener('change', () => {
setUserInternalRegexPlaceholder(checkbox);
});
}
import initAdmin from './admin';
import initUserInternalRegexPlaceholder from './application_settings/account_and_limits';
document.addEventListener('DOMContentLoaded', initAdmin);
document.addEventListener('DOMContentLoaded', () => {
initAdmin();
initUserInternalRegexPlaceholder();
});
import $ from 'jquery';
export default class UserInternalRegexHandler {
constructor() {
this.regexPattern = $('[data-user-internal-regex-pattern]').data('user-internal-regex-pattern');
if (this.regexPattern && this.regexPattern !== '') {
this.regexOptions = $('[data-user-internal-regex-options]').data('user-internal-regex-options');
this.external = $('#user_external');
this.warningMessage = $('#warning_external_automatically_set');
this.addListenerToEmailField();
this.addListenerToUserExternalCheckbox();
}
}
addListenerToEmailField() {
$('#user_email').on('input', (event) => {
this.setExternalCheckbox(event.currentTarget.value);
});
}
addListenerToUserExternalCheckbox() {
this.external.on('click', () => {
this.warningMessage.addClass('hidden');
});
}
isEmailInternal(email) {
const regex = new RegExp(this.regexPattern, this.regexOptions);
return regex.test(email);
}
setExternalCheckbox(email) {
const isChecked = this.external.prop('checked');
if (this.isEmailInternal(email)) {
if (isChecked) {
this.external.prop('checked', false);
this.warningMessage.removeClass('hidden');
}
} else if (!isChecked) {
this.external.prop('checked', true);
this.warningMessage.addClass('hidden');
}
}
}
document.addEventListener('DOMContentLoaded', () => {
// eslint-disable-next-line
new UserInternalRegexHandler();
});
......@@ -3,6 +3,6 @@ gl-emoji {
display: inline-flex;
vertical-align: middle;
font-family: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-size: 1.5em;
line-height: 0.9;
font-size: 1.4em;
line-height: 1em;
}
......@@ -111,3 +111,42 @@ body {
.with-performance-bar .layout-page {
margin-top: $header-height + $performance-bar-height;
}
.fullscreen-layout {
padding-top: 0;
height: 100vh;
width: 100%;
display: flex;
flex-direction: column;
align-items: stretch;
overflow: hidden;
> #js-peek,
> .navbar-gitlab {
position: static;
top: auto;
}
.flash-container {
margin-top: 0;
margin-bottom: 0;
}
.alert-wrapper .flash-container .flash-alert:last-child,
.alert-wrapper .flash-container .flash-notice:last-child {
margin-bottom: 0;
}
.content-wrapper {
margin-top: 0;
padding-bottom: 0;
flex: 1;
min-height: 0;
}
&.flash-shown {
.content-wrapper {
margin-top: 0;
}
}
}
......@@ -327,7 +327,7 @@ h6 {
pre {
font-family: $monospace-font;
display: block;
padding: $gl-padding-8;
padding: $gl-padding-8 $input-horizontal-padding;
margin: 0 0 $gl-padding-8;
font-size: 13px;
word-break: break-all;
......
......@@ -236,6 +236,7 @@ $gl-vert-padding: 6px;
$gl-padding-top: 10px;
$gl-sidebar-padding: 22px;
$gl-bar-padding: 3px;
$input-horizontal-padding: 12px;
/*
* Misc
......
......@@ -28,11 +28,10 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
.ide-view {
position: relative;
display: flex;
height: calc(100vh - #{$header-height});
margin-top: 0;
padding-bottom: $ide-statusbar-height;
color: $gl-text-color;
min-height: 0; // firefox fix
&.is-collapsed {
.ide-file-list {
......@@ -50,7 +49,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
min-height: 0; // firefox fix
.file {
height: 32px;
......@@ -357,7 +356,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
.multi-file-editor-holder {
height: 100%;
min-height: 0;
min-height: 0; // firefox fix
&.is-readonly,
.editor.original {
......@@ -546,7 +545,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
border-left: 1px solid $white-dark;
border-top: 1px solid $white-dark;
border-top-left-radius: $border-radius-small;
min-height: 0;
min-height: 0; // firefox fix
}
}
......@@ -758,7 +757,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
.ide-loading {
display: flex;
height: 100vh;
height: 100%;
align-items: center;
justify-content: center;
}
......@@ -772,60 +771,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
.ide {
overflow: hidden;
&.nav-only {
padding-top: $header-height;
.with-performance-bar & {
padding-top: $header-height + $performance-bar-height;
}
.flash-container {
margin-top: 0;
margin-bottom: 0;
}
.alert-wrapper .flash-container .flash-alert:last-child,
.alert-wrapper .flash-container .flash-notice:last-child {
margin-bottom: 0;
}
.content-wrapper {
margin-top: 0;
padding-bottom: 0;
}
&.flash-shown {
.content-wrapper {
margin-top: 0;
}
.ide-view {
height: calc(100vh - #{$header-height + $flash-height});
}
}
}
}
.with-performance-bar .ide.nav-only {
.flash-container {
margin-top: 0;
}
.content-wrapper {
margin-top: 0;
padding-bottom: 0;
}
.ide-view {
height: calc(100vh - #{$header-height + $performance-bar-height});
}
&.flash-shown {
.ide-view {
height: calc(100vh - #{$header-height + $performance-bar-height + $flash-height});
}
}
flex: 1;
}
.drag-handle {
......@@ -1199,7 +1145,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
}
.avatar-container {
flex: initial;
flex: 0 0 auto;
margin-right: 0;
}
......@@ -1209,7 +1155,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
}
.ide-context-body {
min-height: 0;
min-height: 0; // firefox fix
}
.ide-sidebar-project-title {
......
......@@ -25,10 +25,6 @@
color: $gl-text-color;
border-radius: 0 0 3px 3px;
.code {
padding: 0;
}
.unfold {
cursor: pointer;
}
......
......@@ -141,6 +141,9 @@ ul.notes {
}
.note-body {
overflow-x: auto;
overflow-y: hidden;
.note-text {
@include md-typography;
// Reset ul style types since we're nested inside a ul already
......
# frozen_string_literal: true
module SendsBlob
extend ActiveSupport::Concern
included do
include BlobHelper
include SendFileUpload
end
def send_blob(blob, params = {})
if blob
headers['X-Content-Type-Options'] = 'nosniff'
return if cached_blob?(blob)
if blob.stored_externally?
send_lfs_object(blob)
else
send_git_blob(repository, blob, params)
end
else
render_404
end
end
private
def cached_blob?(blob)
stale = stale?(etag: blob.id) # The #stale? method sets cache headers.
# Because we are opinionated we set the cache headers ourselves.
response.cache_control[:public] = project.public?
response.cache_control[:max_age] =
if @ref && @commit && @ref == @commit.id # rubocop:disable Gitlab/ModuleWithInstanceVariables
# This is a link to a commit by its commit SHA. That means that the blob
# is immutable. The only reason to invalidate the cache is if the commit
# was deleted or if the user lost access to the repository.
Blob::CACHE_TIME_IMMUTABLE
else
# A branch or tag points at this blob. That means that the expected blob
# value may change over time.
Blob::CACHE_TIME
end
response.etag = blob.id
!stale
end
def send_lfs_object(blob)
lfs_object = find_lfs_object(blob)
if lfs_object && lfs_object.project_allowed_access?(project)
send_upload(lfs_object.file, attachment: blob.name)
else
render_404
end
end
def find_lfs_object(blob)
lfs_object = LfsObject.find_by_oid(blob.lfs_oid)
if lfs_object && lfs_object.file.exists?
lfs_object
else
nil
end
end
end
class IdeController < ApplicationController
layout 'nav_only'
layout 'fullscreen'
def index
end
......
class Projects::AvatarsController < Projects::ApplicationController
include BlobHelper
include SendsBlob
before_action :authorize_admin_project!, only: [:destroy]
def show
@blob = @repository.blob_at_branch(@repository.root_ref, @project.avatar_in_git)
if @blob
headers['X-Content-Type-Options'] = 'nosniff'
return if cached_blob?
send_git_blob @repository, @blob
else
render_404
end
send_blob(@blob)
end
def destroy
@project.remove_avatar!
@project.save
redirect_to edit_project_path(@project, anchor: 'js-general-project-settings'), status: :found
......
# Controller for viewing a file's raw
class Projects::RawController < Projects::ApplicationController
include ExtractsPath
include BlobHelper
include SendFileUpload
include SendsBlob
before_action :require_non_empty_project
before_action :assign_ref_vars
......@@ -10,39 +9,7 @@ class Projects::RawController < Projects::ApplicationController
def show
@blob = @repository.blob_at(@commit.id, @path)
if @blob
headers['X-Content-Type-Options'] = 'nosniff'
return if cached_blob?
if @blob.stored_externally?
send_lfs_object
else
send_git_blob @repository, @blob, inline: (params[:inline] != 'false')
end
else
render_404
end
end
private
def send_lfs_object
lfs_object = find_lfs_object
if lfs_object && lfs_object.project_allowed_access?(@project)
send_upload(lfs_object.file, attachment: @blob.name)
else
render_404
end
end
def find_lfs_object
lfs_object = LfsObject.find_by_oid(@blob.lfs_oid)
if lfs_object && lfs_object.file.exists?
lfs_object
else
nil
end
send_blob(@blob, inline: (params[:inline] != 'false'))
end
end
......@@ -255,6 +255,7 @@ module ApplicationSettingsHelper
:instance_statistics_visibility_private,
:user_default_external,
:user_show_add_ssh_key_message,
:user_default_internal_regex,
:user_oauth_applications,
:version_check_enabled,
:web_ide_clientside_preview_enabled
......
......@@ -157,28 +157,6 @@ module BlobHelper
end
end
def cached_blob?
stale = stale?(etag: @blob.id) # The #stale? method sets cache headers.
# Because we are opionated we set the cache headers ourselves.
response.cache_control[:public] = @project.public?
response.cache_control[:max_age] =
if @ref && @commit && @ref == @commit.id
# This is a link to a commit by its commit SHA. That means that the blob
# is immutable. The only reason to invalidate the cache is if the commit
# was deleted or if the user lost access to the repository.
Blob::CACHE_TIME_IMMUTABLE
else
# A branch or tag points at this blob. That means that the expected blob
# value may change over time.
Blob::CACHE_TIME
end
response.etag = @blob.id
!stale
end
def licenses_for_select
return @licenses_for_select if defined?(@licenses_for_select)
......
......@@ -64,8 +64,7 @@ module SubmoduleHelper
end
def relative_self_url?(url)
# (./)?(../repo.git) || (./)?(../../project/repo.git) )
url =~ %r{\A((\./)?(\.\./))(?!(\.\.)|(.*/)).*(\.git)?\z} || url =~ %r{\A((\./)?(\.\./){2})(?!(\.\.))([^/]*)/(?!(\.\.)|(.*/)).*(\.git)?\z}
url.start_with?('../', './')
end
def standard_links(host, namespace, project, commit)
......@@ -73,25 +72,29 @@ module SubmoduleHelper
[base, [base, '/tree/', commit].join('')]
end
def relative_self_links(url, commit, project)
url.rstrip!
# Map relative links to a namespace and project
# For example:
# ../bar.git -> same namespace, repo bar
# ../foo/bar.git -> namespace foo, repo bar
# ../../foo/bar/baz.git -> namespace bar, repo baz
components = url.split('/')
base = components.pop.gsub(/.git$/, '')
namespace = components.pop.gsub(/^\.\.$/, '')
if namespace.empty?
namespace = project.namespace.full_path
def relative_self_links(relative_path, commit, project)
relative_path.rstrip!
absolute_project_path = "/" + project.full_path
# Resolve `relative_path` to target path
# Assuming `absolute_project_path` is `/g1/p1`:
# ../p2.git -> /g1/p2
# ../g2/p3.git -> /g1/g2/p3
# ../../g3/g4/p4.git -> /g3/g4/p4
submodule_project_path = File.absolute_path(relative_path, absolute_project_path)
target_namespace_path = File.dirname(submodule_project_path)
if target_namespace_path == '/' || target_namespace_path.start_with?(absolute_project_path)
return [nil, nil]
end
target_namespace_path.sub!(%r{^/}, '')
submodule_base = File.basename(submodule_project_path, '.git')
begin
[
namespace_project_path(namespace, base),
namespace_project_tree_path(namespace, base, commit)
namespace_project_path(target_namespace_path, submodule_base),
namespace_project_tree_path(target_namespace_path, submodule_base, commit)
]
rescue ActionController::UrlGenerationError
[nil, nil]
......
......@@ -23,6 +23,17 @@ module UsersHelper
profile_tabs.include?(tab)
end
def user_internal_regex_data
settings = Gitlab::CurrentSettings.current_application_settings
pattern, options = if settings.user_default_internal_regex_enabled?
regex = settings.user_default_internal_regex_instance
JsRegex.new(regex).to_h.slice(:source, :options).values
end
{ user_internal_regex_pattern: pattern, user_internal_regex_options: options }
end
def current_user_menu_items
@current_user_menu_items ||= get_current_user_menu_items
end
......
......@@ -192,6 +192,8 @@ class ApplicationSetting < ActiveRecord::Base
numericality: { less_than_or_equal_to: :gitaly_timeout_default },
if: :gitaly_timeout_default
validates :user_default_internal_regex, js_regex: true, allow_nil: true
SUPPORTED_KEY_TYPES.each do |type|
validates :"#{type}_key_restriction", presence: true, key_restriction: { type: type }
end
......@@ -299,6 +301,7 @@ class ApplicationSetting < ActiveRecord::Base
usage_ping_enabled: Settings.gitlab['usage_ping_enabled'],
instance_statistics_visibility_private: false,
user_default_external: false,
user_default_internal_regex: nil,
user_show_add_ssh_key_message: true
}
end
......@@ -435,6 +438,14 @@ class ApplicationSetting < ActiveRecord::Base
password_authentication_enabled_for_web? || password_authentication_enabled_for_git?
end
def user_default_internal_regex_enabled?
user_default_external? && user_default_internal_regex.present?
end
def user_default_internal_regex_instance
Regexp.new(user_default_internal_regex, Regexp::IGNORECASE)
end
delegate :terms, to: :latest_terms, allow_nil: true
def latest_terms
@latest_terms ||= Term.latest
......
......@@ -26,7 +26,7 @@
module AtomicInternalId
extend ActiveSupport::Concern
module ClassMethods
class_methods do
def has_internal_id(column, scope:, init:, presence: true) # rubocop:disable Naming/PredicateName
# We require init here to retain the ability to recalculate in the absence of a
# InternaLId record (we may delete records in `internal_ids` for example).
......
......@@ -12,7 +12,7 @@ module Awardable
end
end
module ClassMethods
class_methods do
def awarded(user, name)
sql = <<~EOL
EXISTS (
......
......@@ -4,7 +4,7 @@
module CaseSensitivity
extend ActiveSupport::Concern
module ClassMethods
class_methods do
# Queries the given columns regardless of the casing used.
#
# Unlike other ActiveRecord methods this method only operates on a Hash.
......
......@@ -3,7 +3,7 @@
module EachBatch
extend ActiveSupport::Concern
module ClassMethods
class_methods do
# Iterates over the rows in a relation in batches, similar to Rails'
# `in_batches` but in a more efficient way.
#
......
......@@ -14,7 +14,7 @@
module IgnorableColumn
extend ActiveSupport::Concern
module ClassMethods
class_methods do
def columns
super.reject { |column| ignored_columns.include?(column.name) }
end
......
......@@ -118,7 +118,7 @@ module Issuable
end
end
module ClassMethods
class_methods do
# Searches for records with a matching title.
#
# This method uses ILIKE on PostgreSQL and LIKE on MySQL.
......
......@@ -3,7 +3,7 @@
module LoadedInGroupList
extend ActiveSupport::Concern
module ClassMethods
class_methods do
def with_counts(archived:)
selects_including_counts = [
'namespaces.*',
......
......@@ -3,7 +3,7 @@
module ManualInverseAssociation
extend ActiveSupport::Concern
module ClassMethods
class_methods do
def manual_inverse_association(association, inverse)
define_method(association) do |*args|
super(*args).tap do |value|
......
......@@ -10,7 +10,7 @@
module Mentionable
extend ActiveSupport::Concern
module ClassMethods
class_methods do
# Indicate which attributes of the Mentionable to search for GFM references.
def attr_mentionable(attr, options = {})
attr = attr.to_s
......
......@@ -3,7 +3,7 @@
module OptionallySearch
extend ActiveSupport::Concern
module ClassMethods
class_methods do
def search(*)
raise(
NotImplementedError,
......
......@@ -26,7 +26,7 @@
module Participable
extend ActiveSupport::Concern
module ClassMethods
class_methods do
# Adds a list of participant attributes. Attributes can either be symbols or
# Procs.
#
......
......@@ -40,7 +40,7 @@ module Referable
end
end
module ClassMethods
class_methods do
# The character that prefixes the actual reference identifier
#
# This should be overridden by the including class.
......
......@@ -20,7 +20,7 @@ module ResolvableNote
scope :unresolved, -> { resolvable.where(resolved_at: nil) }
end
module ClassMethods
class_methods do
# This method must be kept in sync with `#resolve!`
def resolve!(current_user)
unresolved.update_all(resolved_at: Time.now, resolved_by_id: current_user.id)
......
......@@ -3,7 +3,7 @@
module SelectForProjectAuthorization
extend ActiveSupport::Concern
module ClassMethods
class_methods do
def select_for_project_authorization
select("projects.id AS project_id, members.access_level")
end
......
......@@ -3,7 +3,7 @@
module ShaAttribute
extend ActiveSupport::Concern
module ClassMethods
class_methods do
def sha_attribute(name)
return if ENV['STATIC_VERIFICATION']
......
......@@ -19,7 +19,7 @@ module Sortable
scope :order_name_desc, -> { reorder(Arel::Nodes::Descending.new(arel_table[:name].lower)) }
end
module ClassMethods
class_methods do
def order_by(method)
case method.to_s
when 'created_asc' then order_created_asc
......
......@@ -3,7 +3,7 @@
module Spammable
extend ActiveSupport::Concern
module ClassMethods
class_methods do
def attr_spammable(attr, options = {})
spammable_attrs << [attr.to_s, options]
end
......
......@@ -14,7 +14,7 @@
module StripAttribute
extend ActiveSupport::Concern
module ClassMethods
class_methods do
def strip_attributes(*attrs)
strip_attrs.concat(attrs)
end
......
......@@ -6,6 +6,7 @@ module TriggerableHooks
push_hooks: :push_events,
tag_push_hooks: :tag_push_events,
issue_hooks: :issues_events,
confidential_note_hooks: :confidential_note_events,
confidential_issue_hooks: :confidential_issues_events,
note_hooks: :note_events,
merge_request_hooks: :merge_requests_events,
......
......@@ -2,7 +2,6 @@
class DiffFileEntity < Grape::Entity
include RequestAwareEntity
include BlobHelper
include CommitsHelper
include DiffHelper
include SubmoduleHelper
......
......@@ -43,8 +43,8 @@ module Projects
@new_path = File.join(@new_namespace.try(:full_path) || '', project.path)
@old_namespace = project.namespace
if Project.where(path: project.path, namespace_id: @new_namespace.try(:id)).exists?
raise TransferError.new("Project with same path in target namespace already exists")
if Project.where(namespace_id: @new_namespace.try(:id)).where('path = ? or name = ?', project.path, project.name).exists?
raise TransferError.new("Project with same name or path in target namespace already exists")
end
if project.has_container_registry_tags?
......@@ -118,6 +118,7 @@ module Projects
def rollback_side_effects
rollback_folder_move
project.reload
update_namespace_and_visibility(@old_namespace)
write_repository_config(@old_path)
end
......
......@@ -2,6 +2,10 @@
module Users
class BuildService < BaseService
delegate :user_default_internal_regex_enabled?,
:user_default_internal_regex_instance,
to: :'Gitlab::CurrentSettings.current_application_settings'
def initialize(current_user, params = {})
@current_user = current_user
@params = params.dup
......@@ -89,6 +93,10 @@ module Users
if params[:reset_password]
user_params.merge!(force_random_password: true, password_expires_at: nil)
end
if user_default_internal_regex_enabled? && !user_params.key?(:external)
user_params[:external] = user_external?
end
else
allowed_signup_params = signup_params
allowed_signup_params << :skip_confirmation if skip_authorization
......@@ -105,5 +113,9 @@ module Users
def skip_user_confirmation_email_from_setting
!Gitlab::CurrentSettings.send_user_confirmation_email
end
def user_external?
user_default_internal_regex_instance.match(params[:email]).nil?
end
end
end
class JsRegexValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
return true if value.blank?
parsed_regex = JsRegex.new(Regexp.new(value, Regexp::IGNORECASE))
if parsed_regex.source.empty?
record.errors.add(attribute, "Regex Pattern #{value} can not be expressed in Javascript")
else
parsed_regex.warnings.each { |warning| record.errors.add(attribute, warning) }
end
rescue RegexpError => regex_error
record.errors.add(attribute, regex_error.to_s)
end
end
......@@ -29,6 +29,13 @@
= f.check_box :user_default_external, class: 'form-check-input'
= f.label :user_default_external, class: 'form-check-label' do
Newly registered users will by default be external
.prepend-top-10
= _('Internal users')
= f.text_field :user_default_internal_regex, placeholder: _('Regex pattern'), class: 'form-control prepend-top-5'
.help-block
= _('Specify an e-mail address regex pattern to identify default internal users.')
= link_to _('More information'), help_page_path('user/permissions', anchor: 'external-users-permissions'),
target: '_blank'
.form-group
= f.label :user_show_add_ssh_key_message, 'Prompt users to upload SSH keys', class: 'label-bold'
.form-check
......
......@@ -34,8 +34,12 @@
.form-group.row
.col-sm-2.text-right
= f.label :external, class: 'col-form-label'
.hidden{ data: user_internal_regex_data }
.col-sm-10
= f.check_box :external do
External
%p.light
External users cannot see internal or private projects unless access is explicitly granted. Also, external users cannot create projects or groups.
%row.hidden#warning_external_automatically_set.hidden
.badge.badge-warning.text-white
= _('Automatically marked as default internal user')
- @body_class = 'ide'
- @body_class = 'ide-layout'
- page_title 'IDE'
- content_for :page_specific_javascripts do
......
!!! 5
%html{ lang: I18n.locale, class: page_class }
= render "layouts/head"
%body{ class: "#{user_application_theme} #{@body_class} nav-only", data: { page: body_data_page } }
%body{ class: "#{user_application_theme} #{@body_class} fullscreen-layout", data: { page: body_data_page } }
= render 'peek/bar'
= render "layouts/header/default"
= render 'shared/outdated_browser'
......@@ -10,5 +10,5 @@
= render "layouts/broadcast"
= yield :flash_message
= render "layouts/flash"
.content{ id: "content-body" }
.content-wrapper{ id: "content-body", class: "d-flex flex-column align-items-stretch" }
= yield
......@@ -30,11 +30,13 @@
%pre.dark#merge-info-3
- if @merge_request.for_fork?
:preserve
git checkout #{h @merge_request.target_branch}
git fetch origin
git checkout origin/#{h @merge_request.target_branch}
git merge --no-ff #{h @merge_request.source_project_path}-#{h @merge_request.source_branch}
- else
:preserve
git checkout #{h @merge_request.target_branch}
git fetch origin
git checkout origin/#{h @merge_request.target_branch}
git merge --no-ff #{h @merge_request.source_branch}
%p
%strong Step 4.
......
.banner-callout.compact.milestone-deprecation-message.prepend-top-20
.banner-graphic= image_tag 'illustrations/milestone_removing-page.svg'
.banner-body.prepend-left-10.append-right-10
%h5.banner-title.prepend-top-0
= _('The tabs below will be removed in a future version')
%p.milestone-banner-text
= _('Learn more about %{issue_boards_url}, to keep track of issues in multiple lists, using labels, assignees, and milestones. If you’re missing something from issue boards, please create an issue on %{gitlab_issues_url}.').html_safe % { issue_boards_url: link_to(_('issue boards'), help_page_url('user/project/issue_board'), target: '_blank', rel: 'noopener noreferrer'), gitlab_issues_url: link_to(_('GitLab’s issue tracker'), 'https://gitlab.com/gitlab-org/gitlab-ce/issues', target: '_blank', rel: 'noopener noreferrer') }
......@@ -67,5 +67,6 @@
.alert.alert-success.prepend-top-default
%span All issues for this milestone are closed. You may close this milestone now.
= render 'deprecation_message'
= render 'shared/milestones/tabs', milestone: @milestone
= render 'shared/milestones/sidebar', milestone: @milestone, project: @project, affix_offset: 153
......@@ -10,17 +10,7 @@ class BackgroundMigrationWorker
# maintenance related tasks have plenty of time to clean up after a migration
# has been performed.
def self.minimum_interval
if enable_health_check?
2.minutes.to_i
else
5.minutes.to_i
end
end
def self.enable_health_check?
Rails.env.development? ||
Rails.env.test? ||
Feature.enabled?('background_migration_health_check')
2.minutes.to_i
end
# Performs the background migration.
......@@ -86,8 +76,6 @@ class BackgroundMigrationWorker
# class_name - The name of the background migration that we might want to
# run.
def healthy_database?
return true unless self.class.enable_health_check?
return true unless Gitlab::Database.postgresql?
!Postgresql::ReplicationSlot.lag_too_great?
......
......@@ -11,7 +11,7 @@ module ApplicationWorker
set_queue
end
module ClassMethods
class_methods do
def inherited(subclass)
subclass.set_queue
end
......
......@@ -3,7 +3,7 @@
module WaitableWorker
extend ActiveSupport::Concern
module ClassMethods
class_methods do
# Schedules multiple jobs and waits for them to be completed.
def bulk_perform_and_wait(args_list, timeout: 10)
# Short-circuit: it's more efficient to do small numbers of jobs inline
......
---
title: Fix git submodule link for subgroup projects with relative path
merge_request: 21154
author:
type: fixed
---
title: Fix project transfer name validation issues causing a redirect loop
merge_request: 21408
author:
type: fixed
---
title: Fix IDE issues with persistent banners
merge_request: 21283
author:
type: fixed
---
title: Importing a project no longer fails when visibility level holds a string value
type
merge_request: 21242
author:
type: fixed
---
title: Show deprecation message on project milestone page for category tabs
merge_request: 21236
author:
type: changed
---
title: Adds Rubocop rule to enforce class_methods over module ClassMethods
merge_request: 21379
author: Jacopo Beschi @jacopo-beschi
type: added
---
title: Add default parameter to branches API
merge_request: 21294
author: Riccardo Padovani
type: changed
---
title: Add an option to whitelist users based on email address as internal when the "New user set to external" setting is enabled.
merge_request: 17711
author: Roger Rüttimann
type: added
---
title: Fix Emojis cutting in the right way
merge_request:
author: Alexander Popov
type: fixed
---
title: Fixed bug when the project logo file is stored in LFS
merge_request: 20948
author:
type: fixed
---
title: Enabled multiple file uploads in the Web IDE
merge_request:
author:
type: added
---
title: Fixed IDE file row scrolling into view when hovering
merge_request:
author:
type: fixed
---
title: Remove health check feature flag in BackgroundMigrationWorker
merge_request:
author:
type: changed
---
title: Backport schema_changed.sh from EE which prints the diff if the schema is different
merge_request: 21422
author: Jasper Maes
type: other
---
title: Fix "Confidential comments" button not saving in project hooks
merge_request: 21289
author:
type: fixed
---
title: Fix Error 500s due to encoding issues when Wiki hooks fire
merge_request: 21414
author:
type: fixed
---
title: Increase padding in code blocks
merge_request:
author:
type: fixed
class AddUserInternalRegexToApplicationSetting < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def up
add_column :application_settings, :user_default_internal_regex, :string, null: true
end
def down
remove_column :application_settings, :user_default_internal_regex
end
end
......@@ -164,6 +164,7 @@ ActiveRecord::Schema.define(version: 20180826111825) do
t.boolean "authorized_keys_enabled", default: true, null: false
t.string "auto_devops_domain"
t.boolean "pages_domain_verification_enabled", default: true, null: false
t.string "user_default_internal_regex"
t.boolean "allow_local_requests_from_hooks_and_services", default: false, null: false
t.boolean "enforce_terms", default: false
t.boolean "mirror_available", default: true, null: false
......
# Compliance features
You can configure the following GitLab features to help ensure that your GitLab instance meets common compliance standards. Click a feature name for further documentation.
GitLab’s [security features](../security/README.md) may also help you meet relevant compliance standards.
|Feature |GitLab tier |GitLab.com |
| ---------| :--------: | :-------: |
|**[Restrict SSH Keys](../README.html#administrator-documentation)**<br>Control the technology and key length of SSH keys used to access GitLab|Core+||
|**[Granular user roles and flexible permissions](../user/permissions.html)**<br>Manage access and permissions with five different user roles and settings for external users. Set permissions according to people's role, rather than either read or write access to a repository. Don't share the source code with people that only need access to the issue tracker.|Core+|✓|
|**[Enforce TOS acceptance](../user/admin_area/settings/terms.html)**<br>Enforce your users accepting new terms of service by blocking GitLab traffic.|Core+||
|**[Email all users of a project, group, or entire server](../user/admin_area/settings/terms.html)**<br>An admin can email groups of users based on project or group membership, or email everyone using the GitLab instance. This is great for scheduled maintenance or upgrades.|Starter+||
|**[Omnibus package supports log forwarding](https://docs.gitlab.com/omnibus/settings/logs.html#udp-log-forwarding)**<br>Forward your logs to a central system.|Starter+||
|**[Lock project membership to group](../workflow/groups.html#lock-project-membership-to-members-of-this-group)**<br>Group owners can prevent new members from being added to projects within a group.|Starter+|✓|
|**[LDAP group sync](https://docs.gitlab.com/ee/administration/auth/ldap-ee.html#group-sync)**<br>GitLab Enterprise Edition gives admins the ability to automatically sync groups and manage SSH keys, permissions, and authentication, so you can focus on building your product, not configuring your tools.|Starter+||
|**[LDAP group sync filters](https://docs.gitlab.com/ee/administration/auth/ldap-ee.html#group-sync)**<br>GitLab Enterprise Edition Premium gives more flexibility to synchronize with LDAP based on filters, meaning you can leverage LDAP attributes to map GitLab permissions.|Premium+||
|**[Audit logs](https://docs.gitlab.com/ee/administration/audit_events.html)**<br>To maintain the integrity of your code, GitLab Enterprise Edition Premium gives admins the ability to view any modifications made within the GitLab server in an advanced audit log system, so you can control, analyze and track every change.|Premium+||
|**[Auditor users](https://docs.gitlab.com/ee/administration/auditor_users.html)**<br>Auditor users are users who are given read-only access to all projects, groups, and other resources on the GitLab instance.|Premium+||
\ No newline at end of file
......@@ -46,6 +46,7 @@ Learn how to install, configure, update, and maintain your GitLab instance.
- [Plugins](plugins.md): With custom plugins, GitLab administrators can introduce custom integrations without modifying GitLab's source code.
- [Enforcing Terms of Service](../user/admin_area/settings/terms.md)
- [Third party offers](../user/admin_area/settings/third_party_offers.md)
- [Compliance](compliance.md): A collection of features from across the application that you may configure to help ensure that your GitLab instance and DevOps workflow meet compliance standards.
#### Customizing GitLab's appearance
......
......@@ -27,6 +27,7 @@ Example response:
"name": "master",
"merged": false,
"protected": true,
"default": true,
"developers_can_push": false,
"developers_can_merge": false,
"can_push": true,
......@@ -75,6 +76,7 @@ Example response:
"name": "master",
"merged": false,
"protected": true,
"default": true,
"developers_can_push": false,
"developers_can_merge": false,
"can_push": true,
......@@ -141,6 +143,7 @@ Example response:
"name": "master",
"merged": false,
"protected": true,
"default": true,
"developers_can_push": true,
"developers_can_merge": true,
"can_push": true
......@@ -190,6 +193,7 @@ Example response:
"name": "master",
"merged": false,
"protected": false,
"default": true,
"developers_can_push": false,
"developers_can_merge": false,
"can_push": true
......@@ -234,6 +238,7 @@ Example response:
"name": "newbranch",
"merged": false,
"protected": false,
"default": false,
"developers_can_push": false,
"developers_can_merge": false,
"can_push": true
......
doc/user/group/img/groups.png

60.6 KB | W: | H:

doc/user/group/img/groups.png

60.1 KB | W: | H:

doc/user/group/img/groups.png
doc/user/group/img/groups.png
doc/user/group/img/groups.png
doc/user/group/img/groups.png
  • 2-up
  • Swipe
  • Onion skin
......@@ -197,7 +197,7 @@ They will, like usual users, receive a role in the project or group with all
the abilities that are mentioned in the table above. They cannot however create
groups or projects, and they have the same access as logged out users in all
other cases.
An administrator can flag a user as external [through the API](../api/users.md)
or by checking the checkbox on the admin panel. As an administrator, navigate
to **Admin > Users** to create a new user or edit an existing one. There, you
......@@ -206,6 +206,21 @@ will find the option to flag the user as external.
By default new users are not set as external users. This behavior can be changed
by an administrator under **Admin > Application Settings**.
### Default internal users
The "Internal users" field allows specifying an e-mail address regex pattern to identify default internal users.
New users whose email address matches the regex pattern will be set to internal by default rather than an external collaborator.
The regex pattern format is Ruby, but it needs to be convertible to JavaScript, and the ignore case flag will be set, e.g. "/regex pattern/i".
Here are some examples:
- Use `\.internal@domain\.com` to mark email addresses containing ".internal@domain.com" internal.
- Use `^(?:(?!\.ext@domain\.com).)*$\r?` to mark users with email addresses NOT including .ext@domain.com internal.
Please be aware that this regex could lead to a DOS attack, [see](https://en.wikipedia.org/wiki/ReDoS?) ReDos on Wikipedia.
## Auditor users **[PREMIUM ONLY]**
>[Introduced][ee-998] in [GitLab Premium][eep] 8.17.
......
......@@ -2,7 +2,7 @@
## On Microsoft Teams
To enable Microsoft Teams integration you must create an incoming webhook integration on Microsoft Teams by following the steps described in this [document](https://docs.microsoft.com/en-us/microsoftteams/platform/concepts/connectors#setting-up-a-custom-incoming-webhook).
To enable Microsoft Teams integration you must create an incoming webhook integration on Microsoft Teams by following the steps described in this [document](https://docs.microsoft.com/en-us/microsoftteams/platform/concepts/connectors/connectors-using#setting-up-a-custom-incoming-webhook).
## On GitLab
......
......@@ -84,7 +84,7 @@ module API
end
end
module ClassMethods
class_methods do
private
def install_error_responders(base)
......
......@@ -370,6 +370,10 @@ module API
expose :can_push do |repo_branch, options|
Gitlab::UserAccess.new(options[:current_user], project: options[:project]).can_push_to_branch?(repo_branch.name)
end
expose :default do |repo_branch, options|
options[:project].default_branch == repo_branch.name
end
end
class TreeObject < Grape::Entity
......
......@@ -2,7 +2,7 @@ module API
module ProjectsRelationBuilder
extend ActiveSupport::Concern
module ClassMethods
class_methods do
def prepare_relation(projects_relation, options = {})
projects_relation = preload_relation(projects_relation, options)
execute_batch_counting(projects_relation)
......
......@@ -75,7 +75,7 @@ module Gitlab
end
def binary_stringio(str)
StringIO.new(str || '').tap { |io| io.set_encoding(Encoding::ASCII_8BIT) }
StringIO.new(str.freeze || '').tap { |io| io.set_encoding(Encoding::ASCII_8BIT) }
end
private
......
......@@ -233,6 +233,8 @@ module Gitlab
end
elsif user
# User access is verified in check_change_access!
elsif authed_via_jwt?
# Authenticated via JWT
else
raise UnauthorizedError, ERROR_MESSAGES[:upload]
end
......@@ -321,6 +323,10 @@ module Gitlab
!Gitlab.config.gitlab_shell.receive_pack
end
def authed_via_jwt?
false
end
protected
def changes_list
......
......@@ -6,7 +6,7 @@ module Gitlab
module ExposeAttribute
extend ActiveSupport::Concern
module ClassMethods
class_methods do
# Defines getter methods for the given attribute names.
#
# Example:
......
......@@ -5,7 +5,7 @@ module Gitlab
module MountMutation
extend ActiveSupport::Concern
module ClassMethods
class_methods do
def mount_mutation(mutation_class)
# Using an underscored field name symbol will make `graphql-ruby`
# standardize the field name
......
......@@ -94,7 +94,10 @@ module Gitlab
end
def restore_project
@project.update_columns(project_params)
Gitlab::Timeless.timeless(@project) do
@project.update(project_params)
end
@project
end
......
......@@ -2,7 +2,7 @@
module StaticModel
extend ActiveSupport::Concern
module ClassMethods
class_methods do
# Used by ActiveRecord's polymorphic association to set object_id
def primary_key
'id'
......
......@@ -727,6 +727,9 @@ msgstr ""
msgid "AutoDevOps|enable Auto DevOps"
msgstr ""
msgid "Automatically marked as default internal user"
msgstr ""
msgid "Available"
msgstr ""
......@@ -2801,6 +2804,9 @@ msgstr ""
msgid "GitLab.com import"
msgstr ""
msgid "GitLab’s issue tracker"
msgstr ""
msgid "Gitaly"
msgstr ""
......@@ -3187,6 +3193,9 @@ msgstr ""
msgid "Internal - The project can be accessed by any logged in user."
msgstr ""
msgid "Internal users"
msgstr ""
msgid "Interval Pattern"
msgstr ""
......@@ -3387,6 +3396,9 @@ msgstr ""
msgid "Learn more"
msgstr ""
msgid "Learn more about %{issue_boards_url}, to keep track of issues in multiple lists, using labels, assignees, and milestones. If you’re missing something from issue boards, please create an issue on %{gitlab_issues_url}."
msgstr ""
msgid "Learn more about Kubernetes"
msgstr ""
......@@ -4670,6 +4682,9 @@ msgid_plural "Refreshing in %d seconds to show the updated status..."
msgstr[0] ""
msgstr[1] ""
msgid "Regex pattern"
msgstr ""
msgid "Register / Sign In"
msgstr ""
......@@ -5277,6 +5292,9 @@ msgstr ""
msgid "Specific Runners"
msgstr ""
msgid "Specify an e-mail address regex pattern to identify default internal users."
msgstr ""
msgid "Specify the following URL during the Runner setup:"
msgstr ""
......@@ -5555,6 +5573,9 @@ msgstr ""
msgid "The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time."
msgstr ""
msgid "The tabs below will be removed in a future version"
msgstr ""
msgid "The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running."
msgstr ""
......@@ -5934,6 +5955,9 @@ msgstr ""
msgid "To add an SSH key you need to %{generate_link_start}generate one%{link_end} or use an %{existing_link_start}existing key%{link_end}."
msgstr ""
msgid "To define internal users, first enable new users set to external"
msgstr ""
msgid "To get started you enter your FogBugz URL and login information below. In the next steps, you'll be able to map users and select the projects you want to import."
msgstr ""
......@@ -6590,6 +6614,9 @@ msgstr ""
msgid "importing"
msgstr ""
msgid "issue boards"
msgstr ""
msgid "latest version"
msgstr ""
......
require 'pathname'
module QA
module Page
class View
......@@ -9,7 +11,7 @@ module QA
end
def pathname
@pathname ||= Pathname.new(::File.join(__dir__, '../../../', @path))
@pathname ||= ::Pathname.new(::File.join(__dir__, '../../../', @path))
.cleanpath.expand_path
end
......
# frozen_string_literal: true
module RuboCop
module Cop
# Enforces the use of 'class_methods' instead of 'module ClassMethods' for activesupport concerns.
# For more information see: https://gitlab.com/gitlab-org/gitlab-ce/issues/50414
#
# @example
# # bad
# module Foo
# extend ActiveSupport::Concern
#
# module ClassMethods
# def a_class_method
# end
# end
# end
#
# # good
# module Foo
# extend ActiveSupport::Concern
#
# class_methods do
# def a_class_method
# end
# end
# end
#
class PreferClassMethodsOverModule < RuboCop::Cop::Cop
include RangeHelp
MSG = 'Do not use module ClassMethods, use class_methods block instead.'
def_node_matcher :extend_activesupport_concern?, <<~PATTERN
(:send nil? :extend (:const (:const nil? :ActiveSupport) :Concern))
PATTERN
def on_module(node)
add_offense(node) if node.defined_module_name == 'ClassMethods' && module_extends_activesupport_concern?(node)
end
def autocorrect(node)
lambda do |corrector|
corrector.replace(module_range(node), 'class_methods do')
end
end
private
def module_extends_activesupport_concern?(node)
container_module = container_module_of(node)
return false unless container_module
container_module.descendants.any? do |descendant|
extend_activesupport_concern?(descendant)
end
end
def container_module_of(node)
while node = node.parent
break if node.type == :module
end
node
end
def module_range(node)
module_node, _ = *node
range_between(node.loc.keyword.begin_pos, module_node.source_range.end_pos)
end
end
end
end
......@@ -7,6 +7,7 @@ require_relative 'cop/include_sidekiq_worker'
require_relative 'cop/avoid_return_from_blocks'
require_relative 'cop/avoid_break_from_strong_memoize'
require_relative 'cop/line_break_around_conditional_block'
require_relative 'cop/prefer_class_methods_over_module'
require_relative 'cop/migration/add_column'
require_relative 'cop/migration/add_concurrent_foreign_key'
require_relative 'cop/migration/add_concurrent_index'
......
function schema_changed() {
if [[ ! -z `git diff --name-only -- db/schema.rb` ]]; then
echo "db/schema.rb after rake db:migrate:reset is different from one in the repository"
#!/bin/sh
schema_changed() {
if [ ! -z "$(git diff --name-only -- db/schema.rb)" ]; then
printf "db/schema.rb after rake db:migrate:reset is different from one in the repository"
printf "The diff is as follows:\n"
diff=$(git diff -p --binary -- db/schema.rb)
printf "%s" "$diff"
exit 1
else
echo "db/schema.rb after rake db:migrate:reset matches one in the repository"
printf "db/schema.rb after rake db:migrate:reset matches one in the repository"
fi
}
......
require 'spec_helper'
describe Projects::AvatarsController do
let(:project) { create(:project, :repository, avatar: fixture_file_upload("spec/fixtures/dk.png", "image/png")) }
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
before do
sign_in(user)
project.add_maintainer(user)
controller.instance_variable_set(:@project, project)
end
it 'GET #show' do
get :show, namespace_id: project.namespace.id, project_id: project.id
describe 'GET #show' do
subject { get :show, namespace_id: project.namespace, project_id: project }
expect(response).to have_gitlab_http_status(404)
context 'when repository has no avatar' do
it 'shows 404' do
subject
expect(response).to have_gitlab_http_status(404)
end
end
context 'when repository has an avatar' do
before do
allow(project).to receive(:avatar_in_git).and_return(filepath)
end
context 'when the avatar is stored in the repository' do
let(:filepath) { 'files/images/logo-white.png' }
it 'sends the avatar' do
subject
expect(response).to have_gitlab_http_status(200)
expect(response.header['Content-Type']).to eq('image/png')
expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:')
end
end
context 'when the avatar is stored in lfs' do
it_behaves_like 'repository lfs file load' do
let(:filename) { 'lfs_object.iso' }
let(:filepath) { "files/lfs/#{filename}" }
end
end
end
end
it 'removes avatar from DB by calling destroy' do
delete :destroy, namespace_id: project.namespace.id, project_id: project.id
expect(project.avatar.present?).to be_falsey
expect(project).to be_valid
describe 'DELETE #destroy' do
it 'removes avatar from DB by calling destroy' do
delete :destroy, namespace_id: project.namespace.id, project_id: project.id
expect(project.avatar.present?).to be_falsey
expect(project).to be_valid
end
end
end
......@@ -30,6 +30,7 @@ describe Projects::HooksController do
tag_push_events: true,
merge_requests_events: true,
issues_events: true,
confidential_note_events: true,
confidential_issues_events: true,
note_events: true,
job_events: true,
......
require 'spec_helper'
describe Projects::RawController do
let(:public_project) { create(:project, :public, :repository) }
let(:project) { create(:project, :public, :repository) }
describe 'GET #show' do
subject do
get(:show,
namespace_id: project.namespace,
project_id: project,
id: filepath)
end
describe '#show' do
context 'regular filename' do
let(:id) { 'master/README.md' }
let(:filepath) { 'master/README.md' }
it 'delivers ASCII file' do
get_show(public_project, id)
subject
expect(response).to have_gitlab_http_status(200)
expect(response.header['Content-Type']).to eq('text/plain; charset=utf-8')
......@@ -19,10 +26,10 @@ describe Projects::RawController do
end
context 'image header' do
let(:id) { 'master/files/images/6049019_460s.jpg' }
let(:filepath) { 'master/files/images/6049019_460s.jpg' }
it 'sets image content type header' do
get_show(public_project, id)
subject
expect(response).to have_gitlab_http_status(200)
expect(response.header['Content-Type']).to eq('image/jpeg')
......@@ -30,85 +37,9 @@ describe Projects::RawController do
end
end
context 'lfs object' do
let(:id) { 'be93687/files/lfs/lfs_object.iso' }
let!(:lfs_object) { create(:lfs_object, oid: '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', size: '1575078') }
context 'when lfs is enabled' do
before do
allow_any_instance_of(Project).to receive(:lfs_enabled?).and_return(true)
end
context 'when project has access' do
before do
public_project.lfs_objects << lfs_object
allow_any_instance_of(LfsObjectUploader).to receive(:exists?).and_return(true)
allow(controller).to receive(:send_file) { controller.head :ok }
end
it 'serves the file' do
expect(controller).to receive(:send_file).with("#{LfsObjectUploader.root}/91/ef/f75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897", filename: 'lfs_object.iso', disposition: 'attachment')
get_show(public_project, id)
expect(response).to have_gitlab_http_status(200)
end
context 'and lfs uses object storage' do
let(:lfs_object) { create(:lfs_object, :with_file, oid: '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', size: '1575078') }
before do
stub_lfs_object_storage
lfs_object.file.migrate!(LfsObjectUploader::Store::REMOTE)
end
it 'responds with redirect to file' do
get_show(public_project, id)
expect(response).to have_gitlab_http_status(302)
expect(response.location).to include(lfs_object.reload.file.path)
end
it 'sets content disposition' do
get_show(public_project, id)
file_uri = URI.parse(response.location)
params = CGI.parse(file_uri.query)
expect(params["response-content-disposition"].first).to eq 'attachment;filename="lfs_object.iso"'
end
end
end
context 'when project does not have access' do
it 'does not serve the file' do
get_show(public_project, id)
expect(response).to have_gitlab_http_status(404)
end
end
end
context 'when lfs is not enabled' do
before do
allow_any_instance_of(Project).to receive(:lfs_enabled?).and_return(false)
end
it 'delivers ASCII file' do
get_show(public_project, id)
expect(response).to have_gitlab_http_status(200)
expect(response.header['Content-Type']).to eq('text/plain; charset=utf-8')
expect(response.header['Content-Disposition'])
.to eq('inline')
expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:')
end
end
it_behaves_like 'repository lfs file load' do
let(:filename) { 'lfs_object.iso' }
let(:filepath) { "be93687/files/lfs/#{filename}" }
end
end
def get_show(project, id)
get(:show, namespace_id: project.namespace.to_param,
project_id: project,
id: id)
end
end
......@@ -78,6 +78,18 @@ describe 'Admin updates settings' do
expect(page).to have_content "Application settings saved successfully"
end
it 'Change New users set to external', :js do
user_internal_regex = find('#application_setting_user_default_internal_regex', visible: :all)
expect(user_internal_regex).to be_readonly
expect(user_internal_regex['placeholder']).to eq 'To define internal users, first enable new users set to external'
check 'application_setting_user_default_external'
expect(user_internal_regex).not_to be_readonly
expect(user_internal_regex['placeholder']).to eq 'Regex pattern'
end
it 'Change Sign-in restrictions' do
page.within('.as-signin') do
fill_in 'Home page URL', with: 'https://about.gitlab.com/'
......
......@@ -125,6 +125,52 @@ describe "Admin::Users" do
expect(page).to have_content('Username can contain only letters, digits')
end
end
context 'with new users set to external enabled' do
context 'with regex to match internal user email address set', :js do
before do
stub_application_setting(user_default_external: true)
stub_application_setting(user_default_internal_regex: '.internal@')
visit new_admin_user_path
end
def expects_external_to_be_checked
expect(find('#user_external')).to be_checked
end
def expects_external_to_be_unchecked
expect(find('#user_external')).not_to be_checked
end
def expects_warning_to_be_hidden
expect(find('#warning_external_automatically_set', visible: :all)[:class]).to include 'hidden'
end
def expects_warning_to_be_shown
expect(find('#warning_external_automatically_set')[:class]).not_to include 'hidden'
end
it 'automatically unchecks external for matching email' do
expects_external_to_be_checked
expects_warning_to_be_hidden
fill_in 'user_email', with: 'test.internal@domain.ch'
expects_external_to_be_unchecked
expects_warning_to_be_shown
fill_in 'user_email', with: 'test@domain.ch'
expects_external_to_be_checked
expects_warning_to_be_hidden
uncheck 'user_external'
expects_warning_to_be_hidden
end
end
end
end
describe "GET /admin/users/:id" do
......
......@@ -122,7 +122,7 @@ describe 'Merge request > User sees diff', :js do
}
CONTENT
file_name = 'xss_file.txt'
file_name = 'xss_file.rs'
create_file('master', file_name, file_content)
merge_request = create(:merge_request, source_project: project)
......@@ -133,6 +133,7 @@ describe 'Merge request > User sees diff', :js do
visit diffs_project_merge_request_path(project, merge_request)
expect(page).to have_text("function foo<input> {")
expect(page).to have_css(".line[lang='rust'] .k")
end
end
end
......
......@@ -5,6 +5,7 @@
"commit",
"merged",
"protected",
"default",
"developers_can_push",
"developers_can_merge"
],
......@@ -13,6 +14,7 @@
"commit": { "$ref": "commit/basic.json" },
"merged": { "type": "boolean" },
"protected": { "type": "boolean" },
"default": { "type": "boolean" },
"developers_can_push": { "type": "boolean" },
"developers_can_merge": { "type": "boolean" },
"can_push": { "type": "boolean" }
......
......@@ -162,42 +162,77 @@ describe SubmoduleHelper do
end
context 'submodules with relative links' do
let(:group) { create(:group, name: "Master Project", path: "master-project") }
let(:group) { create(:group, name: "top group", path: "top-group") }
let(:project) { create(:project, group: group) }
let(:commit_id) { sample_commit[:id] }
let(:repo) { double(:repo, project: project) }
def expect_relative_link_to_resolve_to(relative_path, expected_path)
allow(repo).to receive(:submodule_url_for).and_return(relative_path)
result = submodule_links(submodule_item)
expect(result).to eq([expected_path, "#{expected_path}/tree/#{submodule_item.id}"])
end
it 'one level down' do
result = relative_self_links('../test.git', commit_id, project)
expect(result).to eq(["/#{group.path}/test", "/#{group.path}/test/tree/#{commit_id}"])
it 'handles project under same group' do
expect_relative_link_to_resolve_to('../test.git', "/#{group.path}/test")
end
it 'with trailing whitespace' do
result = relative_self_links('../test.git ', commit_id, project)
expect(result).to eq(["/#{group.path}/test", "/#{group.path}/test/tree/#{commit_id}"])
it 'handles trailing whitespace' do
expect_relative_link_to_resolve_to('../test.git ', "/#{group.path}/test")
end
it 'two levels down' do
result = relative_self_links('../../test.git', commit_id, project)
expect(result).to eq(["/#{group.path}/test", "/#{group.path}/test/tree/#{commit_id}"])
it 'handles project under another top group' do
expect_relative_link_to_resolve_to('../../baz/test.git ', "/baz/test")
end
context 'repo path resolves to be located at root (namespace absent)' do
it 'returns nil' do
allow(repo).to receive(:submodule_url_for).and_return('../../test.git')
result = submodule_links(submodule_item)
expect(result).to eq([nil, nil])
end
end
it 'one level down with namespace and repo' do
result = relative_self_links('../foobar/test.git', commit_id, project)
expect(result).to eq(["/foobar/test", "/foobar/test/tree/#{commit_id}"])
context 'repo path resolves to be located underneath current project path' do
it 'returns nil because it is not possible to have repo nested under another repo' do
allow(repo).to receive(:submodule_url_for).and_return('./test.git')
result = submodule_links(submodule_item)
expect(result).to eq([nil, nil])
end
end
it 'two levels down with namespace and repo' do
result = relative_self_links('../foobar/baz/test.git', commit_id, project)
expect(result).to eq(["/baz/test", "/baz/test/tree/#{commit_id}"])
context 'subgroup' do
let(:sub_group) { create(:group, parent: group, name: "sub group", path: "sub-group") }
let(:sub_project) { create(:project, group: sub_group) }
context 'project in sub group' do
let(:project) { sub_project }
it "handles referencing ancestor group's project" do
expect_relative_link_to_resolve_to('../../../top-group/test.git', "/#{group.path}/test")
end
end
it "handles referencing descendent group's project" do
expect_relative_link_to_resolve_to('../sub-group/test.git', "/top-group/sub-group/test")
end
it "handles referencing another top group's project" do
expect_relative_link_to_resolve_to('../../frontend/css/test.git', "/frontend/css/test")
end
end
context 'personal project' do
let(:user) { create(:user) }
let(:project) { create(:project, namespace: user.namespace) }
it 'one level down with personal project' do
result = relative_self_links('../test.git', commit_id, project)
expect(result).to eq(["/#{user.username}/test", "/#{user.username}/test/tree/#{commit_id}"])
it 'handles referencing another personal project' do
expect_relative_link_to_resolve_to('../test.git', "/#{user.username}/test")
end
end
end
......
......@@ -42,6 +42,30 @@ describe UsersHelper do
end
end
describe '#user_internal_regex_data' do
using RSpec::Parameterized::TableSyntax
where(:user_default_external, :user_default_internal_regex, :result) do
false | nil | { user_internal_regex_pattern: nil, user_internal_regex_options: nil }
false | '' | { user_internal_regex_pattern: nil, user_internal_regex_options: nil }
false | 'mockRegexPattern' | { user_internal_regex_pattern: nil, user_internal_regex_options: nil }
true | nil | { user_internal_regex_pattern: nil, user_internal_regex_options: nil }
true | '' | { user_internal_regex_pattern: nil, user_internal_regex_options: nil }
true | 'mockRegexPattern' | { user_internal_regex_pattern: 'mockRegexPattern', user_internal_regex_options: 'gi' }
end
with_them do
before do
stub_application_setting(user_default_external: user_default_external)
stub_application_setting(user_default_internal_regex: user_default_internal_regex)
end
subject { helper.user_internal_regex_data }
it { is_expected.to eq(result) }
end
end
describe '#current_user_menu_items' do
subject(:items) { helper.current_user_menu_items }
......
require 'spec_helper'
describe Admin::UsersController, '(JavaScript fixtures)', type: :controller do
include StubENV
include JavaScriptFixturesHelpers
let(:admin) { create(:admin) }
before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
sign_in(admin)
end
render_views
before(:all) do
clean_frontend_fixtures('admin/users')
end
it 'admin/users/new_with_internal_user_regex.html.raw' do |example|
stub_application_setting(user_default_external: true)
stub_application_setting(user_default_internal_regex: '^(?:(?!\.ext@).)*$\r?')
get :new
expect(response).to be_success
store_frontend_fixture(response, example.description)
end
end
require 'spec_helper'
describe Admin::ApplicationSettingsController, '(JavaScript fixtures)', type: :controller do
include StubENV
include JavaScriptFixturesHelpers
let(:admin) { create(:admin) }
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project_empty_repo, namespace: namespace, path: 'application-settings') }
before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
sign_in(admin)
end
render_views
before(:all) do
clean_frontend_fixtures('application_settings/')
end
after do
remove_repository(project)
end
it 'application_settings/accounts_and_limit.html.raw' do |example|
stub_application_setting(user_default_external: false)
get :show
expect(response).to be_success
store_frontend_fixture(response, example.description)
end
end
......@@ -21,6 +21,23 @@ describe('new dropdown upload', () => {
vm.$destroy();
});
describe('openFile', () => {
it('calls for each file', () => {
const files = ['test', 'test2', 'test3'];
spyOn(vm, 'readFile');
spyOnProperty(vm.$refs.fileUpload, 'files').and.returnValue(files);
vm.openFile();
expect(vm.readFile.calls.count()).toBe(3);
files.forEach((file, i) => {
expect(vm.readFile.calls.argsFor(i)).toEqual([file]);
});
});
});
describe('readFile', () => {
beforeEach(() => {
spyOn(FileReader.prototype, 'readAsText');
......
......@@ -121,4 +121,25 @@ describe('RepoFile', () => {
).toContain('Locked by testuser');
});
});
it('calls scrollIntoView if made active', done => {
createComponent({
file: {
...file(),
type: 'blob',
active: false,
},
level: 0,
});
spyOn(vm, 'scrollIntoView');
vm.file.active = true;
vm.$nextTick(() => {
expect(vm.scrollIntoView).toHaveBeenCalled();
done();
});
});
});
import $ from 'jquery';
import initUserInternalRegexPlaceholder, { PLACEHOLDER_USER_EXTERNAL_DEFAULT_FALSE,
PLACEHOLDER_USER_EXTERNAL_DEFAULT_TRUE } from '~/pages/admin/application_settings/account_and_limits';
describe('AccountAndLimits', () => {
const FIXTURE = 'application_settings/accounts_and_limit.html.raw';
let $userDefaultExternal;
let $userInternalRegex;
preloadFixtures(FIXTURE);
beforeEach(() => {
loadFixtures(FIXTURE);
initUserInternalRegexPlaceholder();
$userDefaultExternal = $('#application_setting_user_default_external');
$userInternalRegex = document.querySelector('#application_setting_user_default_internal_regex');
});
describe('Changing of userInternalRegex when userDefaultExternal', () => {
it('is unchecked', () => {
expect($userDefaultExternal.prop('checked')).toBeFalsy();
expect($userInternalRegex.placeholder).toEqual(PLACEHOLDER_USER_EXTERNAL_DEFAULT_FALSE);
expect($userInternalRegex.readOnly).toBeTruthy();
});
it('is checked', (done) => {
if (!$userDefaultExternal.prop('checked')) $userDefaultExternal.click();
expect($userDefaultExternal.prop('checked')).toBeTruthy();
expect($userInternalRegex.placeholder).toEqual(PLACEHOLDER_USER_EXTERNAL_DEFAULT_TRUE);
expect($userInternalRegex.readOnly).toBeFalsy();
done();
});
});
});
import $ from 'jquery';
import UserInternalRegexHandler from '~/pages/admin/users/new/index';
describe('UserInternalRegexHandler', () => {
const FIXTURE = 'admin/users/new_with_internal_user_regex.html.raw';
let $userExternal;
let $userEmail;
let $warningMessage;
preloadFixtures(FIXTURE);
beforeEach(() => {
loadFixtures(FIXTURE);
// eslint-disable-next-line no-new
new UserInternalRegexHandler();
$userExternal = $('#user_external');
$userEmail = $('#user_email');
$warningMessage = $('#warning_external_automatically_set');
if (!$userExternal.prop('checked')) $userExternal.prop('checked', 'checked');
});
describe('Behaviour of userExternal checkbox when', () => {
it('matches email as internal', (done) => {
expect($warningMessage.hasClass('hidden')).toBeTruthy();
$userEmail.val('test@').trigger('input');
expect($userExternal.prop('checked')).toBeFalsy();
expect($warningMessage.hasClass('hidden')).toBeFalsy();
done();
});
it('matches email as external', (done) => {
expect($warningMessage.hasClass('hidden')).toBeTruthy();
$userEmail.val('test.ext@').trigger('input');
expect($userExternal.prop('checked')).toBeTruthy();
expect($warningMessage.hasClass('hidden')).toBeTruthy();
done();
});
});
});
......@@ -3,53 +3,45 @@ import pdfjsLib from 'vendor/pdf';
import workerSrc from 'vendor/pdf.worker.min';
import PageComponent from '~/pdf/page/index.vue';
import testPDF from '../fixtures/blob/pdf/test.pdf';
const Component = Vue.extend(PageComponent);
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import testPDF from 'spec/fixtures/blob/pdf/test.pdf';
describe('Page component', () => {
const Component = Vue.extend(PageComponent);
let vm;
let testPage;
pdfjsLib.PDFJS.workerSrc = workerSrc;
const checkRendered = (done) => {
if (vm.rendering) {
setTimeout(() => {
checkRendered(done);
}, 100);
} else {
done();
}
};
beforeEach((done) => {
pdfjsLib.getDocument(testPDF)
beforeEach(done => {
pdfjsLib.PDFJS.workerSrc = workerSrc;
pdfjsLib
.getDocument(testPDF)
.then(pdf => pdf.getPage(1))
.then((page) => {
.then(page => {
testPage = page;
done();
})
.catch((error) => {
done.fail(error);
});
.then(done)
.catch(done.fail);
});
describe('render', () => {
beforeEach((done) => {
vm = new Component({
propsData: {
page: testPage,
number: 1,
},
});
vm.$mount();
afterEach(() => {
vm.$destroy();
});
checkRendered(done);
it('renders the page when mounting', done => {
const promise = Promise.resolve();
spyOn(testPage, 'render').and.callFake(() => promise);
vm = mountComponent(Component, {
page: testPage,
number: 1,
});
expect(vm.rendering).toBe(true);
it('renders first page', () => {
expect(vm.$el.tagName).toBeDefined();
});
promise
.then(() => {
expect(testPage.render).toHaveBeenCalledWith(vm.renderContext);
expect(vm.rendering).toBe(false);
})
.then(done)
.catch(done.fail);
});
});
# coding: utf-8
require "spec_helper"
describe Gitlab::EncodingHelper do
......@@ -187,4 +188,15 @@ describe Gitlab::EncodingHelper do
end
end
end
describe '#binary_stringio' do
it 'does not mutate the original string encoding' do
test = 'my-test'
io_stream = ext_class.binary_stringio(test)
expect(io_stream.external_encoding.name).to eq('ASCII-8BIT')
expect(test.encoding.name).to eq('UTF-8')
end
end
end
......@@ -63,6 +63,16 @@ describe Gitlab::ImportExport::Importer do
importer.execute
end
it 'sets the correct visibility_level when visibility level is a string' do
project.create_or_update_import_data(
data: { override_params: { visibility_level: Gitlab::VisibilityLevel::PRIVATE.to_s } }
)
importer.execute
expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
end
end
context 'when project successfully restored' do
......
......@@ -538,4 +538,28 @@ describe ApplicationSetting do
expect(setting.allow_signup?).to be_falsey
end
end
describe '#user_default_internal_regex_enabled?' do
using RSpec::Parameterized::TableSyntax
where(:user_default_external, :user_default_internal_regex, :result) do
false | nil | false
false | '' | false
false | '^(?:(?!\.ext@).)*$\r?\n?' | false
true | '' | false
true | nil | false
true | '^(?:(?!\.ext@).)*$\r?\n?' | true
end
with_them do
before do
setting.update(user_default_external: user_default_external)
setting.update(user_default_internal_regex: user_default_internal_regex)
end
subject { setting.user_default_internal_regex_enabled? }
it { is_expected.to eq(result) }
end
end
end
# frozen_string_literal: true
require 'spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../rubocop/cop/prefer_class_methods_over_module'
describe RuboCop::Cop::PreferClassMethodsOverModule do
include CopHelper
subject(:cop) { described_class.new }
it 'flags violation when using module ClassMethods' do
expect_offense(<<~RUBY)
module Foo
extend ActiveSupport::Concern
module ClassMethods
^^^^^^^^^^^^^^^^^^^ Do not use module ClassMethods, use class_methods block instead.
def a_class_method
end
end
end
RUBY
end
it "doesn't flag violation when using class_methods" do
expect_no_offenses(<<~RUBY)
module Foo
extend ActiveSupport::Concern
class_methods do
def a_class_method
end
end
end
RUBY
end
it "doesn't flag violation when module is not extending ActiveSupport::Concern" do
expect_no_offenses(<<~RUBY)
module Foo
module ClassMethods
def a_class_method
end
end
end
RUBY
end
it "doesn't flag violation when ClassMethods is used inside a class" do
expect_no_offenses(<<~RUBY)
class Foo
module ClassMethods
def a_class_method
end
end
end
RUBY
end
it "doesn't flag violation when not using either class_methods or ClassMethods" do
expect_no_offenses(<<~RUBY)
module Foo
extend ActiveSupport::Concern
def a_method
end
end
RUBY
end
it 'autocorrects ClassMethods into class_methods' do
source = <<~RUBY
module Foo
extend ActiveSupport::Concern
module ClassMethods
def a_class_method
end
end
end
RUBY
autocorrected = autocorrect_source(source)
expected_source = <<~RUBY
module Foo
extend ActiveSupport::Concern
class_methods do
def a_class_method
end
end
end
RUBY
expect(autocorrected).to eq(expected_source)
end
end
......@@ -169,6 +169,35 @@ describe Projects::TransferService do
it { expect(project.errors[:new_namespace]).to include('Cannot move project') }
end
context 'target namespace containing the same project name' do
before do
group.add_owner(user)
project.update(name: 'new_name')
create(:project, name: 'new_name', group: group, path: 'other')
@result = transfer_project(project, user, group)
end
it { expect(@result).to eq false }
it { expect(project.namespace).to eq(user.namespace) }
it { expect(project.errors[:new_namespace]).to include('Project with same name or path in target namespace already exists') }
end
context 'target namespace containing the same project path' do
before do
group.add_owner(user)
create(:project, name: 'other-name', path: project.path, group: group)
@result = transfer_project(project, user, group)
end
it { expect(@result).to eq false }
it { expect(project.namespace).to eq(user.namespace) }
it { expect(project.errors[:new_namespace]).to include('Project with same name or path in target namespace already exists') }
end
def transfer_project(project, user, new_namespace)
service = Projects::TransferService.new(project, user)
......
......@@ -13,6 +13,59 @@ describe Users::BuildService do
it 'returns a valid user' do
expect(service.execute).to be_valid
end
context 'with "user_default_external" application setting' do
using RSpec::Parameterized::TableSyntax
where(:user_default_external, :external, :email, :user_default_internal_regex, :result) do
true | nil | 'fl@example.com' | nil | true
true | true | 'fl@example.com' | nil | true
true | false | 'fl@example.com' | nil | false
true | nil | 'fl@example.com' | '' | true
true | true | 'fl@example.com' | '' | true
true | false | 'fl@example.com' | '' | false
true | nil | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false
true | true | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | true
true | false | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false
true | nil | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | true
true | true | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | true
true | false | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | false
false | nil | 'fl@example.com' | nil | false
false | true | 'fl@example.com' | nil | true
false | false | 'fl@example.com' | nil | false
false | nil | 'fl@example.com' | '' | false
false | true | 'fl@example.com' | '' | true
false | false | 'fl@example.com' | '' | false
false | nil | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false
false | true | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | true
false | false | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false
false | nil | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | false
false | true | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | true
false | false | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | false
end
with_them do
before do
stub_application_setting(user_default_external: user_default_external)
stub_application_setting(user_default_internal_regex: user_default_internal_regex)
params.merge!({ external: external, email: email }.compact)
end
subject(:user) { service.execute }
it 'correctly sets user.external' do
expect(user.external).to eq(result)
end
end
end
end
context 'with non admin user' do
......@@ -50,6 +103,59 @@ describe Users::BuildService do
expect(service.execute).to be_confirmed
end
end
context 'with "user_default_external" application setting' do
using RSpec::Parameterized::TableSyntax
where(:user_default_external, :external, :email, :user_default_internal_regex, :result) do
true | nil | 'fl@example.com' | nil | true
true | true | 'fl@example.com' | nil | true
true | false | 'fl@example.com' | nil | true
true | nil | 'fl@example.com' | '' | true
true | true | 'fl@example.com' | '' | true
true | false | 'fl@example.com' | '' | true
true | nil | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | true
true | true | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | true
true | false | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | true
true | nil | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | true
true | true | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | true
true | false | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | true
false | nil | 'fl@example.com' | nil | false
false | true | 'fl@example.com' | nil | false
false | false | 'fl@example.com' | nil | false
false | nil | 'fl@example.com' | '' | false
false | true | 'fl@example.com' | '' | false
false | false | 'fl@example.com' | '' | false
false | nil | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false
false | true | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false
false | false | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false
false | nil | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | false
false | true | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | false
false | false | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | false
end
with_them do
before do
stub_application_setting(user_default_external: user_default_external)
stub_application_setting(user_default_internal_regex: user_default_internal_regex)
params.merge!({ external: external, email: email }.compact)
end
subject(:user) { service.execute }
it 'sets the value of Gitlab::CurrentSettings.user_default_external' do
expect(user.external).to eq(result)
end
end
end
end
end
end
......@@ -42,6 +42,7 @@ Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
RSpec.configure do |config|
config.use_transactional_fixtures = false
config.use_instantiated_fixtures = false
config.fixture_path = Rails.root
config.verbose_retry = true
config.display_try_failure_messages = true
......
......@@ -11,6 +11,4 @@ RSpec.configure do |config|
config.include StubMetrics
config.include StubObjectStorage
config.include StubENV
config.fixture_path = Rails.root if defined?(Rails)
end
# frozen_string_literal: true
# Shared examples for controllers that load and send files from the git repository
# (like Projects::RawController or Projects::AvatarsController)
# These examples requires the following variables:
# - `project`
# - `filename`: filename of the file
# - `filepath`: path of the file (contains filename)
# - `subject`: the request to be made to the controller. Example:
# subject { get :show, namespace_id: project.namespace, project_id: project }
shared_examples 'repository lfs file load' do
context 'when file is stored in lfs' do
let(:lfs_oid) { '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897' }
let(:lfs_size) { '1575078' }
let!(:lfs_object) { create(:lfs_object, oid: lfs_oid, size: lfs_size) }
context 'when lfs is enabled' do
before do
allow_any_instance_of(Project).to receive(:lfs_enabled?).and_return(true)
end
context 'when project has access' do
before do
project.lfs_objects << lfs_object
allow_any_instance_of(LfsObjectUploader).to receive(:exists?).and_return(true)
allow(controller).to receive(:send_file) { controller.head :ok }
end
it 'serves the file' do
expect(controller).to receive(:send_file).with("#{LfsObjectUploader.root}/91/ef/f75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897", filename: filename, disposition: 'attachment')
subject
expect(response).to have_gitlab_http_status(200)
end
context 'and lfs uses object storage' do
let(:lfs_object) { create(:lfs_object, :with_file, oid: lfs_oid, size: lfs_size) }
before do
stub_lfs_object_storage
lfs_object.file.migrate!(LfsObjectUploader::Store::REMOTE)
end
it 'responds with redirect to file' do
subject
expect(response).to have_gitlab_http_status(302)
expect(response.location).to include(lfs_object.reload.file.path)
end
it 'sets content disposition' do
subject
file_uri = URI.parse(response.location)
params = CGI.parse(file_uri.query)
expect(params["response-content-disposition"].first).to eq "attachment;filename=\"#{filename}\""
end
end
end
context 'when project does not have access' do
it 'does not serve the file' do
subject
expect(response).to have_gitlab_http_status(404)
end
end
end
context 'when lfs is not enabled' do
before do
allow_any_instance_of(Project).to receive(:lfs_enabled?).and_return(false)
end
it 'delivers ASCII file' do
subject
expect(response).to have_gitlab_http_status(200)
expect(response.header['Content-Type']).to eq('text/plain; charset=utf-8')
expect(response.header['Content-Disposition'])
.to eq('inline')
expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:')
end
end
end
end
require 'spec_helper'
describe JsRegexValidator do
describe '#validates_each' do
using RSpec::Parameterized::TableSyntax
let(:validator) { described_class.new(attributes: [:user_default_internal_regex]) }
let(:application_setting) { build(:application_setting, user_default_external: true) }
where(:user_default_internal_regex, :result) do
nil | []
'' | []
'(?#comment)' | ['Regex Pattern (?#comment) can not be expressed in Javascript']
'(?(a)b|c)' | ['invalid conditional pattern: /(?(a)b|c)/i']
'[a-z&&[^uo]]' | ["Dropped unsupported set intersection '[a-z&&[^uo]]' at index 0",
"Dropped unsupported nested negative set data '[^uo]' at index 6"]
end
with_them do
it 'generates correct errors' do
validator.validate_each(application_setting, :user_default_internal_regex, user_default_internal_regex)
expect(application_setting.errors[:user_default_internal_regex]).to eq result
end
end
end
end
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