Commit 94a407d5 authored by Clement Ho's avatar Clement Ho

Merge branch 'master' into 'ee-refactor-protected-branches'

# Conflicts:
#   config/webpack.config.js
parents 0daa7bd9 50c66197
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.6-golang-1.9-git-2.14-chrome-63.0-node-8.x-yarn-1.2-postgresql-9.6" image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.6-golang-1.9-git-2.16-chrome-63.0-node-8.x-yarn-1.2-postgresql-9.6"
.dedicated-runner: &dedicated-runner .dedicated-runner: &dedicated-runner
retry: 1 retry: 1
......
...@@ -124,8 +124,8 @@ Lint/DuplicateMethods: ...@@ -124,8 +124,8 @@ Lint/DuplicateMethods:
- 'lib/gitlab/git/repository.rb' - 'lib/gitlab/git/repository.rb'
- 'lib/gitlab/git/tree.rb' - 'lib/gitlab/git/tree.rb'
- 'lib/gitlab/git/wiki_page.rb' - 'lib/gitlab/git/wiki_page.rb'
- 'lib/gitlab/ldap/person.rb' - 'lib/gitlab/auth/ldap/person.rb'
- 'lib/gitlab/o_auth/user.rb' - 'lib/gitlab/auth/o_auth/user.rb'
# Offense count: 4 # Offense count: 4
Lint/InterpolationCheck: Lint/InterpolationCheck:
...@@ -812,7 +812,7 @@ Style/TrivialAccessors: ...@@ -812,7 +812,7 @@ Style/TrivialAccessors:
Exclude: Exclude:
- 'app/models/external_issue.rb' - 'app/models/external_issue.rb'
- 'app/serializers/base_serializer.rb' - 'app/serializers/base_serializer.rb'
- 'lib/gitlab/ldap/person.rb' - 'lib/gitlab/auth/ldap/person.rb'
- 'lib/system_check/base_check.rb' - 'lib/system_check/base_check.rb'
# Offense count: 4 # Offense count: 4
......
...@@ -1064,7 +1064,7 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -1064,7 +1064,7 @@ Please view this file on the master branch, on stable branches it's out of date.
- Adds abitlity to render deploy boards in the frontend side. !1233 - Adds abitlity to render deploy boards in the frontend side. !1233
- Add filtered search to MR page. !1243 - Add filtered search to MR page. !1243
- Update project list API returns with approvals_before_merge attribute. !1245 (Geoff Webster) - Update project list API returns with approvals_before_merge attribute. !1245 (Geoff Webster)
- Catch Net::LDAP::DN exceptions in EE::Gitlab::LDAP::Group. !1260 - Catch Net::LDAP::DN exceptions in EE::Gitlab::Auth::LDAP::Group. !1260
- API: Use `post ":id/#{type}/:subscribable_id/subscribe"` to subscribe and `post ":id/#{type}/:subscribable_id/unsubscribe"` to unsubscribe from a resource. !1274 (Robert Schilling) - API: Use `post ":id/#{type}/:subscribable_id/subscribe"` to subscribe and `post ":id/#{type}/:subscribable_id/unsubscribe"` to unsubscribe from a resource. !1274 (Robert Schilling)
- API: Remove deprecated fields Notes#upvotes and Notes#downvotes. !1275 (Robert Schilling) - API: Remove deprecated fields Notes#upvotes and Notes#downvotes. !1275 (Robert Schilling)
- Deploy board backend. !1278 - Deploy board backend. !1278
......
...@@ -7,7 +7,7 @@ function onError() { ...@@ -7,7 +7,7 @@ function onError() {
return flash; return flash;
} }
function loadBalsamiqFile() { export default function loadBalsamiqFile() {
const viewer = document.getElementById('js-balsamiq-viewer'); const viewer = document.getElementById('js-balsamiq-viewer');
if (!(viewer instanceof Element)) return; if (!(viewer instanceof Element)) return;
...@@ -17,5 +17,3 @@ function loadBalsamiqFile() { ...@@ -17,5 +17,3 @@ function loadBalsamiqFile() {
const balsamiqViewer = new BalsamiqViewer(viewer); const balsamiqViewer = new BalsamiqViewer(viewer);
balsamiqViewer.loadFile(endpoint).catch(onError); balsamiqViewer.loadFile(endpoint).catch(onError);
} }
$(loadBalsamiqFile);
import renderNotebook from './notebook'; import renderNotebook from './notebook';
document.addEventListener('DOMContentLoaded', renderNotebook); export default renderNotebook;
import renderPDF from './pdf'; import renderPDF from './pdf';
document.addEventListener('DOMContentLoaded', renderPDF); export default renderPDF;
/* eslint-disable no-new */ /* eslint-disable no-new */
import SketchLoader from './sketch'; import SketchLoader from './sketch';
document.addEventListener('DOMContentLoaded', () => { export default () => {
const el = document.getElementById('js-sketch-viewer'); const el = document.getElementById('js-sketch-viewer');
new SketchLoader(el); new SketchLoader(el);
}); };
import Renderer from './3d_viewer'; import Renderer from './3d_viewer';
document.addEventListener('DOMContentLoaded', () => { export default () => {
const viewer = new Renderer(document.getElementById('js-stl-viewer')); const viewer = new Renderer(document.getElementById('js-stl-viewer'));
[].slice.call(document.querySelectorAll('.js-material-changer')).forEach((el) => { [].slice.call(document.querySelectorAll('.js-material-changer')).forEach((el) => {
...@@ -16,4 +16,4 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -16,4 +16,4 @@ document.addEventListener('DOMContentLoaded', () => {
viewer.changeObjectMaterials(target.dataset.type); viewer.changeObjectMaterials(target.dataset.type);
}); });
}); });
}); };
...@@ -5,6 +5,7 @@ import axios from '../../lib/utils/axios_utils'; ...@@ -5,6 +5,7 @@ import axios from '../../lib/utils/axios_utils';
export default class BlobViewer { export default class BlobViewer {
constructor() { constructor() {
BlobViewer.initAuxiliaryViewer(); BlobViewer.initAuxiliaryViewer();
BlobViewer.initRichViewer();
this.initMainViewers(); this.initMainViewers();
} }
...@@ -16,6 +17,38 @@ export default class BlobViewer { ...@@ -16,6 +17,38 @@ export default class BlobViewer {
BlobViewer.loadViewer(auxiliaryViewer); BlobViewer.loadViewer(auxiliaryViewer);
} }
static initRichViewer() {
const viewer = document.querySelector('.blob-viewer[data-type="rich"]');
if (!viewer || !viewer.dataset.richType) return;
const initViewer = promise => promise
.then(module => module.default(viewer))
.catch((error) => {
Flash('Error loading file viewer.');
throw error;
});
switch (viewer.dataset.richType) {
case 'balsamiq':
initViewer(import(/* webpackChunkName: 'balsamiq_viewer' */ '../balsamiq_viewer'));
break;
case 'notebook':
initViewer(import(/* webpackChunkName: 'notebook_viewer' */ '../notebook_viewer'));
break;
case 'pdf':
initViewer(import(/* webpackChunkName: 'pdf_viewer' */ '../pdf_viewer'));
break;
case 'sketch':
initViewer(import(/* webpackChunkName: 'sketch_viewer' */ '../sketch_viewer'));
break;
case 'stl':
initViewer(import(/* webpackChunkName: 'stl_viewer' */ '../stl_viewer'));
break;
default:
break;
}
}
initMainViewers() { initMainViewers() {
this.$fileHolder = $('.file-holder'); this.$fileHolder = $('.file-holder');
if (!this.$fileHolder.length) return; if (!this.$fileHolder.length) return;
......
import './dropdown_emoji';
import './dropdown_hint';
import './dropdown_non_user';
import './dropdown_user';
import './dropdown_utils';
import './filtered_search_dropdown_manager';
import './filtered_search_dropdown';
import './filtered_search_manager';
import './filtered_search_tokenizer';
import './filtered_search_visual_tokens';
...@@ -10,14 +10,22 @@ import DropdownUser from './dropdown_user'; ...@@ -10,14 +10,22 @@ import DropdownUser from './dropdown_user';
import FilteredSearchVisualTokens from './filtered_search_visual_tokens'; import FilteredSearchVisualTokens from './filtered_search_visual_tokens';
export default class FilteredSearchDropdownManager { export default class FilteredSearchDropdownManager {
constructor(baseEndpoint = '', tokenizer, page, isGroup, filteredSearchTokenKeys) { constructor({
baseEndpoint = '',
tokenizer,
page,
isGroup,
isGroupAncestor,
filteredSearchTokenKeys,
}) {
this.container = FilteredSearchContainer.container; this.container = FilteredSearchContainer.container;
this.baseEndpoint = baseEndpoint.replace(/\/$/, ''); this.baseEndpoint = baseEndpoint.replace(/\/$/, '');
this.tokenizer = tokenizer; this.tokenizer = tokenizer;
this.filteredSearchTokenKeys = filteredSearchTokenKeys || FilteredSearchTokenKeys; this.filteredSearchTokenKeys = filteredSearchTokenKeys || FilteredSearchTokenKeys;
this.filteredSearchInput = this.container.querySelector('.filtered-search'); this.filteredSearchInput = this.container.querySelector('.filtered-search');
this.page = page; this.page = page;
this.groupsOnly = page === 'boards' && isGroup; this.groupsOnly = isGroup;
this.groupAncestor = isGroupAncestor;
this.setupMapping(); this.setupMapping();
...@@ -60,7 +68,7 @@ export default class FilteredSearchDropdownManager { ...@@ -60,7 +68,7 @@ export default class FilteredSearchDropdownManager {
reference: null, reference: null,
gl: DropdownNonUser, gl: DropdownNonUser,
extraArguments: { extraArguments: {
endpoint: `${this.baseEndpoint}/milestones.json${this.groupsOnly ? '?only_group_milestones=true' : ''}`, endpoint: this.getMilestoneEndpoint(),
symbol: '%', symbol: '%',
}, },
element: this.container.querySelector('#js-dropdown-milestone'), element: this.container.querySelector('#js-dropdown-milestone'),
...@@ -69,7 +77,7 @@ export default class FilteredSearchDropdownManager { ...@@ -69,7 +77,7 @@ export default class FilteredSearchDropdownManager {
reference: null, reference: null,
gl: DropdownNonUser, gl: DropdownNonUser,
extraArguments: { extraArguments: {
endpoint: `${this.baseEndpoint}/labels.json${this.groupsOnly ? '?only_group_labels=true' : ''}`, endpoint: this.getLabelsEndpoint(),
symbol: '~', symbol: '~',
preprocessing: DropdownUtils.duplicateLabelPreprocessing, preprocessing: DropdownUtils.duplicateLabelPreprocessing,
}, },
...@@ -96,6 +104,28 @@ export default class FilteredSearchDropdownManager { ...@@ -96,6 +104,28 @@ export default class FilteredSearchDropdownManager {
this.mapping = allowedMappings; this.mapping = allowedMappings;
} }
getMilestoneEndpoint() {
let endpoint = `${this.baseEndpoint}/milestones.json`;
// EE-only
if (this.groupsOnly) {
endpoint = `${endpoint}?only_group_milestones=true`;
}
return endpoint;
}
getLabelsEndpoint() {
let endpoint = `${this.baseEndpoint}/labels.json`;
// EE-only
if (this.groupsOnly) {
endpoint = `${endpoint}?only_group_labels=true`;
}
return endpoint;
}
static addWordToInput(tokenName, tokenValue = '', clicked = false) { static addWordToInput(tokenName, tokenValue = '', clicked = false) {
const input = FilteredSearchContainer.container.querySelector('.filtered-search'); const input = FilteredSearchContainer.container.querySelector('.filtered-search');
......
...@@ -20,10 +20,13 @@ import DropdownUtils from './dropdown_utils'; ...@@ -20,10 +20,13 @@ import DropdownUtils from './dropdown_utils';
export default class FilteredSearchManager { export default class FilteredSearchManager {
constructor({ constructor({
page, page,
isGroup = false,
isGroupAncestor = false,
filteredSearchTokenKeys = FilteredSearchTokenKeys, filteredSearchTokenKeys = FilteredSearchTokenKeys,
stateFiltersSelector = '.issues-state-filters', stateFiltersSelector = '.issues-state-filters',
}) { }) {
this.isGroup = false; this.isGroup = isGroup;
this.isGroupAncestor = isGroupAncestor;
this.states = ['opened', 'closed', 'merged', 'all']; this.states = ['opened', 'closed', 'merged', 'all'];
this.page = page; this.page = page;
...@@ -98,13 +101,14 @@ export default class FilteredSearchManager { ...@@ -98,13 +101,14 @@ export default class FilteredSearchManager {
if (this.filteredSearchInput) { if (this.filteredSearchInput) {
this.tokenizer = FilteredSearchTokenizer; this.tokenizer = FilteredSearchTokenizer;
this.dropdownManager = new FilteredSearchDropdownManager( this.dropdownManager = new FilteredSearchDropdownManager({
this.filteredSearchInput.getAttribute('data-base-endpoint') || '', baseEndpoint: this.filteredSearchInput.getAttribute('data-base-endpoint') || '',
this.tokenizer, tokenizer: this.tokenizer,
this.page, page: this.page,
this.isGroup, isGroup: this.isGroup,
this.filteredSearchTokenKeys, isGroupAncestor: this.isGroupAncestor,
); filteredSearchTokenKeys: this.filteredSearchTokenKeys,
});
this.recentSearchesRoot = new RecentSearchesRoot( this.recentSearchesRoot = new RecentSearchesRoot(
this.recentSearchesStore, this.recentSearchesStore,
......
import FilteredSearchManager from '~/filtered_search/filtered_search_manager'; import FilteredSearchManager from '~/filtered_search/filtered_search_manager';
export default ({ page, filteredSearchTokenKeys, stateFiltersSelector }) => { export default ({
page,
filteredSearchTokenKeys,
isGroup,
isGroupAncestor,
stateFiltersSelector,
}) => {
const filteredSearchEnabled = FilteredSearchManager && document.querySelector('.filtered-search'); const filteredSearchEnabled = FilteredSearchManager && document.querySelector('.filtered-search');
if (filteredSearchEnabled) { if (filteredSearchEnabled) {
const filteredSearchManager = new FilteredSearchManager({ const filteredSearchManager = new FilteredSearchManager({
page, page,
isGroup,
isGroupAncestor,
filteredSearchTokenKeys, filteredSearchTokenKeys,
stateFiltersSelector, stateFiltersSelector,
}); });
......
...@@ -316,7 +316,7 @@ ...@@ -316,7 +316,7 @@
v-if="pipeline.flags.cancelable" v-if="pipeline.flags.cancelable"
:endpoint="pipeline.cancel_path" :endpoint="pipeline.cancel_path"
css-class="js-pipelines-cancel-button btn-remove" css-class="js-pipelines-cancel-button btn-remove"
title="Cancel" title="Stop"
icon="close" icon="close"
:pipeline-id="pipeline.id" :pipeline-id="pipeline.id"
data-toggle="modal" data-toggle="modal"
......
...@@ -14,6 +14,11 @@ ...@@ -14,6 +14,11 @@
collapsedCalendarIcon, collapsedCalendarIcon,
}, },
props: { props: {
blockClass: {
type: String,
required: false,
default: '',
},
collapsed: { collapsed: {
type: Boolean, type: Boolean,
required: false, required: false,
...@@ -91,7 +96,10 @@ ...@@ -91,7 +96,10 @@
</script> </script>
<template> <template>
<div class="block"> <div
class="block"
:class="blockClass"
>
<div class="issuable-sidebar-header"> <div class="issuable-sidebar-header">
<toggle-sidebar <toggle-sidebar
:collapsed="collapsed" :collapsed="collapsed"
......
...@@ -199,7 +199,7 @@ class ApplicationController < ActionController::Base ...@@ -199,7 +199,7 @@ class ApplicationController < ActionController::Base
return unless signed_in? && session[:service_tickets] return unless signed_in? && session[:service_tickets]
valid = session[:service_tickets].all? do |provider, ticket| valid = session[:service_tickets].all? do |provider, ticket|
Gitlab::OAuth::Session.valid?(provider, ticket) Gitlab::Auth::OAuth::Session.valid?(provider, ticket)
end end
unless valid unless valid
...@@ -223,7 +223,7 @@ class ApplicationController < ActionController::Base ...@@ -223,7 +223,7 @@ class ApplicationController < ActionController::Base
if current_user && current_user.requires_ldap_check? if current_user && current_user.requires_ldap_check?
return unless current_user.try_obtain_ldap_lease return unless current_user.try_obtain_ldap_lease
unless Gitlab::LDAP::Access.allowed?(current_user) unless Gitlab::Auth::LDAP::Access.allowed?(current_user)
sign_out current_user sign_out current_user
flash[:alert] = "Access denied for your LDAP account." flash[:alert] = "Access denied for your LDAP account."
redirect_to new_user_session_path redirect_to new_user_session_path
...@@ -238,7 +238,7 @@ class ApplicationController < ActionController::Base ...@@ -238,7 +238,7 @@ class ApplicationController < ActionController::Base
end end
def gitlab_ldap_access(&block) def gitlab_ldap_access(&block)
Gitlab::LDAP::Access.open { |access| yield(access) } Gitlab::Auth::LDAP::Access.open { |access| yield(access) }
end end
# JSON for infinite scroll via Pager object # JSON for infinite scroll via Pager object
...@@ -292,7 +292,7 @@ class ApplicationController < ActionController::Base ...@@ -292,7 +292,7 @@ class ApplicationController < ActionController::Base
end end
def github_import_configured? def github_import_configured?
Gitlab::OAuth::Provider.enabled?(:github) Gitlab::Auth::OAuth::Provider.enabled?(:github)
end end
def gitlab_import_enabled? def gitlab_import_enabled?
...@@ -300,7 +300,7 @@ class ApplicationController < ActionController::Base ...@@ -300,7 +300,7 @@ class ApplicationController < ActionController::Base
end end
def gitlab_import_configured? def gitlab_import_configured?
Gitlab::OAuth::Provider.enabled?(:gitlab) Gitlab::Auth::OAuth::Provider.enabled?(:gitlab)
end end
def bitbucket_import_enabled? def bitbucket_import_enabled?
...@@ -308,7 +308,7 @@ class ApplicationController < ActionController::Base ...@@ -308,7 +308,7 @@ class ApplicationController < ActionController::Base
end end
def bitbucket_import_configured? def bitbucket_import_configured?
Gitlab::OAuth::Provider.enabled?(:bitbucket) Gitlab::Auth::OAuth::Provider.enabled?(:bitbucket)
end end
def google_code_import_enabled? def google_code_import_enabled?
......
...@@ -71,7 +71,7 @@ class Import::BitbucketController < Import::BaseController ...@@ -71,7 +71,7 @@ class Import::BitbucketController < Import::BaseController
end end
def provider def provider
Gitlab::OAuth::Provider.config_for('bitbucket') Gitlab::Auth::OAuth::Provider.config_for('bitbucket')
end end
def options def options
......
...@@ -11,8 +11,8 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -11,8 +11,8 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
end end
end end
if Gitlab::LDAP::Config.enabled? if Gitlab::Auth::LDAP::Config.enabled?
Gitlab::LDAP::Config.available_servers.each do |server| Gitlab::Auth::LDAP::Config.available_servers.each do |server|
define_method server['provider_name'] do define_method server['provider_name'] do
ldap ldap
end end
...@@ -32,7 +32,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -32,7 +32,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
# We only find ourselves here # We only find ourselves here
# if the authentication to LDAP was successful. # if the authentication to LDAP was successful.
def ldap def ldap
ldap_user = Gitlab::LDAP::User.new(oauth) ldap_user = Gitlab::Auth::LDAP::User.new(oauth)
ldap_user.save if ldap_user.changed? # will also save new users ldap_user.save if ldap_user.changed? # will also save new users
@user = ldap_user.gl_user @user = ldap_user.gl_user
...@@ -65,13 +65,13 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -65,13 +65,13 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
redirect_to after_sign_in_path_for(current_user) redirect_to after_sign_in_path_for(current_user)
end end
else else
saml_user = Gitlab::Saml::User.new(oauth) saml_user = Gitlab::Auth::Saml::User.new(oauth)
saml_user.save if saml_user.changed? saml_user.save if saml_user.changed?
@user = saml_user.gl_user @user = saml_user.gl_user
continue_login_process continue_login_process
end end
rescue Gitlab::OAuth::SignupDisabledError rescue Gitlab::Auth::OAuth::User::SignupDisabledError
handle_signup_error handle_signup_error
end end
...@@ -119,20 +119,20 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -119,20 +119,20 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
log_audit_event(current_user, with: oauth['provider']) log_audit_event(current_user, with: oauth['provider'])
redirect_to profile_account_path, notice: 'Authentication method updated' redirect_to profile_account_path, notice: 'Authentication method updated'
else else
oauth_user = Gitlab::OAuth::User.new(oauth) oauth_user = Gitlab::Auth::OAuth::User.new(oauth)
oauth_user.save oauth_user.save
@user = oauth_user.gl_user @user = oauth_user.gl_user
continue_login_process continue_login_process
end end
rescue Gitlab::OAuth::SigninDisabledForProviderError rescue Gitlab::Auth::OAuth::User::SigninDisabledForProviderError
handle_disabled_provider handle_disabled_provider
rescue Gitlab::OAuth::SignupDisabledError rescue Gitlab::Auth::OAuth::User::SignupDisabledError
handle_signup_error handle_signup_error
end end
def handle_service_ticket(provider, ticket) def handle_service_ticket(provider, ticket)
Gitlab::OAuth::Session.create provider, ticket Gitlab::Auth::OAuth::Session.create provider, ticket
session[:service_tickets] ||= {} session[:service_tickets] ||= {}
session[:service_tickets][provider] = ticket session[:service_tickets][provider] = ticket
end end
...@@ -155,7 +155,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -155,7 +155,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
end end
def handle_signup_error def handle_signup_error
label = Gitlab::OAuth::Provider.label_for(oauth['provider']) label = Gitlab::Auth::OAuth::Provider.label_for(oauth['provider'])
message = "Signing in using your #{label} account without a pre-existing GitLab account is not allowed." message = "Signing in using your #{label} account without a pre-existing GitLab account is not allowed."
if Gitlab::CurrentSettings.allow_signup? if Gitlab::CurrentSettings.allow_signup?
...@@ -184,7 +184,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -184,7 +184,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
end end
def handle_disabled_provider def handle_disabled_provider
label = Gitlab::OAuth::Provider.label_for(oauth['provider']) label = Gitlab::Auth::OAuth::Provider.label_for(oauth['provider'])
flash[:alert] = "Signing in using #{label} has been disabled" flash[:alert] = "Signing in using #{label} has been disabled"
redirect_to new_user_session_path redirect_to new_user_session_path
......
class Profiles::PasswordsController < Profiles::ApplicationController class Profiles::PasswordsController < Profiles::ApplicationController
skip_before_action :check_password_expiration, only: [:new, :create] skip_before_action :check_password_expiration, only: [:new, :create]
skip_before_action :check_two_factor_requirement, only: [:new, :create]
before_action :set_user before_action :set_user
before_action :authorize_change_password! before_action :authorize_change_password!
......
...@@ -17,7 +17,7 @@ class SessionsController < Devise::SessionsController ...@@ -17,7 +17,7 @@ class SessionsController < Devise::SessionsController
def new def new
set_minimum_password_length set_minimum_password_length
@ldap_servers = Gitlab::LDAP::Config.available_servers @ldap_servers = Gitlab::Auth::LDAP::Config.available_servers
super super
end end
......
...@@ -78,7 +78,7 @@ module ApplicationSettingsHelper ...@@ -78,7 +78,7 @@ module ApplicationSettingsHelper
label_tag(checkbox_name, class: css_class) do label_tag(checkbox_name, class: css_class) do
check_box_tag(checkbox_name, source, !disabled, check_box_tag(checkbox_name, source, !disabled,
autocomplete: 'off') + Gitlab::OAuth::Provider.label_for(source) autocomplete: 'off') + Gitlab::Auth::OAuth::Provider.label_for(source)
end end
end end
end end
......
...@@ -5,7 +5,7 @@ module AuthHelper ...@@ -5,7 +5,7 @@ module AuthHelper
delegate :slack_app_id, to: :'Gitlab::CurrentSettings.current_application_settings' delegate :slack_app_id, to: :'Gitlab::CurrentSettings.current_application_settings'
def ldap_enabled? def ldap_enabled?
Gitlab::LDAP::Config.enabled? Gitlab::Auth::LDAP::Config.enabled?
end end
def kerberos_enabled? def kerberos_enabled?
...@@ -21,11 +21,11 @@ module AuthHelper ...@@ -21,11 +21,11 @@ module AuthHelper
end end
def auth_providers def auth_providers
Gitlab::OAuth::Provider.providers Gitlab::Auth::OAuth::Provider.providers
end end
def label_for_provider(name) def label_for_provider(name)
Gitlab::OAuth::Provider.label_for(name) Gitlab::Auth::OAuth::Provider.label_for(name)
end end
def form_based_provider?(name) def form_based_provider?(name)
......
...@@ -3,7 +3,7 @@ module ProfilesHelper ...@@ -3,7 +3,7 @@ module ProfilesHelper
user_synced_attributes_metadata = current_user.user_synced_attributes_metadata user_synced_attributes_metadata = current_user.user_synced_attributes_metadata
if user_synced_attributes_metadata&.synced?(attribute) if user_synced_attributes_metadata&.synced?(attribute)
if user_synced_attributes_metadata.provider if user_synced_attributes_metadata.provider
Gitlab::OAuth::Provider.label_for(user_synced_attributes_metadata.provider) Gitlab::Auth::OAuth::Provider.label_for(user_synced_attributes_metadata.provider)
else else
'LDAP' 'LDAP'
end end
......
...@@ -25,7 +25,7 @@ module SelectsHelper ...@@ -25,7 +25,7 @@ module SelectsHelper
def ldap_server_select_options def ldap_server_select_options
options_from_collection_for_select( options_from_collection_for_select(
Gitlab::LDAP::Config.available_servers, Gitlab::Auth::LDAP::Config.available_servers,
'provider_name', 'provider_name',
'label' 'label'
) )
......
...@@ -19,12 +19,12 @@ class Identity < ActiveRecord::Base ...@@ -19,12 +19,12 @@ class Identity < ActiveRecord::Base
end end
def ldap? def ldap?
Gitlab::OAuth::Provider.ldap_provider?(provider) Gitlab::Auth::OAuth::Provider.ldap_provider?(provider)
end end
def self.normalize_uid(provider, uid) def self.normalize_uid(provider, uid)
if Gitlab::OAuth::Provider.ldap_provider?(provider) if Gitlab::Auth::OAuth::Provider.ldap_provider?(provider)
Gitlab::LDAP::Person.normalize_dn(uid) Gitlab::Auth::LDAP::Person.normalize_dn(uid)
else else
uid.to_s uid.to_s
end end
......
...@@ -146,7 +146,7 @@ class Repository ...@@ -146,7 +146,7 @@ class Repository
end end
end end
def commits(ref, path: nil, limit: nil, offset: nil, skip_merges: false, after: nil, before: nil) def commits(ref = nil, path: nil, limit: nil, offset: nil, skip_merges: false, after: nil, before: nil, all: nil)
options = { options = {
repo: raw_repository, repo: raw_repository,
ref: ref, ref: ref,
...@@ -156,7 +156,8 @@ class Repository ...@@ -156,7 +156,8 @@ class Repository
after: after, after: after,
before: before, before: before,
follow: Array(path).length == 1, follow: Array(path).length == 1,
skip_merges: skip_merges skip_merges: skip_merges,
all: all
} }
commits = Gitlab::Git::Commit.where(options) commits = Gitlab::Git::Commit.where(options)
......
...@@ -746,7 +746,7 @@ class User < ActiveRecord::Base ...@@ -746,7 +746,7 @@ class User < ActiveRecord::Base
def ldap_user? def ldap_user?
if identities.loaded? if identities.loaded?
identities.find { |identity| Gitlab::OAuth::Provider.ldap_provider?(identity.provider) && !identity.extern_uid.nil? } identities.find { |identity| Gitlab::Auth::OAuth::Provider.ldap_provider?(identity.provider) && !identity.extern_uid.nil? }
else else
identities.exists?(["provider LIKE ? AND extern_uid IS NOT NULL", "ldap%"]) identities.exists?(["provider LIKE ? AND extern_uid IS NOT NULL", "ldap%"])
end end
......
...@@ -26,6 +26,6 @@ class UserSyncedAttributesMetadata < ActiveRecord::Base ...@@ -26,6 +26,6 @@ class UserSyncedAttributesMetadata < ActiveRecord::Base
private private
def sync_profile_from_provider? def sync_profile_from_provider?
Gitlab::OAuth::Provider.sync_profile_from_provider?(provider) Gitlab::Auth::OAuth::Provider.sync_profile_from_provider?(provider)
end end
end end
...@@ -203,7 +203,7 @@ ...@@ -203,7 +203,7 @@
Password authentication enabled for Git over HTTP(S) Password authentication enabled for Git over HTTP(S)
.help-block .help-block
When disabled, a Personal Access Token When disabled, a Personal Access Token
- if Gitlab::LDAP::Config.enabled? - if Gitlab::Auth::LDAP::Config.enabled?
or LDAP password or LDAP password
must be used to authenticate. must be used to authenticate.
- if omniauth_enabled? && button_based_providers.any? - if omniauth_enabled? && button_based_providers.any?
......
...@@ -41,7 +41,7 @@ ...@@ -41,7 +41,7 @@
= f.submit 'Save changes', class: "btn btn-save" = f.submit 'Save changes', class: "btn btn-save"
= link_to 'Cancel', admin_group_path(@group), class: "btn btn-cancel" = link_to 'Cancel', admin_group_path(@group), class: "btn btn-cancel"
- if @group.persisted? && Gitlab::LDAP::Config.group_sync_enabled? - if @group.persisted? && Gitlab::Auth::LDAP::Config.group_sync_enabled?
%h3.page-title LDAP synchronizations %h3.page-title LDAP synchronizations
= render 'ldap_group_links/form', group: @group = render 'ldap_group_links/form', group: @group
= render 'ldap_group_links/ldap_group_links', group: @group = render 'ldap_group_links/ldap_group_links', group: @group
...@@ -62,7 +62,7 @@ ...@@ -62,7 +62,7 @@
= render partial: "namespaces/shared_runner_status", locals: { namespace: @group } = render partial: "namespaces/shared_runner_status", locals: { namespace: @group }
- if Gitlab::LDAP::Config.group_sync_enabled? && @group.ldap_synced? - if Gitlab::Auth::LDAP::Config.group_sync_enabled? && @group.ldap_synced?
.panel.panel-default .panel.panel-default
.panel-heading Active synchronizations .panel-heading Active synchronizations
%ul.well-list %ul.well-list
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
.form-group .form-group
= f.label :provider, class: 'control-label' = f.label :provider, class: 'control-label'
.col-sm-10 .col-sm-10
- values = Gitlab::OAuth::Provider.providers.map { |name| ["#{Gitlab::OAuth::Provider.label_for(name)} (#{name})", name] } - values = Gitlab::Auth::OAuth::Provider.providers.map { |name| ["#{Gitlab::Auth::OAuth::Provider.label_for(name)} (#{name})", name] }
= f.select :provider, values, { allow_blank: false }, class: 'form-control' = f.select :provider, values, { allow_blank: false }, class: 'form-control'
.form-group .form-group
= f.label :extern_uid, "Identifier", class: 'control-label' = f.label :extern_uid, "Identifier", class: 'control-label'
......
%tr %tr
%td %td
#{Gitlab::OAuth::Provider.label_for(identity.provider)} (#{identity.provider}) #{Gitlab::Auth::OAuth::Provider.label_for(identity.provider)} (#{identity.provider})
%td %td
= identity.extern_uid = identity.extern_uid
%td %td
......
- hidden = local_assigns.fetch(:hidden, false) - hidden = local_assigns.fetch(:hidden, false)
- render_error = viewer.render_error - render_error = viewer.render_error
- rich_type = viewer.type == :rich ? viewer.partial_name : nil
- load_async = local_assigns.fetch(:load_async, viewer.load_async? && render_error.nil?) - load_async = local_assigns.fetch(:load_async, viewer.load_async? && render_error.nil?)
- viewer_url = local_assigns.fetch(:viewer_url) { url_for(params.merge(viewer: viewer.type, format: :json)) } if load_async - viewer_url = local_assigns.fetch(:viewer_url) { url_for(params.merge(viewer: viewer.type, format: :json)) } if load_async
.blob-viewer{ data: { type: viewer.type, url: viewer_url }, class: ('hidden' if hidden) } .blob-viewer{ data: { type: viewer.type, rich_type: rich_type, url: viewer_url }, class: ('hidden' if hidden) }
- if render_error - if render_error
= render 'projects/blob/render_error', viewer: viewer = render 'projects/blob/render_error', viewer: viewer
- elsif load_async - elsif load_async
......
- content_for :page_specific_javascripts do
= webpack_bundle_tag('balsamiq_viewer')
.file-content.balsamiq-viewer#js-balsamiq-viewer{ data: { endpoint: blob_raw_path } } .file-content.balsamiq-viewer#js-balsamiq-viewer{ data: { endpoint: blob_raw_path } }
- content_for :page_specific_javascripts do
= webpack_bundle_tag('common_vue')
= webpack_bundle_tag('notebook_viewer')
.file-content#js-notebook-viewer{ data: { endpoint: blob_raw_path } } .file-content#js-notebook-viewer{ data: { endpoint: blob_raw_path } }
- content_for :page_specific_javascripts do
= webpack_bundle_tag('common_vue')
= webpack_bundle_tag('pdf_viewer')
.file-content#js-pdf-viewer{ data: { endpoint: blob_raw_path } } .file-content#js-pdf-viewer{ data: { endpoint: blob_raw_path } }
- content_for :page_specific_javascripts do
= webpack_bundle_tag('common_vue')
= webpack_bundle_tag('sketch_viewer')
.file-content#js-sketch-viewer{ data: { endpoint: blob_raw_path } } .file-content#js-sketch-viewer{ data: { endpoint: blob_raw_path } }
.js-loading-icon.text-center.prepend-top-default.append-bottom-default.js-loading-icon{ 'aria-label' => 'Loading Sketch preview' } .js-loading-icon.text-center.prepend-top-default.append-bottom-default.js-loading-icon{ 'aria-label' => 'Loading Sketch preview' }
= icon('spinner spin 2x', 'aria-hidden' => 'true'); = icon('spinner spin 2x', 'aria-hidden' => 'true');
- content_for :page_specific_javascripts do
= webpack_bundle_tag('stl_viewer')
.file-content.is-stl-loading .file-content.is-stl-loading
.text-center#js-stl-viewer{ data: { endpoint: blob_raw_path } } .text-center#js-stl-viewer{ data: { endpoint: blob_raw_path } }
= icon('spinner spin 2x', class: 'prepend-top-default append-bottom-default', 'aria-hidden' => 'true', 'aria-label' => 'Loading') = icon('spinner spin 2x', class: 'prepend-top-default append-bottom-default', 'aria-hidden' => 'true', 'aria-label' => 'Loading')
......
---
title: Moved o_auth/saml/ldap modules under gitlab/auth
merge_request: 17359
author: Horatiu Eugen Vlad
---
title: Allow commits endpoint to work over all commits of a repository
merge_request: 17182
author:
type: added
---
title: Update tooltip on pipeline cancel to Stop (#42946)
merge_request: 17444
author:
type: fixed
---
title: Add NOT NULL constraint to projects.namespace_id
merge_request: 17448
author:
type: other
---
title: Removing the two factor check when the user sets a new password
merge_request: 17457
author:
type: fixed
---
title: Encode branch name as binary before creating a RPC request to copy attributes
merge_request: 17291
author:
type: fixed
...@@ -212,9 +212,9 @@ Devise.setup do |config| ...@@ -212,9 +212,9 @@ Devise.setup do |config|
# manager.default_strategies(scope: :user).unshift :some_external_strategy # manager.default_strategies(scope: :user).unshift :some_external_strategy
# end # end
if Gitlab::LDAP::Config.enabled? if Gitlab::Auth::LDAP::Config.enabled?
Gitlab::LDAP::Config.providers.each do |provider| Gitlab::Auth::LDAP::Config.providers.each do |provider|
ldap_config = Gitlab::LDAP::Config.new(provider) ldap_config = Gitlab::Auth::LDAP::Config.new(provider)
config.omniauth(provider, ldap_config.omniauth_options) config.omniauth(provider, ldap_config.omniauth_options)
end end
end end
...@@ -235,9 +235,9 @@ Devise.setup do |config| ...@@ -235,9 +235,9 @@ Devise.setup do |config|
if provider['name'] == 'cas3' if provider['name'] == 'cas3'
provider['args'][:on_single_sign_out] = lambda do |request| provider['args'][:on_single_sign_out] = lambda do |request|
ticket = request.params[:session_index] ticket = request.params[:session_index]
raise "Service Ticket not found." unless Gitlab::OAuth::Session.valid?(:cas3, ticket) raise "Service Ticket not found." unless Gitlab::Auth::OAuth::Session.valid?(:cas3, ticket)
Gitlab::OAuth::Session.destroy(:cas3, ticket) Gitlab::Auth::OAuth::Session.destroy(:cas3, ticket)
true true
end end
end end
...@@ -245,8 +245,8 @@ Devise.setup do |config| ...@@ -245,8 +245,8 @@ Devise.setup do |config|
if provider['name'] == 'authentiq' if provider['name'] == 'authentiq'
provider['args'][:remote_sign_out_handler] = lambda do |request| provider['args'][:remote_sign_out_handler] = lambda do |request|
authentiq_session = request.params['sid'] authentiq_session = request.params['sid']
if Gitlab::OAuth::Session.valid?(:authentiq, authentiq_session) if Gitlab::Auth::OAuth::Session.valid?(:authentiq, authentiq_session)
Gitlab::OAuth::Session.destroy(:authentiq, authentiq_session) Gitlab::Auth::OAuth::Session.destroy(:authentiq, authentiq_session)
true true
else else
false false
......
...@@ -18,13 +18,26 @@ module Sidekiq ...@@ -18,13 +18,26 @@ module Sidekiq
%i(perform_async perform_at perform_in).each do |name| %i(perform_async perform_at perform_in).each do |name|
define_method(name) do |*args| define_method(name) do |*args|
if !Sidekiq::Worker.skip_transaction_check && AfterCommitQueue.inside_transaction? if !Sidekiq::Worker.skip_transaction_check && AfterCommitQueue.inside_transaction?
raise Sidekiq::Worker::EnqueueFromTransactionError, <<~MSG begin
raise Sidekiq::Worker::EnqueueFromTransactionError, <<~MSG
`#{self}.#{name}` cannot be called inside a transaction as this can lead to `#{self}.#{name}` cannot be called inside a transaction as this can lead to
race conditions when the worker runs before the transaction is committed and race conditions when the worker runs before the transaction is committed and
tries to access a model that has not been saved yet. tries to access a model that has not been saved yet.
Use an `after_commit` hook, or include `AfterCommitQueue` and use a `run_after_commit` block instead. Use an `after_commit` hook, or include `AfterCommitQueue` and use a `run_after_commit` block instead.
MSG MSG
rescue Sidekiq::Worker::EnqueueFromTransactionError => e
if Rails.env.production?
Rails.logger.error(e.message)
if Gitlab::Sentry.enabled?
Gitlab::Sentry.context
Raven.capture_exception(e)
end
else
raise
end
end
end end
super(*args) super(*args)
......
if Gitlab::LDAP::Config.enabled? if Gitlab::Auth::LDAP::Config.enabled?
module OmniAuth::Strategies module OmniAuth::Strategies
Gitlab::LDAP::Config.available_servers.each do |server| Gitlab::Auth::LDAP::Config.available_servers.each do |server|
# do not redeclare LDAP # do not redeclare LDAP
next if server['provider_name'] == 'ldap' next if server['provider_name'] == 'ldap'
......
API::API.logger Rails.logger ::API::API.logger Rails.logger
mount API::API => '/' mount ::API::API => '/'
...@@ -48,15 +48,9 @@ function generateEntries() { ...@@ -48,15 +48,9 @@ function generateEntries() {
autoEntriesCount = Object.keys(autoEntries).length; autoEntriesCount = Object.keys(autoEntries).length;
const manualEntries = { const manualEntries = {
balsamiq_viewer: './blob/balsamiq_viewer.js',
filtered_search: './filtered_search/filtered_search_bundle.js',
monitoring: './monitoring/monitoring_bundle.js', monitoring: './monitoring/monitoring_bundle.js',
mr_notes: './mr_notes/index.js', mr_notes: './mr_notes/index.js',
notebook_viewer: './blob/notebook_viewer.js',
pdf_viewer: './blob/pdf_viewer.js',
registry_list: './registry/index.js', registry_list: './registry/index.js',
sketch_viewer: './blob/sketch_viewer.js',
stl_viewer: './blob/stl_viewer.js',
terminal: './terminal/terminal_bundle.js', terminal: './terminal/terminal_bundle.js',
two_factor_auth: './two_factor_auth.js', two_factor_auth: './two_factor_auth.js',
...@@ -81,7 +75,6 @@ function generateEntries() { ...@@ -81,7 +75,6 @@ function generateEntries() {
ldap_group_links: 'ee/groups/ldap_group_links.js', ldap_group_links: 'ee/groups/ldap_group_links.js',
mirrors: 'ee/mirrors', mirrors: 'ee/mirrors',
service_desk: 'ee/projects/settings_service_desk/service_desk_bundle.js', service_desk: 'ee/projects/settings_service_desk/service_desk_bundle.js',
service_desk_issues: 'ee/service_desk_issues/index.js',
roadmap: 'ee/roadmap', roadmap: 'ee/roadmap',
}; };
...@@ -239,33 +232,6 @@ const config = { ...@@ -239,33 +232,6 @@ const config = {
return `${moduleNames[0]}-${hash.substr(0, 6)}`; return `${moduleNames[0]}-${hash.substr(0, 6)}`;
}), }),
// create cacheable common library bundle for all vue chunks
new webpack.optimize.CommonsChunkPlugin({
name: 'common_vue',
chunks: [
'boards',
'deploy_keys',
'environments',
'filtered_search',
'groups',
'monitoring',
'mr_notes',
'notebook_viewer',
'pdf_viewer',
'pipelines',
'pipelines_details',
'registry_list',
'ide',
'schedule_form',
'schedules_index',
'sidebar',
'vue_merge_request_widget',
],
minChunks: function(module, count) {
return module.resource && (/vue_shared/).test(module.resource);
},
}),
// create cacheable common library bundles // create cacheable common library bundles
new webpack.optimize.CommonsChunkPlugin({ new webpack.optimize.CommonsChunkPlugin({
names: ['main', 'common', 'webpack_runtime'], names: ['main', 'common', 'webpack_runtime'],
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class ChangeProjectNamespaceIdNotNull < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
class Project < ActiveRecord::Base
self.table_name = 'projects'
include EachBatch
end
BATCH_SIZE = 1000
DOWNTIME = false
disable_ddl_transaction!
def up
Project.where(namespace_id: nil).each_batch(of: BATCH_SIZE) do |batch|
batch.delete_all
end
change_column_null :projects, :namespace_id, false
end
def down
change_column_null :projects, :namespace_id, true
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20180222043024) do ActiveRecord::Schema.define(version: 20180301084653) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -1866,7 +1866,7 @@ ActiveRecord::Schema.define(version: 20180222043024) do ...@@ -1866,7 +1866,7 @@ ActiveRecord::Schema.define(version: 20180222043024) do
t.datetime "created_at" t.datetime "created_at"
t.datetime "updated_at" t.datetime "updated_at"
t.integer "creator_id" t.integer "creator_id"
t.integer "namespace_id" t.integer "namespace_id", null: false
t.datetime "last_activity_at" t.datetime "last_activity_at"
t.string "import_url" t.string "import_url"
t.integer "visibility_level", default: 0, null: false t.integer "visibility_level", default: 0, null: false
......
...@@ -327,7 +327,7 @@ step of the sync. ...@@ -327,7 +327,7 @@ step of the sync.
1. Run a group sync for this particular group. 1. Run a group sync for this particular group.
```ruby ```ruby
EE::Gitlab::LDAP::Sync::Group.execute_all_providers(group) EE::Gitlab::Auth::LDAP::Sync::Group.execute_all_providers(group)
``` ```
1. Look through the output of the sync. See [example log output](#example-log-output) 1. Look through the output of the sync. See [example log output](#example-log-output)
below for more information about the output. below for more information about the output.
...@@ -336,11 +336,11 @@ step of the sync. ...@@ -336,11 +336,11 @@ step of the sync.
run the following query: run the following query:
```ruby ```ruby
adapter = Gitlab::LDAP::Adapter.new('ldapmain') # If `main` is the LDAP provider adapter = Gitlab::Auth::LDAP::Adapter.new('ldapmain') # If `main` is the LDAP provider
ldap_group = EE::Gitlab::LDAP::Group.find_by_cn('group_cn_here', adapter) ldap_group = EE::Gitlab::Auth::LDAP::Group.find_by_cn('group_cn_here', adapter)
# Output # Output
=> #<EE::Gitlab::LDAP::Group:0x007fcbdd0bb6d8 => #<EE::Gitlab::Auth::LDAP::Group:0x007fcbdd0bb6d8
``` ```
1. Query the LDAP group's member DNs and see if the user's DN is in the list. 1. Query the LDAP group's member DNs and see if the user's DN is in the list.
One of the DNs here should match the 'Identifier' from the LDAP identity One of the DNs here should match the 'Identifier' from the LDAP identity
......
...@@ -14,6 +14,9 @@ GET /projects/:id/repository/commits ...@@ -14,6 +14,9 @@ GET /projects/:id/repository/commits
| `ref_name` | string | no | The name of a repository branch or tag or if not given the default branch | | `ref_name` | string | no | The name of a repository branch or tag or if not given the default branch |
| `since` | string | no | Only commits after or on this date will be returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ | | `since` | string | no | Only commits after or on this date will be returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ |
| `until` | string | no | Only commits before or on this date will be returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ | | `until` | string | no | Only commits before or on this date will be returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ |
| `path` | string | no | The file path |
| `all` | boolean | no | Retrieve every commit from the repository |
```bash ```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/repository/commits" curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/repository/commits"
......
...@@ -1204,6 +1204,7 @@ GET /projects/:id/hooks/:hook_id ...@@ -1204,6 +1204,7 @@ GET /projects/:id/hooks/:hook_id
"project_id": 3, "project_id": 3,
"push_events": true, "push_events": true,
"issues_events": true, "issues_events": true,
"confidential_issues_events": true,
"merge_requests_events": true, "merge_requests_events": true,
"tag_push_events": true, "tag_push_events": true,
"note_events": true, "note_events": true,
...@@ -1229,6 +1230,7 @@ POST /projects/:id/hooks ...@@ -1229,6 +1230,7 @@ POST /projects/:id/hooks
| `url` | string | yes | The hook URL | | `url` | string | yes | The hook URL |
| `push_events` | boolean | no | Trigger hook on push events | | `push_events` | boolean | no | Trigger hook on push events |
| `issues_events` | boolean | no | Trigger hook on issues events | | `issues_events` | boolean | no | Trigger hook on issues events |
| `confidential_issues_events` | boolean | no | Trigger hook on confidential issues events |
| `merge_requests_events` | boolean | no | Trigger hook on merge requests events | | `merge_requests_events` | boolean | no | Trigger hook on merge requests events |
| `tag_push_events` | boolean | no | Trigger hook on tag push events | | `tag_push_events` | boolean | no | Trigger hook on tag push events |
| `note_events` | boolean | no | Trigger hook on note events | | `note_events` | boolean | no | Trigger hook on note events |
...@@ -1253,6 +1255,7 @@ PUT /projects/:id/hooks/:hook_id ...@@ -1253,6 +1255,7 @@ PUT /projects/:id/hooks/:hook_id
| `url` | string | yes | The hook URL | | `url` | string | yes | The hook URL |
| `push_events` | boolean | no | Trigger hook on push events | | `push_events` | boolean | no | Trigger hook on push events |
| `issues_events` | boolean | no | Trigger hook on issues events | | `issues_events` | boolean | no | Trigger hook on issues events |
| `confidential_issues_events` | boolean | no | Trigger hook on confidential issues events |
| `merge_requests_events` | boolean | no | Trigger hook on merge requests events | | `merge_requests_events` | boolean | no | Trigger hook on merge requests events |
| `tag_push_events` | boolean | no | Trigger hook on tag push events | | `tag_push_events` | boolean | no | Trigger hook on tag push events |
| `note_events` | boolean | no | Trigger hook on note events | | `note_events` | boolean | no | Trigger hook on note events |
......
...@@ -619,6 +619,7 @@ Example response: ...@@ -619,6 +619,7 @@ Example response:
"active": true, "active": true,
"push_events": true, "push_events": true,
"issues_events": true, "issues_events": true,
"confidential_issues_events": true,
"merge_requests_events": true, "merge_requests_events": true,
"tag_push_events": true, "tag_push_events": true,
"note_events": true, "note_events": true,
......
...@@ -33,6 +33,40 @@ rest of the code should be as close to the CE files as possible. ...@@ -33,6 +33,40 @@ rest of the code should be as close to the CE files as possible.
[single code base]: https://gitlab.com/gitlab-org/gitlab-ee/issues/2952#note_41016454 [single code base]: https://gitlab.com/gitlab-org/gitlab-ee/issues/2952#note_41016454
### Detection of EE-only files
For each commit (except on `master`), the `ee-files-location-check` CI job tries
to detect if there are any new files that are EE-only. If any file is detected,
the job fails with an explanation of why and what to do to make it pass.
Basically, the fix is simple: `git mv <file> ee/<file>`.
#### How to name your branches?
For any EE branch, the job will try to detect its CE counterpart by removing any
`ee-` prefix or `-ee` suffix from the EE branch name, and matching the last
branch that contains it.
For instance, from the EE branch `new-shiny-feature-ee` (or
`ee-new-shiny-feature`), the job would find the corresponding CE branches:
- `new-shiny-feature`
- `ce-new-shiny-feature`
- `new-shiny-feature-ce`
- `my-super-new-shiny-feature-in-ce`
#### Whitelist some EE-only files that cannot be moved to `ee/`
The `ee-files-location-check` CI job provides a whitelist of files or folders
that cannot or should not be moved to `ee/`. Feel free to open an issue to
discuss adding a new file/folder to this whitelist.
For instance, it was decided that moving EE-only files from `qa/` to `ee/qa/`
would make it difficult to build the `gitLab-{ce,ee}-qa` Docker images and it
was [not worth the complexity].
[not worth the complexity]: https://gitlab.com/gitlab-org/gitlab-ee/issues/4997#note_59764702
### EE-only features ### EE-only features
If the feature being developed is not present in any form in CE, we don't If the feature being developed is not present in any form in CE, we don't
...@@ -52,6 +86,11 @@ is applied not only to models. Here's a list of other examples: ...@@ -52,6 +86,11 @@ is applied not only to models. Here's a list of other examples:
- `ee/app/validators/foo_attr_validator.rb` - `ee/app/validators/foo_attr_validator.rb`
- `ee/app/workers/foo_worker.rb` - `ee/app/workers/foo_worker.rb`
This works because for every path that are present in CE's eager-load/auto-load
paths, we add the same `ee/`-prepended path in [`config/application.rb`].
[`config/application.rb`]: https://gitlab.com/gitlab-org/gitlab-ee/blob/d278b76d6600a0e27d8019a0be27971ba23ab640/config/application.rb#L41-51
### EE features based on CE features ### EE features based on CE features
For features that build on existing CE features, write a module in the For features that build on existing CE features, write a module in the
......
...@@ -36,6 +36,15 @@ If you are asynchronously adding content which contains lazy images then you nee ...@@ -36,6 +36,15 @@ If you are asynchronously adding content which contains lazy images then you nee
`gl.lazyLoader.searchLazyImages()` which will search for lazy images and load them if needed. `gl.lazyLoader.searchLazyImages()` which will search for lazy images and load them if needed.
But in general it should be handled automatically through a `MutationObserver` in the lazy loading function. But in general it should be handled automatically through a `MutationObserver` in the lazy loading function.
### Animations
Only animate `opacity` & `transform` properties. Other properties (such as `top`, `left`, `margin`, and `padding`) all cause
Layout to be recalculated, which is much more expensive. For details on this, see "Styles that Affect Layout" in
[High Performance Animations][high-perf-animations].
If you _do_ need to change layout (e.g. a sidebar that pushes main content over), prefer [FLIP][flip] to change expensive
properties once, and handle the actual animation with transforms.
## Reducing Asset Footprint ## Reducing Asset Footprint
### Page-specific JavaScript ### Page-specific JavaScript
...@@ -87,6 +96,7 @@ General tips: ...@@ -87,6 +96,7 @@ General tips:
- Compress and minify assets wherever possible (For CSS/JS, Sprockets and webpack do this for us). - Compress and minify assets wherever possible (For CSS/JS, Sprockets and webpack do this for us).
- If some functionality can reasonably be achieved without adding extra libraries, avoid them. - If some functionality can reasonably be achieved without adding extra libraries, avoid them.
- Use page-specific JavaScript as described above to dynamically load libraries that are only needed on certain pages. - Use page-specific JavaScript as described above to dynamically load libraries that are only needed on certain pages.
- [High Performance Animations][high-perf-animations]
------- -------
...@@ -105,3 +115,5 @@ General tips: ...@@ -105,3 +115,5 @@ General tips:
[d3]: https://d3js.org/ [d3]: https://d3js.org/
[chartjs]: http://www.chartjs.org/ [chartjs]: http://www.chartjs.org/
[page-specific-js-example]: https://gitlab.com/gitlab-org/gitlab-ce/blob/13bb9ed77f405c5f6ee4fdbc964ecf635c9a223f/app/views/projects/graphs/_head.html.haml#L6-8 [page-specific-js-example]: https://gitlab.com/gitlab-org/gitlab-ce/blob/13bb9ed77f405c5f6ee4fdbc964ecf635c9a223f/app/views/projects/graphs/_head.html.haml#L6-8
[high-perf-animations]: https://www.html5rocks.com/en/tutorials/speed/high-performance-animations/
[flip]: https://aerotwist.com/blog/flip-your-animations/
This diff is collapsed.
...@@ -15,6 +15,7 @@ are very appreciative of the work done by translators and proofreaders! ...@@ -15,6 +15,7 @@ are very appreciative of the work done by translators and proofreaders!
- Dutch - Dutch
- Esperanto - Esperanto
- French - French
- Rémy Coutable - [GitLab](https://gitlab.com/rymai), [Crowdin](https://crowdin.com/profile/rymai)
- German - German
- Italian - Italian
- Paolo Falomo - [GitLab](https://gitlab.com/paolofalomo), [Crowdin](https://crowdin.com/profile/paolo.falomo) - Paolo Falomo - [GitLab](https://gitlab.com/paolofalomo), [Crowdin](https://crowdin.com/profile/paolo.falomo)
......
...@@ -37,33 +37,43 @@ Comments can be added to discuss a translation with the community. ...@@ -37,33 +37,43 @@ Comments can be added to discuss a translation with the community.
Remember to **Save** each translation. Remember to **Save** each translation.
## Translation Guidelines ## General Translation Guidelines
Be sure to check the following guidelines before you translate any strings. Be sure to check the following guidelines before you translate any strings.
### Namespaced strings
When an externalized string is prepended with a namespace, e.g.
`s_('OpenedNDaysAgo|Opened')`, the namespace should be removed from the final
translation.
For example in French `OpenedNDaysAgo|Opened` would be translated to
`Ouvert•e`, not `OpenedNDaysAgo|Ouvert•e`.
### Technical terms ### Technical terms
Technical terms should be treated like proper nouns and not be translated. Some technical terms should be treated like proper nouns and not be translated.
This helps maintain a logical connection and consistency between tools (e.g. `git` client) and
GitLab.
Technical terms that should always be in English are noted in the glossary when using Technical terms that should always be in English are noted in the glossary when
[translate.gitlab.com](https://translate.gitlab.com). using [translate.gitlab.com](https://translate.gitlab.com).
This helps maintain a logical connection and consistency between tools (e.g.
`git` client) and GitLab.
### Formality ### Formality
The level of formality used in software varies by language. The level of formality used in software varies by language.
For example, in French we translate `you` as the informal `tu`. For example, in French we translate `you` as the formal `vous`.
You can refer to other translated strings and notes in the glossary to assist determining a You can refer to other translated strings and notes in the glossary to assist
suitable level of formality. determining a suitable level of formality.
### Inclusive language ### Inclusive language
[Diversity] is one of GitLab's values. [Diversity] is one of GitLab's values.
We ask you to avoid translations which exclude people based on their gender or ethnicity. We ask you to avoid translations which exclude people based on their gender or
In languages which distinguish between a male and female form, ethnicity.
use both or choose a neutral formulation. In languages which distinguish between a male and female form, use both or
choose a neutral formulation.
For example in German, the word "user" can be translated into "Benutzer" (male) or "Benutzerin" (female). For example in German, the word "user" can be translated into "Benutzer" (male) or "Benutzerin" (female).
Therefore "create a new user" would translate into "Benutzer(in) anlegen". Therefore "create a new user" would translate into "Benutzer(in) anlegen".
...@@ -74,3 +84,14 @@ Therefore "create a new user" would translate into "Benutzer(in) anlegen". ...@@ -74,3 +84,14 @@ Therefore "create a new user" would translate into "Benutzer(in) anlegen".
To propose additions to the glossary please To propose additions to the glossary please
[open an issue](https://gitlab.com/gitlab-org/gitlab-ce/issues). [open an issue](https://gitlab.com/gitlab-org/gitlab-ce/issues).
## French Translation Guidelines
### Inclusive language in French
In French, we should follow the guidelines from [ecriture-inclusive.fr]. For
instance:
- Utilisateur•rice•s
[ecriture-inclusive.fr]: http://www.ecriture-inclusive.fr/
...@@ -93,9 +93,9 @@ Is the system packaged Git too old? Remove it and compile from source. ...@@ -93,9 +93,9 @@ Is the system packaged Git too old? Remove it and compile from source.
# Download and compile from source # Download and compile from source
cd /tmp cd /tmp
curl --remote-name --progress https://www.kernel.org/pub/software/scm/git/git-2.14.3.tar.gz curl --remote-name --progress https://www.kernel.org/pub/software/scm/git/git-2.16.2.tar.gz
echo '023ffff6d3ba8a1bea779dfecc0ed0bb4ad68ab8601d14435dd8c08416f78d7f git-2.14.3.tar.gz' | shasum -a256 -c - && tar -xzf git-2.14.3.tar.gz echo '9acc4339b7a2ab484eea69d705923271682b7058015219cf5a7e6ed8dee5b5fb git-2.16.2.tar.gz' | shasum -a256 -c - && tar -xzf git-2.16.2.tar.gz
cd git-2.14.3/ cd git-2.16.2/
./configure ./configure
make prefix=/usr/local all make prefix=/usr/local all
......
import FilteredSearchManager from '~/filtered_search/filtered_search_manager'; import initFilteredSearch from '~/pages/search/init_filtered_search';
import FilteredSearchTokenKeysEpics from 'ee/filtered_search/filtered_search_token_keys_epics'; import FilteredSearchTokenKeysEpics from 'ee/filtered_search/filtered_search_token_keys_epics';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const filteredSearchEnabled = FilteredSearchManager && document.querySelector('.filtered-search'); initFilteredSearch({
if (filteredSearchEnabled) { page: 'epics',
const filteredSearchManager = new FilteredSearchManager({ filteredSearchTokenKeys: FilteredSearchTokenKeysEpics,
page: 'epics', stateFiltersSelector: '.epics-state-filters',
filteredSearchTokenKeys: FilteredSearchTokenKeysEpics, });
stateFiltersSelector: '.epics-state-filters',
});
filteredSearchManager.setup();
}
}); });
...@@ -28,4 +28,3 @@ export default class FilteredSearchServiceDesk extends FilteredSearchManager { ...@@ -28,4 +28,3 @@ export default class FilteredSearchServiceDesk extends FilteredSearchManager {
return onlyValidParams; return onlyValidParams;
} }
} }
...@@ -59,7 +59,7 @@ module EE ...@@ -59,7 +59,7 @@ module EE
def render_approvals_json def render_approvals_json
respond_to do |format| respond_to do |format|
format.json do format.json do
entity = ::API::Entities::MergeRequestApprovals.new(merge_request, current_user: current_user) entity = EE::API::Entities::MergeRequestApprovals.new(merge_request, current_user: current_user)
render json: entity render json: entity
end end
end end
......
...@@ -17,6 +17,6 @@ class Groups::LdapsController < Groups::ApplicationController ...@@ -17,6 +17,6 @@ class Groups::LdapsController < Groups::ApplicationController
private private
def check_enabled_extras! def check_enabled_extras!
render_404 unless Gitlab::LDAP::Config.group_sync_enabled? render_404 unless Gitlab::Auth::LDAP::Config.group_sync_enabled?
end end
end end
...@@ -337,7 +337,9 @@ module EE ...@@ -337,7 +337,9 @@ module EE
end end
def remove_mirror_repository_reference def remove_mirror_repository_reference
repository.async_remove_remote(::Repository::MIRROR_REMOTE) run_after_commit do
repository.async_remove_remote(::Repository::MIRROR_REMOTE)
end
end end
def username_only_import_url def username_only_import_url
......
...@@ -29,14 +29,14 @@ class LdapGroupLink < ActiveRecord::Base ...@@ -29,14 +29,14 @@ class LdapGroupLink < ActiveRecord::Base
end end
def config def config
Gitlab::LDAP::Config.new(provider) Gitlab::Auth::LDAP::Config.new(provider)
rescue Gitlab::LDAP::Config::InvalidProvider rescue Gitlab::Auth::LDAP::Config::InvalidProvider
nil nil
end end
# default to the first LDAP server # default to the first LDAP server
def provider def provider
read_attribute(:provider) || Gitlab::LDAP::Config.providers.first read_attribute(:provider) || Gitlab::Auth::LDAP::Config.providers.first
end end
def provider_label def provider_label
......
...@@ -24,7 +24,8 @@ class RemoteMirror < ActiveRecord::Base ...@@ -24,7 +24,8 @@ class RemoteMirror < ActiveRecord::Base
after_save :set_override_remote_mirror_available, unless: -> { Gitlab::CurrentSettings.current_application_settings.mirror_available } after_save :set_override_remote_mirror_available, unless: -> { Gitlab::CurrentSettings.current_application_settings.mirror_available }
after_save :refresh_remote, if: :mirror_url_changed? after_save :refresh_remote, if: :mirror_url_changed?
after_update :reset_fields, if: :mirror_url_changed? after_update :reset_fields, if: :mirror_url_changed?
after_destroy :remove_remote
after_commit :remove_remote, on: :destroy
scope :enabled, -> { where(enabled: true) } scope :enabled, -> { where(enabled: true) }
scope :started, -> { with_update_status(:started) } scope :started, -> { with_update_status(:started) }
......
class GeoNodeStatusSerializer < BaseSerializer class GeoNodeStatusSerializer < BaseSerializer
entity API::Entities::GeoNodeStatus entity EE::API::Entities::GeoNodeStatus
end end
- if Gitlab::LDAP::Config.group_sync_enabled? && can?(current_user, :admin_ldap_group_links, @group) - if Gitlab::Auth::LDAP::Config.group_sync_enabled? && can?(current_user, :admin_ldap_group_links, @group)
= nav_link(path: 'ldap_group_links#index') do = nav_link(path: 'ldap_group_links#index') do
= link_to group_ldap_group_links_path(@group), title: 'LDAP Group' do = link_to group_ldap_group_links_path(@group), title: 'LDAP Group' do
%span %span
......
- page_title "Epics" - page_title "Epics"
- content_for :page_specific_javascripts do
= webpack_bundle_tag 'common_vue'
= webpack_bundle_tag 'filtered_search'
.top-area .top-area
= render 'shared/issuable/epic_nav', type: :epics = render 'shared/issuable/epic_nav', type: :epics
......
...@@ -5,8 +5,6 @@ ...@@ -5,8 +5,6 @@
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= webpack_bundle_tag 'common_vue' = webpack_bundle_tag 'common_vue'
= webpack_bundle_tag 'filtered_search'
= webpack_bundle_tag 'service_desk_issues'
= webpack_bundle_tag 'issues' = webpack_bundle_tag 'issues'
- content_for :breadcrumbs_extra do - content_for :breadcrumbs_extra do
......
...@@ -3,10 +3,10 @@ class LdapAllGroupsSyncWorker ...@@ -3,10 +3,10 @@ class LdapAllGroupsSyncWorker
include CronjobQueue include CronjobQueue
def perform def perform
return unless Gitlab::LDAP::Config.group_sync_enabled? return unless Gitlab::Auth::LDAP::Config.group_sync_enabled?
logger.info 'Started LDAP group sync' logger.info 'Started LDAP group sync'
EE::Gitlab::LDAP::Sync::Groups.execute EE::Gitlab::Auth::LDAP::Sync::Groups.execute
logger.info 'Finished LDAP group sync' logger.info 'Finished LDAP group sync'
end end
end end
...@@ -2,12 +2,12 @@ class LdapGroupSyncWorker ...@@ -2,12 +2,12 @@ class LdapGroupSyncWorker
include ApplicationWorker include ApplicationWorker
def perform(group_ids, provider = nil) def perform(group_ids, provider = nil)
return unless Gitlab::LDAP::Config.group_sync_enabled? return unless Gitlab::Auth::LDAP::Config.group_sync_enabled?
groups = Group.where(id: Array(group_ids)) groups = Group.where(id: Array(group_ids))
if provider if provider
EE::Gitlab::LDAP::Sync::Proxy.open(provider) do |proxy| EE::Gitlab::Auth::LDAP::Sync::Proxy.open(provider) do |proxy|
sync_groups(groups, proxy: proxy) sync_groups(groups, proxy: proxy)
end end
else else
...@@ -23,9 +23,9 @@ class LdapGroupSyncWorker ...@@ -23,9 +23,9 @@ class LdapGroupSyncWorker
logger.info "Started LDAP group sync for group #{group.name} (#{group.id})" logger.info "Started LDAP group sync for group #{group.name} (#{group.id})"
if proxy if proxy
EE::Gitlab::LDAP::Sync::Group.execute(group, proxy) EE::Gitlab::Auth::LDAP::Sync::Group.execute(group, proxy)
else else
EE::Gitlab::LDAP::Sync::Group.execute_all_providers(group) EE::Gitlab::Auth::LDAP::Sync::Group.execute_all_providers(group)
end end
logger.info "Finished LDAP group sync for group #{group.name} (#{group.id})" logger.info "Finished LDAP group sync for group #{group.name} (#{group.id})"
......
...@@ -3,14 +3,14 @@ class LdapSyncWorker ...@@ -3,14 +3,14 @@ class LdapSyncWorker
include CronjobQueue include CronjobQueue
def perform def perform
return unless Gitlab::LDAP::Config.group_sync_enabled? return unless Gitlab::Auth::LDAP::Config.group_sync_enabled?
Rails.logger.info "Performing daily LDAP sync task." Rails.logger.info "Performing daily LDAP sync task."
User.ldap.find_each(batch_size: 100).each do |ldap_user| User.ldap.find_each(batch_size: 100).each do |ldap_user|
Rails.logger.debug "Syncing user #{ldap_user.username}, #{ldap_user.email}" Rails.logger.debug "Syncing user #{ldap_user.username}, #{ldap_user.email}"
# Use the 'update_ldap_group_links_synchronously' option to avoid creating a ton # Use the 'update_ldap_group_links_synchronously' option to avoid creating a ton
# of new Sidekiq jobs all at once. # of new Sidekiq jobs all at once.
Gitlab::LDAP::Access.allowed?(ldap_user, update_ldap_group_links_synchronously: true) Gitlab::Auth::LDAP::Access.allowed?(ldap_user, update_ldap_group_links_synchronously: true)
end end
end end
end end
---
title: Supresses error being raised due to async remote removal being run outside
a transaction
merge_request: 4747
author:
type: fixed
# rubocop:disable Rails/ReversibleMigration # rubocop:disable Rails/ReversibleMigration
class UpdateGroupLinks < ActiveRecord::Migration class UpdateGroupLinks < ActiveRecord::Migration
def change def change
provider = quote_string(Gitlab::LDAP::Config.providers.first) provider = quote_string(Gitlab::Auth::LDAP::Config.providers.first)
execute("UPDATE ldap_group_links SET provider = '#{provider}' WHERE provider IS NULL") execute("UPDATE ldap_group_links SET provider = '#{provider}' WHERE provider IS NULL")
end end
end end
...@@ -54,7 +54,7 @@ module API ...@@ -54,7 +54,7 @@ module API
# The issues list in the correct order in body will be returned as part of #4250 # The issues list in the correct order in body will be returned as part of #4250
if result if result
present epic.issues(current_user), present epic.issues(current_user),
with: Entities::EpicIssue, with: EE::API::Entities::EpicIssue,
current_user: current_user current_user: current_user
else else
render_api_error!({ error: "Issue could not be moved!" }, 400) render_api_error!({ error: "Issue could not be moved!" }, 400)
...@@ -62,7 +62,7 @@ module API ...@@ -62,7 +62,7 @@ module API
end end
desc 'Get issues assigned to the epic' do desc 'Get issues assigned to the epic' do
success Entities::EpicIssue success EE::API::Entities::EpicIssue
end end
params do params do
requires :epic_iid, type: Integer, desc: 'The iid of the epic' requires :epic_iid, type: Integer, desc: 'The iid of the epic'
...@@ -71,12 +71,12 @@ module API ...@@ -71,12 +71,12 @@ module API
authorize_can_read! authorize_can_read!
present epic.issues(current_user), present epic.issues(current_user),
with: Entities::EpicIssue, with: EE::API::Entities::EpicIssue,
current_user: current_user current_user: current_user
end end
desc 'Assign an issue to the epic' do desc 'Assign an issue to the epic' do
success Entities::EpicIssueLink success EE::API::Entities::EpicIssueLink
end end
params do params do
requires :epic_iid, type: Integer, desc: 'The iid of the epic' requires :epic_iid, type: Integer, desc: 'The iid of the epic'
...@@ -93,14 +93,14 @@ module API ...@@ -93,14 +93,14 @@ module API
if result[:status] == :success if result[:status] == :success
epic_issue_link = EpicIssue.find_by!(epic: epic, issue: issue) epic_issue_link = EpicIssue.find_by!(epic: epic, issue: issue)
present epic_issue_link, with: Entities::EpicIssueLink present epic_issue_link, with: EE::API::Entities::EpicIssueLink
else else
render_api_error!(result[:message], result[:http_status]) render_api_error!(result[:message], result[:http_status])
end end
end end
desc 'Remove an issue from the epic' do desc 'Remove an issue from the epic' do
success Entities::EpicIssueLink success EE::API::Entities::EpicIssueLink
end end
params do params do
requires :epic_iid, type: Integer, desc: 'The iid of the epic' requires :epic_iid, type: Integer, desc: 'The iid of the epic'
...@@ -112,7 +112,7 @@ module API ...@@ -112,7 +112,7 @@ module API
result = ::EpicIssues::DestroyService.new(link, current_user).execute result = ::EpicIssues::DestroyService.new(link, current_user).execute
if result[:status] == :success if result[:status] == :success
present link, with: Entities::EpicIssueLink present link, with: EE::API::Entities::EpicIssueLink
else else
render_api_error!(result[:message], result[:http_status]) render_api_error!(result[:message], result[:http_status])
end end
......
...@@ -46,7 +46,7 @@ module API ...@@ -46,7 +46,7 @@ module API
resource :groups, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do resource :groups, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
desc 'Get epics for the group' do desc 'Get epics for the group' do
success Entities::Epic success EE::API::Entities::Epic
end end
params do params do
optional :order_by, type: String, values: %w[created_at updated_at], default: 'created_at', optional :order_by, type: String, values: %w[created_at updated_at], default: 'created_at',
...@@ -58,11 +58,11 @@ module API ...@@ -58,11 +58,11 @@ module API
optional :labels, type: String, desc: 'Comma-separated list of label names' optional :labels, type: String, desc: 'Comma-separated list of label names'
end end
get ':id/-/epics' do get ':id/-/epics' do
present find_epics(group_id: user_group.id), with: Entities::Epic present find_epics(group_id: user_group.id), with: EE::API::Entities::Epic
end end
desc 'Get details of an epic' do desc 'Get details of an epic' do
success Entities::Epic success EE::API::Entities::Epic
end end
params do params do
requires :epic_iid, type: Integer, desc: 'The internal ID of an epic' requires :epic_iid, type: Integer, desc: 'The internal ID of an epic'
...@@ -70,11 +70,11 @@ module API ...@@ -70,11 +70,11 @@ module API
get ':id/-/epics/:epic_iid' do get ':id/-/epics/:epic_iid' do
authorize_can_read! authorize_can_read!
present epic, with: Entities::Epic present epic, with: EE::API::Entities::Epic
end end
desc 'Create a new epic' do desc 'Create a new epic' do
success Entities::Epic success EE::API::Entities::Epic
end end
params do params do
requires :title, type: String, desc: 'The title of an epic' requires :title, type: String, desc: 'The title of an epic'
...@@ -88,14 +88,14 @@ module API ...@@ -88,14 +88,14 @@ module API
epic = ::Epics::CreateService.new(user_group, current_user, declared_params(include_missing: false)).execute epic = ::Epics::CreateService.new(user_group, current_user, declared_params(include_missing: false)).execute
if epic.valid? if epic.valid?
present epic, with: Entities::Epic present epic, with: EE::API::Entities::Epic
else else
render_validation_error!(epic) render_validation_error!(epic)
end end
end end
desc 'Update an epic' do desc 'Update an epic' do
success Entities::Epic success EE::API::Entities::Epic
end end
params do params do
requires :epic_iid, type: Integer, desc: 'The internal ID of an epic' requires :epic_iid, type: Integer, desc: 'The internal ID of an epic'
...@@ -114,14 +114,14 @@ module API ...@@ -114,14 +114,14 @@ module API
result = ::Epics::UpdateService.new(user_group, current_user, update_params).execute(epic) result = ::Epics::UpdateService.new(user_group, current_user, update_params).execute(epic)
if result.valid? if result.valid?
present result, with: Entities::Epic present result, with: EE::API::Entities::Epic
else else
render_validation_error!(result) render_validation_error!(result)
end end
end end
desc 'Destroy an epic' do desc 'Destroy an epic' do
success Entities::Epic success EE::API::Entities::Epic
end end
params do params do
requires :epic_iid, type: Integer, desc: 'The internal ID of an epic' requires :epic_iid, type: Integer, desc: 'The internal ID of an epic'
......
...@@ -36,7 +36,7 @@ module API ...@@ -36,7 +36,7 @@ module API
authenticate_by_gitlab_geo_node_token! authenticate_by_gitlab_geo_node_token!
status = ::GeoNodeStatus.current_node_status status = ::GeoNodeStatus.current_node_status
present status, with: Entities::GeoNodeStatus present status, with: EE::API::Entities::GeoNodeStatus
end end
end end
......
...@@ -12,13 +12,13 @@ module API ...@@ -12,13 +12,13 @@ module API
# Example request: # Example request:
# GET /geo_nodes # GET /geo_nodes
desc 'Retrieves the available Geo nodes' do desc 'Retrieves the available Geo nodes' do
success Entities::GeoNode success EE::API::Entities::GeoNode
end end
get do get do
nodes = GeoNode.all nodes = GeoNode.all
present paginate(nodes), with: Entities::GeoNode present paginate(nodes), with: EE::API::Entities::GeoNode
end end
# Get all Geo node statuses # Get all Geo node statuses
...@@ -26,12 +26,12 @@ module API ...@@ -26,12 +26,12 @@ module API
# Example request: # Example request:
# GET /geo_nodes/status # GET /geo_nodes/status
desc 'Get status for all Geo nodes' do desc 'Get status for all Geo nodes' do
success Entities::GeoNodeStatus success EE::API::Entities::GeoNodeStatus
end end
get '/status' do get '/status' do
status = GeoNodeStatus.all status = GeoNodeStatus.all
present paginate(status), with: Entities::GeoNodeStatus present paginate(status), with: EE::API::Entities::GeoNodeStatus
end end
# Get project registry failures for the current Geo node # Get project registry failures for the current Geo node
...@@ -78,12 +78,12 @@ module API ...@@ -78,12 +78,12 @@ module API
# Example request: # Example request:
# GET /geo_nodes/:id # GET /geo_nodes/:id
desc 'Get a single GeoNode' do desc 'Get a single GeoNode' do
success Entities::GeoNode success EE::API::Entities::GeoNode
end end
get do get do
not_found!('GeoNode') unless geo_node not_found!('GeoNode') unless geo_node
present geo_node, with: Entities::GeoNode present geo_node, with: EE::API::Entities::GeoNode
end end
# Get Geo metrics for a single node # Get Geo metrics for a single node
...@@ -91,14 +91,14 @@ module API ...@@ -91,14 +91,14 @@ module API
# Example request: # Example request:
# GET /geo_nodes/:id/status # GET /geo_nodes/:id/status
desc 'Get metrics for a single Geo node' do desc 'Get metrics for a single Geo node' do
success Entities::GeoNodeStatus success EE::API::Entities::GeoNodeStatus
end end
get 'status' do get 'status' do
not_found!('GeoNode') unless geo_node not_found!('GeoNode') unless geo_node
not_found!('Status for Geo node not found') unless geo_node_status not_found!('Status for Geo node not found') unless geo_node_status
present geo_node_status, with: Entities::GeoNodeStatus present geo_node_status, with: EE::API::Entities::GeoNodeStatus
end end
# Repair authentication of the Geo node # Repair authentication of the Geo node
...@@ -106,14 +106,14 @@ module API ...@@ -106,14 +106,14 @@ module API
# Example request: # Example request:
# POST /geo_nodes/:id/repair # POST /geo_nodes/:id/repair
desc 'Repair authentication of the Geo node' do desc 'Repair authentication of the Geo node' do
success Entities::GeoNodeStatus success EE::API::Entities::GeoNodeStatus
end end
post 'repair' do post 'repair' do
not_found!('GeoNode') unless geo_node not_found!('GeoNode') unless geo_node
if !geo_node.missing_oauth_application? || geo_node.repair if !geo_node.missing_oauth_application? || geo_node.repair
status 200 status 200
present geo_node_status, with: Entities::GeoNodeStatus present geo_node_status, with: EE::API::Entities::GeoNodeStatus
else else
render_validation_error!(geo_node) render_validation_error!(geo_node)
end end
...@@ -124,7 +124,7 @@ module API ...@@ -124,7 +124,7 @@ module API
# Example request: # Example request:
# PUT /geo_nodes/:id # PUT /geo_nodes/:id
desc 'Edit an existing Geo secondary node' do desc 'Edit an existing Geo secondary node' do
success Entities::GeoNode success EE::API::Entities::GeoNode
end end
params do params do
optional :enabled, type: Boolean, desc: 'Flag indicating if the Geo node is enabled' optional :enabled, type: Boolean, desc: 'Flag indicating if the Geo node is enabled'
...@@ -140,7 +140,7 @@ module API ...@@ -140,7 +140,7 @@ module API
if geo_node.primary? if geo_node.primary?
forbidden!('Primary node cannot be edited') forbidden!('Primary node cannot be edited')
elsif geo_node.update_attributes(update_params) elsif geo_node.update_attributes(update_params)
present geo_node, with: Entities::GeoNode present geo_node, with: EE::API::Entities::GeoNode
else else
render_validation_error!(geo_node) render_validation_error!(geo_node)
end end
......
...@@ -10,20 +10,20 @@ module API ...@@ -10,20 +10,20 @@ module API
end end
resource :projects, requirements: { id: %r{[^/]+} } do resource :projects, requirements: { id: %r{[^/]+} } do
desc 'Get related issues' do desc 'Get related issues' do
success Entities::RelatedIssue success EE::API::Entities::RelatedIssue
end end
get ':id/issues/:issue_iid/links' do get ':id/issues/:issue_iid/links' do
source_issue = find_project_issue(params[:issue_iid]) source_issue = find_project_issue(params[:issue_iid])
related_issues = source_issue.related_issues(current_user) related_issues = source_issue.related_issues(current_user)
present related_issues, present related_issues,
with: Entities::RelatedIssue, with: EE::API::Entities::RelatedIssue,
current_user: current_user, current_user: current_user,
project: user_project project: user_project
end end
desc 'Relate issues' do desc 'Relate issues' do
success Entities::IssueLink success EE::API::Entities::IssueLink
end end
params do params do
requires :target_project_id, type: String, desc: 'The ID of the target project' requires :target_project_id, type: String, desc: 'The ID of the target project'
...@@ -43,14 +43,14 @@ module API ...@@ -43,14 +43,14 @@ module API
if result[:status] == :success if result[:status] == :success
issue_link = IssueLink.find_by!(source: source_issue, target: target_issue) issue_link = IssueLink.find_by!(source: source_issue, target: target_issue)
present issue_link, with: Entities::IssueLink present issue_link, with: EE::API::Entities::IssueLink
else else
render_api_error!(result[:message], result[:http_status]) render_api_error!(result[:message], result[:http_status])
end end
end end
desc 'Remove issues relation' do desc 'Remove issues relation' do
success Entities::IssueLink success EE::API::Entities::IssueLink
end end
params do params do
requires :issue_link_id, type: Integer, desc: 'The ID of an issue link' requires :issue_link_id, type: Integer, desc: 'The ID of an issue link'
...@@ -66,7 +66,7 @@ module API ...@@ -66,7 +66,7 @@ module API
.execute .execute
if result[:status] == :success if result[:status] == :success
present issue_link, with: Entities::IssueLink present issue_link, with: EE::API::Entities::IssueLink
else else
render_api_error!(result[:message], result[:http_status]) render_api_error!(result[:message], result[:http_status])
end end
......
...@@ -6,7 +6,7 @@ module API ...@@ -6,7 +6,7 @@ module API
helpers do helpers do
def get_group_list(provider, search) def get_group_list(provider, search)
search = Net::LDAP::Filter.escape(search) search = Net::LDAP::Filter.escape(search)
Gitlab::LDAP::Adapter.new(provider).groups("#{search}*", 20) Gitlab::Auth::LDAP::Adapter.new(provider).groups("#{search}*", 20)
end end
params :search_params do params :search_params do
...@@ -15,26 +15,26 @@ module API ...@@ -15,26 +15,26 @@ module API
end end
desc 'Get a LDAP groups list. Limit size to 20 of them.' do desc 'Get a LDAP groups list. Limit size to 20 of them.' do
success Entities::LdapGroup success EE::API::Entities::LdapGroup
end end
params do params do
use :search_params use :search_params
end end
get 'groups' do get 'groups' do
provider = Gitlab::LDAP::Config.available_servers.first['provider_name'] provider = Gitlab::Auth::LDAP::Config.available_servers.first['provider_name']
groups = get_group_list(provider, params[:search]) groups = get_group_list(provider, params[:search])
present groups, with: Entities::LdapGroup present groups, with: EE::API::Entities::LdapGroup
end end
desc 'Get a LDAP groups list by the requested provider. Limit size to 20 of them.' do desc 'Get a LDAP groups list by the requested provider. Limit size to 20 of them.' do
success Entities::LdapGroup success EE::API::Entities::LdapGroup
end end
params do params do
use :search_params use :search_params
end end
get ':provider/groups' do get ':provider/groups' do
groups = get_group_list(params[:provider], params[:search]) groups = get_group_list(params[:provider], params[:search])
present groups, with: Entities::LdapGroup present groups, with: EE::API::Entities::LdapGroup
end end
end end
end end
......
...@@ -7,7 +7,7 @@ module API ...@@ -7,7 +7,7 @@ module API
end end
resource :groups do resource :groups do
desc 'Add a linked LDAP group to group' do desc 'Add a linked LDAP group to group' do
success Entities::LdapGroupLink success EE::API::Entities::LdapGroupLink
end end
params do params do
requires 'cn', type: String, desc: 'The CN of a LDAP group' requires 'cn', type: String, desc: 'The CN of a LDAP group'
...@@ -21,7 +21,7 @@ module API ...@@ -21,7 +21,7 @@ module API
ldap_group_link = group.ldap_group_links.new(declared_params(include_missing: false)) ldap_group_link = group.ldap_group_links.new(declared_params(include_missing: false))
if ldap_group_link.save if ldap_group_link.save
present ldap_group_link, with: Entities::LdapGroupLink present ldap_group_link, with: EE::API::Entities::LdapGroupLink
else else
render_api_error!(ldap_group_link.errors.full_messages.first, 409) render_api_error!(ldap_group_link.errors.full_messages.first, 409)
end end
......
...@@ -4,16 +4,16 @@ module API ...@@ -4,16 +4,16 @@ module API
resource :license do resource :license do
desc 'Get information on the currently active license' do desc 'Get information on the currently active license' do
success Entities::GitlabLicense success EE::API::Entities::GitlabLicense
end end
get do get do
license = ::License.current license = ::License.current
present license, with: Entities::GitlabLicense present license, with: EE::API::Entities::GitlabLicense
end end
desc 'Add a new license' do desc 'Add a new license' do
success Entities::GitlabLicense success EE::API::Entities::GitlabLicense
end end
params do params do
requires :license, type: String, desc: 'The license text' requires :license, type: String, desc: 'The license text'
...@@ -21,7 +21,7 @@ module API ...@@ -21,7 +21,7 @@ module API
post do post do
license = ::License.new(data: params[:license]) license = ::License.new(data: params[:license])
if license.save if license.save
present license, with: Entities::GitlabLicense present license, with: EE::API::Entities::GitlabLicense
else else
render_api_error!(license.errors.full_messages.first, 400) render_api_error!(license.errors.full_messages.first, 400)
end end
......
...@@ -25,15 +25,15 @@ module API ...@@ -25,15 +25,15 @@ module API
end end
desc 'Get project push rule' do desc 'Get project push rule' do
success Entities::ProjectPushRule success EE::API::Entities::ProjectPushRule
end end
get ":id/push_rule" do get ":id/push_rule" do
push_rule = user_project.push_rule push_rule = user_project.push_rule
present push_rule, with: Entities::ProjectPushRule present push_rule, with: EE::API::Entities::ProjectPushRule
end end
desc 'Add a push rule to a project' do desc 'Add a push rule to a project' do
success Entities::ProjectPushRule success EE::API::Entities::ProjectPushRule
end end
params do params do
use :push_rule_params use :push_rule_params
...@@ -43,12 +43,12 @@ module API ...@@ -43,12 +43,12 @@ module API
error!("Project push rule exists", 422) error!("Project push rule exists", 422)
else else
push_rule = user_project.create_push_rule(declared_params(include_missing: false)) push_rule = user_project.create_push_rule(declared_params(include_missing: false))
present push_rule, with: Entities::ProjectPushRule present push_rule, with: EE::API::Entities::ProjectPushRule
end end
end end
desc 'Update an existing project push rule' do desc 'Update an existing project push rule' do
success Entities::ProjectPushRule success EE::API::Entities::ProjectPushRule
end end
params do params do
use :push_rule_params use :push_rule_params
...@@ -58,7 +58,7 @@ module API ...@@ -58,7 +58,7 @@ module API
not_found!('Push Rule') unless push_rule not_found!('Push Rule') unless push_rule
if push_rule.update_attributes(declared_params(include_missing: false)) if push_rule.update_attributes(declared_params(include_missing: false))
present push_rule, with: Entities::ProjectPushRule present push_rule, with: EE::API::Entities::ProjectPushRule
else else
render_validation_error!(push_rule) render_validation_error!(push_rule)
end end
......
...@@ -20,16 +20,16 @@ module API ...@@ -20,16 +20,16 @@ module API
end end
desc 'Get project push rule' do desc 'Get project push rule' do
success ::API::Entities::ProjectPushRule success EE::API::Entities::ProjectPushRule
detail DEPRECATION_MESSAGE detail DEPRECATION_MESSAGE
end end
get ":id/git_hook" do get ":id/git_hook" do
push_rule = user_project.push_rule push_rule = user_project.push_rule
present push_rule, with: ::API::Entities::ProjectPushRule present push_rule, with: EE::API::Entities::ProjectPushRule
end end
desc 'Add a push rule to a project' do desc 'Add a push rule to a project' do
success ::API::Entities::ProjectPushRule success EE::API::Entities::ProjectPushRule
detail DEPRECATION_MESSAGE detail DEPRECATION_MESSAGE
end end
params do params do
...@@ -40,12 +40,12 @@ module API ...@@ -40,12 +40,12 @@ module API
error!("Project push rule exists", 422) error!("Project push rule exists", 422)
else else
push_rule = user_project.create_push_rule(declared_params) push_rule = user_project.create_push_rule(declared_params)
present push_rule, with: ::API::Entities::ProjectPushRule present push_rule, with: EE::API::Entities::ProjectPushRule
end end
end end
desc 'Update an existing project push rule' do desc 'Update an existing project push rule' do
success ::API::Entities::ProjectPushRule success EE::API::Entities::ProjectPushRule
detail DEPRECATION_MESSAGE detail DEPRECATION_MESSAGE
end end
params do params do
...@@ -56,7 +56,7 @@ module API ...@@ -56,7 +56,7 @@ module API
not_found!('Push Rule') unless push_rule not_found!('Push Rule') unless push_rule
if push_rule.update_attributes(declared_params(include_missing: false)) if push_rule.update_attributes(declared_params(include_missing: false))
present push_rule, with: ::API::Entities::ProjectPushRule present push_rule, with: EE::API::Entities::ProjectPushRule
else else
render_validation_error!(push_rule) render_validation_error!(push_rule)
end end
......
...@@ -30,7 +30,7 @@ module Audit ...@@ -30,7 +30,7 @@ module Audit
when :remove when :remove
"Removed #{value}" "Removed #{value}"
when :failed_login when :failed_login
"Failed to login with #{Gitlab::OAuth::Provider.label_for(value).upcase} authentication" "Failed to login with #{Gitlab::Auth::OAuth::Provider.label_for(value).upcase} authentication"
when :custom_message when :custom_message
value value
else else
......
This diff is collapsed.
module EE
module Gitlab
module Auth
module LDAP
# Create a hash map of member DNs to access levels. The highest
# access level is retained in cases where `set` is called multiple times
# for the same DN.
class AccessLevels < Hash
def set(dns, to:)
dns.each do |dn|
current = self[dn]
# Keep the higher of the access values.
self[dn] = to if current.nil? || to > current
end
end
end
end
end
end
end
# LDAP connection adapter EE mixin
#
# This module is intended to encapsulate EE-specific adapter methods
# and be **prepended** in the `Gitlab::Auth::LDAP::Adapter` class.
module EE
module Gitlab
module Auth
module LDAP
module Adapter
# Get LDAP groups from ou=Groups
#
# cn - filter groups by name
#
# Ex.
# groups("dev*") # return all groups start with 'dev'
#
def groups(cn = "*", size = nil)
options = {
base: config.group_base,
filter: Net::LDAP::Filter.eq("cn", cn),
attributes: %w(dn cn memberuid member submember uniquemember memberof)
}
options[:size] = size if size
ldap_search(options).map do |entry|
LDAP::Group.new(entry, self)
end
end
def group(*args)
groups(*args).first
end
def group_members_in_range(dn, range_start)
ldap_search(
base: dn,
scope: Net::LDAP::SearchScope_BaseObject,
attributes: ["member;range=#{range_start}-*"]
).first
end
def nested_groups(parent_dn)
options = {
base: config.group_base,
filter: Net::LDAP::Filter.join(
Net::LDAP::Filter.eq('objectClass', 'group'),
Net::LDAP::Filter.eq('memberOf', parent_dn)
)
}
ldap_search(options).map do |entry|
LDAP::Group.new(entry, self)
end
end
end
end
end
end
end
module EE
module Gitlab
module Auth
module LDAP
module Config
extend ActiveSupport::Concern
class_methods do
def group_sync_enabled?
enabled? && ::License.feature_available?(:ldap_group_sync)
end
end
end
end
end
end
end
module EE
module Gitlab
module Auth
module LDAP
class Group
attr_accessor :adapter
attr_reader :entry
def self.find_by_cn(cn, adapter)
cn = Net::LDAP::Filter.escape(cn)
adapter.group(cn)
end
def initialize(entry, adapter = nil)
Rails.logger.debug { "Instantiating #{self.class.name} with LDIF:\n#{entry.to_ldif}" }
@entry = entry
@adapter = adapter
end
def active_directory?
adapter.config.active_directory
end
def cn
entry.cn.first
end
def name
cn
end
def path
name.parameterize
end
def memberuid?
entry.respond_to? :memberuid
end
def member_uids
@member_uids ||= entry.memberuid.map do |uid|
::Gitlab::Auth::LDAP::Person.normalize_uid(uid)
end
end
delegate :dn, to: :entry
def member_dns(nested_groups_to_skip = [])
dns = []
if active_directory? && adapter
dns.concat(active_directory_members(entry, nested_groups_to_skip))
end
dns.concat(entry_member_dns(entry))
dns.uniq
end
private
# Active Directory range member methods
def has_member_range?(entry)
member_range_attribute(entry).present?
end
def member_range_attribute(entry)
entry.attribute_names.find { |a| a.to_s.start_with?("member;range=")}.to_s
end
def active_directory_members(entry, nested_groups_to_skip)
require 'net/ldap/dn'
members = []
# Retrieve all member pages/ranges
members.concat(ranged_members(entry)) if has_member_range?(entry)
# Process nested group members
members.concat(nested_members(nested_groups_to_skip))
# Clean dns of groups and users outside the base
members.reject! { |dn| nested_groups_to_skip.include?(dn) }
return [] if members.empty?
# Only return members within our given base
members_within_base(members)
end
# AD requires use of range retrieval for groups with more than 1500 members
# cf. https://msdn.microsoft.com/en-us/library/aa367017(v=vs.85).aspx
def ranged_members(entry)
members = []
# Concatenate the members in the current range
dns = entry[member_range_attribute(entry)]
dns = normalize_dns(dns)
members.concat(dns)
# Recursively concatenate members until end of ranges
if has_more_member_ranges?(entry)
next_entry = adapter.group_members_in_range(dn, next_member_range_start(entry))
members.concat(ranged_members(next_entry))
end
members
end
# Process any AD nested groups. Use a manual process because
# the AD recursive member of filter is too slow and uses too
# much CPU on the AD server.
def nested_members(nested_groups_to_skip)
# Ignore this group if we see it again in a nested group.
# Prevents infinite loops.
nested_groups_to_skip << dn
members = []
nested_groups = adapter.nested_groups(dn)
nested_groups.each do |nested_group|
next if nested_groups_to_skip.include?(nested_group.dn)
members.concat(nested_group.member_dns(nested_groups_to_skip))
end
members
end
def has_more_member_ranges?(entry)
next_member_range_start(entry).present?
end
def next_member_range_start(entry)
match = member_range_attribute(entry).match /^member;range=\d+-(\d+|\*)$/
match[1].to_i + 1 if match.present? && match[1] != '*'
end
# The old AD recursive member filter would exclude any members that
# were outside the given search base. To maintain that behavior,
# we need to do the same.
#
# Split the base and each member DN into pairs. Compare the last
# base N pairs of the member DN. If they match, the user is within
# the base DN.
#
# Ex.
# - Member DN: 'uid=user,ou=users,dc=example,dc=com'
# - Base DN: 'dc=example,dc=com'
#
# Base has 2 pairs ([dc,example], [dc,com]). If the last 2 pairs of
# the user DN match, profit!
def members_within_base(members)
begin
base = ::Gitlab::Auth::LDAP::DN.new(adapter.config.base).to_a
rescue ::Gitlab::Auth::LDAP::DN::FormatError => e
Rails.logger.error "Configured LDAP `base` is invalid: '#{adapter.config.base}'. Error: \"#{e.message}\""
return []
end
members.select do |dn|
begin
::Gitlab::Auth::LDAP::DN.new(dn).to_a.last(base.length) == base
rescue ::Gitlab::Auth::LDAP::DN::FormatError => e
Rails.logger.warn "Received invalid member DN from LDAP group '#{cn}': '#{dn}'. Error: \"#{e.message}\". Skipping"
end
end
end
def normalize_dns(dns)
dns.map do |dn|
::Gitlab::Auth::LDAP::Person.normalize_dn(dn)
end
end
def entry_member_dns(entry)
dns = entry.try(:member) || entry.try(:uniquemember) || entry.try(:memberof)
dns&.concat(entry.try(:submember) || [])
if dns
normalize_dns(dns)
else
Rails.logger.warn("Could not find member DNs for LDAP group #{entry.inspect}")
[]
end
end
end
end
end
end
end
require 'net/ldap/dn'
module EE
module Gitlab
module Auth
module LDAP
module Person
extend ActiveSupport::Concern
class_methods do
def find_by_email(email, adapter)
email_attributes = Array(adapter.config.attributes['email'])
email_attributes.each do |possible_attribute|
found_user = adapter.user(possible_attribute, email)
return found_user if found_user
end
nil
end
def find_by_kerberos_principal(principal, adapter)
uid, domain = principal.split('@', 2)
return nil unless uid && domain
# In multi-forest setups, there may be several users with matching
# uids but differing DNs, so skip adapters configured to connect to
# non-matching domains
return unless domain.casecmp(domain_from_dn(adapter.config.base)) == 0
find_by_uid(uid, adapter)
end
# Extracts the rightmost unbroken set of domain components from an
# LDAP DN and constructs a domain name from them
def domain_from_dn(dn)
dn_components = []
::Gitlab::Auth::LDAP::DN.new(dn).each_pair { |name, value| dn_components << { name: name, value: value } }
dn_components
.reverse
.take_while { |rdn| rdn[:name].casecmp('DC').zero? } # Domain Component
.map { |rdn| rdn[:value] }
.reverse
.join('.')
end
def ldap_attributes(config)
attributes = super + [
'memberof',
(config.sync_ssh_keys if config.sync_ssh_keys.is_a?(String))
]
attributes.compact.uniq
end
end
def ssh_keys
if config.sync_ssh_keys? && entry.respond_to?(config.sync_ssh_keys)
entry[config.sync_ssh_keys.to_sym]
.map { |key| key[/(ssh|ecdsa)-[^ ]+ [^\s]+/] }
.compact
else
[]
end
end
# We assume that the Kerberos username matches the configured uid
# attribute in LDAP. For Active Directory, this is `sAMAccountName`
def kerberos_principal
return nil unless uid
uid + '@' + self.class.domain_from_dn(dn).upcase
end
def memberof
return [] unless entry.attribute_names.include?(:memberof)
entry.memberof
end
def group_cns
memberof.map { |memberof_value| cn_from_memberof(memberof_value) }
end
def cn_from_memberof(memberof)
# Only get the first CN value of the string, that's the one that contains
# the group name
memberof.match(/(?:cn=([\w\s]+))/i)&.captures&.first
end
end
end
end
end
end
module EE
module Gitlab
module Auth
module LDAP
module Sync
class AdminUsers < Sync::Users
private
def attribute
:admin
end
def member_dns
return [] if admin_group.empty?
proxy.dns_for_group_cn(admin_group)
end
def admin_group
proxy.adapter.config.admin_group
end
end
end
end
end
end
end
module EE
module Gitlab
module Auth
module LDAP
module Sync
class ExternalUsers < Sync::Users
private
def attribute
:external
end
def member_dns
external_groups.flat_map do |group|
proxy.dns_for_group_cn(group)
end.uniq
end
def external_groups
proxy.adapter.config.external_groups
end
end
end
end
end
end
end
module EE
module Gitlab
module Auth
module LDAP
module Sync
class Group
attr_reader :provider, :group, :proxy
class << self
# Sync members across all providers for the given group.
def execute_all_providers(group)
return unless ldap_sync_ready?(group)
group.start_ldap_sync
Rails.logger.debug { "Started syncing all providers for '#{group.name}' group" }
# Shuffle providers to prevent a scenario where sync fails after a time
# and only the first provider or two get synced. This shuffles the order
# so subsequent syncs should eventually get to all providers. Obviously
# we should avoid failure, but this is an additional safeguard.
::Gitlab::Auth::LDAP::Config.providers.shuffle.each do |provider|
Sync::Proxy.open(provider) do |proxy|
new(group, proxy).update_permissions
end
end
group.finish_ldap_sync
Rails.logger.debug { "Finished syncing all providers for '#{group.name}' group" }
end
# Sync members across a single provider for the given group.
def execute(group, proxy)
return unless ldap_sync_ready?(group)
group.start_ldap_sync
Rails.logger.debug { "Started syncing '#{proxy.provider}' provider for '#{group.name}' group" }
sync_group = new(group, proxy)
sync_group.update_permissions
group.finish_ldap_sync
Rails.logger.debug { "Finished syncing '#{proxy.provider}' provider for '#{group.name}' group" }
end
def ldap_sync_ready?(group)
fail_stuck_group(group)
return true unless group.ldap_sync_started?
Rails.logger.warn "Group '#{group.name}' is not ready for LDAP sync. Skipping"
false
end
def fail_stuck_group(group)
return unless group.ldap_sync_started?
if group.ldap_sync_last_sync_at < 1.hour.ago
group.mark_ldap_sync_as_failed('The sync took too long to complete.')
end
end
end
def initialize(group, proxy)
@provider = proxy.provider
@group = group
@proxy = proxy
end
def update_permissions
unless group.ldap_sync_started?
logger.warn "Group '#{group.name}' LDAP sync status must be 'started' before updating permissions"
return
end
access_levels = AccessLevels.new
# Only iterate over group links for the current provider
group.ldap_group_links.with_provider(provider).each do |group_link|
next unless group_link.active?
update_access_levels(access_levels, group_link)
end
update_existing_group_membership(group, access_levels)
add_new_members(group, access_levels)
end
private
def update_access_levels(access_levels, group_link)
if member_dns = get_member_dns(group_link)
access_levels.set(member_dns, to: group_link.group_access)
logger.debug do
"Resolved '#{group.name}' group member access: #{access_levels.to_hash}"
end
end
end
def get_member_dns(group_link)
group_link.cn ? dns_for_group_cn(group_link.cn) : UserFilter.filter(@proxy, group_link.filter)
end
def dns_for_group_cn(group_cn)
if config.group_base.blank?
logger.debug { "No `group_base` configured for '#{provider}' provider and group link CN #{group_cn}. Skipping" }
return nil
end
proxy.dns_for_group_cn(group_cn)
end
def dn_for_uid(uid)
proxy.dn_for_uid(uid)
end
def update_existing_group_membership(group, access_levels)
logger.debug { "Updating existing membership for '#{group.name}' group" }
select_and_preload_group_members(group).each do |member|
user = member.user
identity = user.identities.select(:id, :extern_uid)
.with_provider(provider).first
member_dn = identity.extern_uid.downcase
# Skip if this is not an LDAP user with a valid `extern_uid`.
next unless member_dn.present?
# Prevent shifting group membership, in case where user is a member
# of two LDAP groups from different providers linked to the same
# GitLab group. This is not ideal, but preserves existing behavior.
if user.ldap_identity.id != identity.id
access_levels.delete(member_dn)
next
end
desired_access = access_levels[member_dn]
# Skip validations and callbacks. We have a limited set of attrs
# due to the `select` lookup, and we need to be efficient.
# Low risk, because the member should already be valid.
member.update_column(:ldap, true) unless member.ldap?
# Don't do anything if the user already has the desired access level
if member.access_level == desired_access
access_levels.delete(member_dn)
next
end
# Check and update the access level. If `desired_access` is `nil`
# we need to delete the user from the group.
if desired_access.present?
# Delete this entry from the hash now that we're acting on it
access_levels.delete(member_dn)
next if member.ldap? && member.override?
add_or_update_user_membership(
user,
group,
desired_access
)
elsif group.last_owner?(user)
warn_cannot_remove_last_owner(user, group)
else
group.users.destroy(user)
end
end
end
def add_new_members(group, access_levels)
logger.debug { "Adding new members to '#{group.name}' group" }
access_levels.each do |member_dn, access_level|
user = ::Gitlab::Auth::LDAP::User.find_by_uid_and_provider(member_dn, provider)
if user.present?
add_or_update_user_membership(
user,
group,
access_level
)
else
logger.debug do
<<-MSG.strip_heredoc.tr("\n", ' ')
#{self.class.name}: User with DN `#{member_dn}` should have access
to '#{group.name}' group but there is no user in GitLab with that
identity. Membership will be updated once the user signs in for
the first time.
MSG
end
end
end
end
def add_or_update_user_membership(user, group, access, current_user: nil)
# Prevent the last owner of a group from being demoted
if access < ::Gitlab::Access::OWNER && group.last_owner?(user)
warn_cannot_remove_last_owner(user, group)
else
# If you pass the user object, instead of just user ID,
# it saves an extra user database query.
group.add_user(
user,
access,
current_user: current_user,
ldap: true
)
end
end
def warn_cannot_remove_last_owner(user, group)
logger.warn do
<<-MSG.strip_heredoc.tr("\n", ' ')
#{self.class.name}: LDAP group sync cannot remove #{user.name}
(#{user.id}) from group #{group.name} (#{group.id}) as this is
the group's last owner
MSG
end
end
def select_and_preload_group_members(group)
group.members.select(:id, :access_level, :user_id, :ldap, :override)
.with_identity_provider(provider).preload(:user)
end
def logger
Rails.logger
end
def config
@proxy.adapter.config
end
end
end
end
end
end
end
module EE
module Gitlab
module Auth
module LDAP
module Sync
class Groups
attr_reader :provider, :proxy
def self.execute
# Shuffle providers to prevent a scenario where sync fails after a time
# and only the first provider or two get synced. This shuffles the order
# so subsequent syncs should eventually get to all providers. Obviously
# we should avoid failure, but this is an additional safeguard.
::Gitlab::Auth::LDAP::Config.providers.shuffle.each do |provider|
Sync::Proxy.open(provider) do |proxy|
group_sync = self.new(proxy)
group_sync.update_permissions
end
end
true
end
def initialize(proxy)
@provider = proxy.provider
@proxy = proxy
end
def update_permissions
logger.debug { "Performing LDAP group sync for '#{provider}' provider" }
sync_groups
logger.debug { "Finished LDAP group sync for '#{provider}' provider" }
if config.admin_group.present?
logger.debug { "Syncing admin users for '#{provider}' provider" }
sync_admin_users
logger.debug { "Finished syncing admin users for '#{provider}' provider" }
else
logger.debug { "No `admin_group` configured for '#{provider}' provider. Skipping" }
end
if config.external_groups.empty?
logger.debug { "No `external_groups` configured for '#{provider}' provider. Skipping" }
else
logger.debug { "Syncing external users for '#{provider}' provider" }
sync_external_users
logger.debug { "Finished syncing external users for '#{provider}' provider" }
end
nil
end
private
def sync_groups
groups_where_group_links_with_provider_ordered.each do |group|
Sync::Group.execute(group, proxy)
end
end
def sync_admin_users
Sync::AdminUsers.execute(proxy)
end
def sync_external_users
Sync::ExternalUsers.execute(proxy)
end
def groups_where_group_links_with_provider_ordered
::Group.where_group_links_with_provider(provider)
.preload(:ldap_group_links)
.reorder('ldap_sync_last_successful_update_at ASC, namespaces.id ASC')
.distinct
end
def config
proxy.adapter.config
end
def logger
Rails.logger
end
end
end
end
end
end
end
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment