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
retry: 1
......
......@@ -124,8 +124,8 @@ Lint/DuplicateMethods:
- 'lib/gitlab/git/repository.rb'
- 'lib/gitlab/git/tree.rb'
- 'lib/gitlab/git/wiki_page.rb'
- 'lib/gitlab/ldap/person.rb'
- 'lib/gitlab/o_auth/user.rb'
- 'lib/gitlab/auth/ldap/person.rb'
- 'lib/gitlab/auth/o_auth/user.rb'
# Offense count: 4
Lint/InterpolationCheck:
......@@ -812,7 +812,7 @@ Style/TrivialAccessors:
Exclude:
- 'app/models/external_issue.rb'
- 'app/serializers/base_serializer.rb'
- 'lib/gitlab/ldap/person.rb'
- 'lib/gitlab/auth/ldap/person.rb'
- 'lib/system_check/base_check.rb'
# Offense count: 4
......
......@@ -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
- Add filtered search to MR page. !1243
- 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: Remove deprecated fields Notes#upvotes and Notes#downvotes. !1275 (Robert Schilling)
- Deploy board backend. !1278
......
......@@ -7,7 +7,7 @@ function onError() {
return flash;
}
function loadBalsamiqFile() {
export default function loadBalsamiqFile() {
const viewer = document.getElementById('js-balsamiq-viewer');
if (!(viewer instanceof Element)) return;
......@@ -17,5 +17,3 @@ function loadBalsamiqFile() {
const balsamiqViewer = new BalsamiqViewer(viewer);
balsamiqViewer.loadFile(endpoint).catch(onError);
}
$(loadBalsamiqFile);
import renderNotebook from './notebook';
document.addEventListener('DOMContentLoaded', renderNotebook);
export default renderNotebook;
import renderPDF from './pdf';
document.addEventListener('DOMContentLoaded', renderPDF);
export default renderPDF;
/* eslint-disable no-new */
import SketchLoader from './sketch';
document.addEventListener('DOMContentLoaded', () => {
export default () => {
const el = document.getElementById('js-sketch-viewer');
new SketchLoader(el);
});
};
import Renderer from './3d_viewer';
document.addEventListener('DOMContentLoaded', () => {
export default () => {
const viewer = new Renderer(document.getElementById('js-stl-viewer'));
[].slice.call(document.querySelectorAll('.js-material-changer')).forEach((el) => {
......@@ -16,4 +16,4 @@ document.addEventListener('DOMContentLoaded', () => {
viewer.changeObjectMaterials(target.dataset.type);
});
});
});
};
......@@ -5,6 +5,7 @@ import axios from '../../lib/utils/axios_utils';
export default class BlobViewer {
constructor() {
BlobViewer.initAuxiliaryViewer();
BlobViewer.initRichViewer();
this.initMainViewers();
}
......@@ -16,6 +17,38 @@ export default class BlobViewer {
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() {
this.$fileHolder = $('.file-holder');
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';
import FilteredSearchVisualTokens from './filtered_search_visual_tokens';
export default class FilteredSearchDropdownManager {
constructor(baseEndpoint = '', tokenizer, page, isGroup, filteredSearchTokenKeys) {
constructor({
baseEndpoint = '',
tokenizer,
page,
isGroup,
isGroupAncestor,
filteredSearchTokenKeys,
}) {
this.container = FilteredSearchContainer.container;
this.baseEndpoint = baseEndpoint.replace(/\/$/, '');
this.tokenizer = tokenizer;
this.filteredSearchTokenKeys = filteredSearchTokenKeys || FilteredSearchTokenKeys;
this.filteredSearchInput = this.container.querySelector('.filtered-search');
this.page = page;
this.groupsOnly = page === 'boards' && isGroup;
this.groupsOnly = isGroup;
this.groupAncestor = isGroupAncestor;
this.setupMapping();
......@@ -60,7 +68,7 @@ export default class FilteredSearchDropdownManager {
reference: null,
gl: DropdownNonUser,
extraArguments: {
endpoint: `${this.baseEndpoint}/milestones.json${this.groupsOnly ? '?only_group_milestones=true' : ''}`,
endpoint: this.getMilestoneEndpoint(),
symbol: '%',
},
element: this.container.querySelector('#js-dropdown-milestone'),
......@@ -69,7 +77,7 @@ export default class FilteredSearchDropdownManager {
reference: null,
gl: DropdownNonUser,
extraArguments: {
endpoint: `${this.baseEndpoint}/labels.json${this.groupsOnly ? '?only_group_labels=true' : ''}`,
endpoint: this.getLabelsEndpoint(),
symbol: '~',
preprocessing: DropdownUtils.duplicateLabelPreprocessing,
},
......@@ -96,6 +104,28 @@ export default class FilteredSearchDropdownManager {
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) {
const input = FilteredSearchContainer.container.querySelector('.filtered-search');
......
......@@ -20,10 +20,13 @@ import DropdownUtils from './dropdown_utils';
export default class FilteredSearchManager {
constructor({
page,
isGroup = false,
isGroupAncestor = false,
filteredSearchTokenKeys = FilteredSearchTokenKeys,
stateFiltersSelector = '.issues-state-filters',
}) {
this.isGroup = false;
this.isGroup = isGroup;
this.isGroupAncestor = isGroupAncestor;
this.states = ['opened', 'closed', 'merged', 'all'];
this.page = page;
......@@ -98,13 +101,14 @@ export default class FilteredSearchManager {
if (this.filteredSearchInput) {
this.tokenizer = FilteredSearchTokenizer;
this.dropdownManager = new FilteredSearchDropdownManager(
this.filteredSearchInput.getAttribute('data-base-endpoint') || '',
this.tokenizer,
this.page,
this.isGroup,
this.filteredSearchTokenKeys,
);
this.dropdownManager = new FilteredSearchDropdownManager({
baseEndpoint: this.filteredSearchInput.getAttribute('data-base-endpoint') || '',
tokenizer: this.tokenizer,
page: this.page,
isGroup: this.isGroup,
isGroupAncestor: this.isGroupAncestor,
filteredSearchTokenKeys: this.filteredSearchTokenKeys,
});
this.recentSearchesRoot = new RecentSearchesRoot(
this.recentSearchesStore,
......
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');
if (filteredSearchEnabled) {
const filteredSearchManager = new FilteredSearchManager({
page,
isGroup,
isGroupAncestor,
filteredSearchTokenKeys,
stateFiltersSelector,
});
......
......@@ -316,7 +316,7 @@
v-if="pipeline.flags.cancelable"
:endpoint="pipeline.cancel_path"
css-class="js-pipelines-cancel-button btn-remove"
title="Cancel"
title="Stop"
icon="close"
:pipeline-id="pipeline.id"
data-toggle="modal"
......
......@@ -14,6 +14,11 @@
collapsedCalendarIcon,
},
props: {
blockClass: {
type: String,
required: false,
default: '',
},
collapsed: {
type: Boolean,
required: false,
......@@ -91,7 +96,10 @@
</script>
<template>
<div class="block">
<div
class="block"
:class="blockClass"
>
<div class="issuable-sidebar-header">
<toggle-sidebar
:collapsed="collapsed"
......
......@@ -199,7 +199,7 @@ class ApplicationController < ActionController::Base
return unless signed_in? && session[:service_tickets]
valid = session[:service_tickets].all? do |provider, ticket|
Gitlab::OAuth::Session.valid?(provider, ticket)
Gitlab::Auth::OAuth::Session.valid?(provider, ticket)
end
unless valid
......@@ -223,7 +223,7 @@ class ApplicationController < ActionController::Base
if current_user && current_user.requires_ldap_check?
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
flash[:alert] = "Access denied for your LDAP account."
redirect_to new_user_session_path
......@@ -238,7 +238,7 @@ class ApplicationController < ActionController::Base
end
def gitlab_ldap_access(&block)
Gitlab::LDAP::Access.open { |access| yield(access) }
Gitlab::Auth::LDAP::Access.open { |access| yield(access) }
end
# JSON for infinite scroll via Pager object
......@@ -292,7 +292,7 @@ class ApplicationController < ActionController::Base
end
def github_import_configured?
Gitlab::OAuth::Provider.enabled?(:github)
Gitlab::Auth::OAuth::Provider.enabled?(:github)
end
def gitlab_import_enabled?
......@@ -300,7 +300,7 @@ class ApplicationController < ActionController::Base
end
def gitlab_import_configured?
Gitlab::OAuth::Provider.enabled?(:gitlab)
Gitlab::Auth::OAuth::Provider.enabled?(:gitlab)
end
def bitbucket_import_enabled?
......@@ -308,7 +308,7 @@ class ApplicationController < ActionController::Base
end
def bitbucket_import_configured?
Gitlab::OAuth::Provider.enabled?(:bitbucket)
Gitlab::Auth::OAuth::Provider.enabled?(:bitbucket)
end
def google_code_import_enabled?
......
......@@ -71,7 +71,7 @@ class Import::BitbucketController < Import::BaseController
end
def provider
Gitlab::OAuth::Provider.config_for('bitbucket')
Gitlab::Auth::OAuth::Provider.config_for('bitbucket')
end
def options
......
......@@ -11,8 +11,8 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
end
end
if Gitlab::LDAP::Config.enabled?
Gitlab::LDAP::Config.available_servers.each do |server|
if Gitlab::Auth::LDAP::Config.enabled?
Gitlab::Auth::LDAP::Config.available_servers.each do |server|
define_method server['provider_name'] do
ldap
end
......@@ -32,7 +32,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
# We only find ourselves here
# if the authentication to LDAP was successful.
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
@user = ldap_user.gl_user
......@@ -65,13 +65,13 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
redirect_to after_sign_in_path_for(current_user)
end
else
saml_user = Gitlab::Saml::User.new(oauth)
saml_user = Gitlab::Auth::Saml::User.new(oauth)
saml_user.save if saml_user.changed?
@user = saml_user.gl_user
continue_login_process
end
rescue Gitlab::OAuth::SignupDisabledError
rescue Gitlab::Auth::OAuth::User::SignupDisabledError
handle_signup_error
end
......@@ -119,20 +119,20 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
log_audit_event(current_user, with: oauth['provider'])
redirect_to profile_account_path, notice: 'Authentication method updated'
else
oauth_user = Gitlab::OAuth::User.new(oauth)
oauth_user = Gitlab::Auth::OAuth::User.new(oauth)
oauth_user.save
@user = oauth_user.gl_user
continue_login_process
end
rescue Gitlab::OAuth::SigninDisabledForProviderError
rescue Gitlab::Auth::OAuth::User::SigninDisabledForProviderError
handle_disabled_provider
rescue Gitlab::OAuth::SignupDisabledError
rescue Gitlab::Auth::OAuth::User::SignupDisabledError
handle_signup_error
end
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][provider] = ticket
end
......@@ -155,7 +155,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
end
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."
if Gitlab::CurrentSettings.allow_signup?
......@@ -184,7 +184,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
end
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"
redirect_to new_user_session_path
......
class Profiles::PasswordsController < Profiles::ApplicationController
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 :authorize_change_password!
......
......@@ -17,7 +17,7 @@ class SessionsController < Devise::SessionsController
def new
set_minimum_password_length
@ldap_servers = Gitlab::LDAP::Config.available_servers
@ldap_servers = Gitlab::Auth::LDAP::Config.available_servers
super
end
......
......@@ -78,7 +78,7 @@ module ApplicationSettingsHelper
label_tag(checkbox_name, class: css_class) do
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
......
......@@ -5,7 +5,7 @@ module AuthHelper
delegate :slack_app_id, to: :'Gitlab::CurrentSettings.current_application_settings'
def ldap_enabled?
Gitlab::LDAP::Config.enabled?
Gitlab::Auth::LDAP::Config.enabled?
end
def kerberos_enabled?
......@@ -21,11 +21,11 @@ module AuthHelper
end
def auth_providers
Gitlab::OAuth::Provider.providers
Gitlab::Auth::OAuth::Provider.providers
end
def label_for_provider(name)
Gitlab::OAuth::Provider.label_for(name)
Gitlab::Auth::OAuth::Provider.label_for(name)
end
def form_based_provider?(name)
......
......@@ -3,7 +3,7 @@ module ProfilesHelper
user_synced_attributes_metadata = current_user.user_synced_attributes_metadata
if user_synced_attributes_metadata&.synced?(attribute)
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
'LDAP'
end
......
......@@ -25,7 +25,7 @@ module SelectsHelper
def ldap_server_select_options
options_from_collection_for_select(
Gitlab::LDAP::Config.available_servers,
Gitlab::Auth::LDAP::Config.available_servers,
'provider_name',
'label'
)
......
......@@ -19,12 +19,12 @@ class Identity < ActiveRecord::Base
end
def ldap?
Gitlab::OAuth::Provider.ldap_provider?(provider)
Gitlab::Auth::OAuth::Provider.ldap_provider?(provider)
end
def self.normalize_uid(provider, uid)
if Gitlab::OAuth::Provider.ldap_provider?(provider)
Gitlab::LDAP::Person.normalize_dn(uid)
if Gitlab::Auth::OAuth::Provider.ldap_provider?(provider)
Gitlab::Auth::LDAP::Person.normalize_dn(uid)
else
uid.to_s
end
......
......@@ -146,7 +146,7 @@ class Repository
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 = {
repo: raw_repository,
ref: ref,
......@@ -156,7 +156,8 @@ class Repository
after: after,
before: before,
follow: Array(path).length == 1,
skip_merges: skip_merges
skip_merges: skip_merges,
all: all
}
commits = Gitlab::Git::Commit.where(options)
......
......@@ -746,7 +746,7 @@ class User < ActiveRecord::Base
def ldap_user?
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
identities.exists?(["provider LIKE ? AND extern_uid IS NOT NULL", "ldap%"])
end
......
......@@ -26,6 +26,6 @@ class UserSyncedAttributesMetadata < ActiveRecord::Base
private
def sync_profile_from_provider?
Gitlab::OAuth::Provider.sync_profile_from_provider?(provider)
Gitlab::Auth::OAuth::Provider.sync_profile_from_provider?(provider)
end
end
......@@ -203,7 +203,7 @@
Password authentication enabled for Git over HTTP(S)
.help-block
When disabled, a Personal Access Token
- if Gitlab::LDAP::Config.enabled?
- if Gitlab::Auth::LDAP::Config.enabled?
or LDAP password
must be used to authenticate.
- if omniauth_enabled? && button_based_providers.any?
......
......@@ -41,7 +41,7 @@
= f.submit 'Save changes', class: "btn btn-save"
= 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
= render 'ldap_group_links/form', group: @group
= render 'ldap_group_links/ldap_group_links', group: @group
......@@ -62,7 +62,7 @@
= 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-heading Active synchronizations
%ul.well-list
......
......@@ -4,7 +4,7 @@
.form-group
= f.label :provider, class: 'control-label'
.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'
.form-group
= f.label :extern_uid, "Identifier", class: 'control-label'
......
%tr
%td
#{Gitlab::OAuth::Provider.label_for(identity.provider)} (#{identity.provider})
#{Gitlab::Auth::OAuth::Provider.label_for(identity.provider)} (#{identity.provider})
%td
= identity.extern_uid
%td
......
- hidden = local_assigns.fetch(:hidden, false)
- 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?)
- 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
= render 'projects/blob/render_error', viewer: viewer
- 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 } }
- 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 } }
- 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 } }
- 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 } }
.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');
- content_for :page_specific_javascripts do
= webpack_bundle_tag('stl_viewer')
.file-content.is-stl-loading
.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')
......
---
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|
# manager.default_strategies(scope: :user).unshift :some_external_strategy
# end
if Gitlab::LDAP::Config.enabled?
Gitlab::LDAP::Config.providers.each do |provider|
ldap_config = Gitlab::LDAP::Config.new(provider)
if Gitlab::Auth::LDAP::Config.enabled?
Gitlab::Auth::LDAP::Config.providers.each do |provider|
ldap_config = Gitlab::Auth::LDAP::Config.new(provider)
config.omniauth(provider, ldap_config.omniauth_options)
end
end
......@@ -235,9 +235,9 @@ Devise.setup do |config|
if provider['name'] == 'cas3'
provider['args'][:on_single_sign_out] = lambda do |request|
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
end
end
......@@ -245,8 +245,8 @@ Devise.setup do |config|
if provider['name'] == 'authentiq'
provider['args'][:remote_sign_out_handler] = lambda do |request|
authentiq_session = request.params['sid']
if Gitlab::OAuth::Session.valid?(:authentiq, authentiq_session)
Gitlab::OAuth::Session.destroy(:authentiq, authentiq_session)
if Gitlab::Auth::OAuth::Session.valid?(:authentiq, authentiq_session)
Gitlab::Auth::OAuth::Session.destroy(:authentiq, authentiq_session)
true
else
false
......
......@@ -18,13 +18,26 @@ module Sidekiq
%i(perform_async perform_at perform_in).each do |name|
define_method(name) do |*args|
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
race conditions when the worker runs before the transaction is committed and
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.
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
super(*args)
......
if Gitlab::LDAP::Config.enabled?
if Gitlab::Auth::LDAP::Config.enabled?
module OmniAuth::Strategies
Gitlab::LDAP::Config.available_servers.each do |server|
Gitlab::Auth::LDAP::Config.available_servers.each do |server|
# do not redeclare LDAP
next if server['provider_name'] == 'ldap'
......
API::API.logger Rails.logger
mount API::API => '/'
::API::API.logger Rails.logger
mount ::API::API => '/'
......@@ -48,15 +48,9 @@ function generateEntries() {
autoEntriesCount = Object.keys(autoEntries).length;
const manualEntries = {
balsamiq_viewer: './blob/balsamiq_viewer.js',
filtered_search: './filtered_search/filtered_search_bundle.js',
monitoring: './monitoring/monitoring_bundle.js',
mr_notes: './mr_notes/index.js',
notebook_viewer: './blob/notebook_viewer.js',
pdf_viewer: './blob/pdf_viewer.js',
registry_list: './registry/index.js',
sketch_viewer: './blob/sketch_viewer.js',
stl_viewer: './blob/stl_viewer.js',
terminal: './terminal/terminal_bundle.js',
two_factor_auth: './two_factor_auth.js',
......@@ -81,7 +75,6 @@ function generateEntries() {
ldap_group_links: 'ee/groups/ldap_group_links.js',
mirrors: 'ee/mirrors',
service_desk: 'ee/projects/settings_service_desk/service_desk_bundle.js',
service_desk_issues: 'ee/service_desk_issues/index.js',
roadmap: 'ee/roadmap',
};
......@@ -239,33 +232,6 @@ const config = {
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
new webpack.optimize.CommonsChunkPlugin({
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 @@
#
# 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
enable_extension "plpgsql"
......@@ -1866,7 +1866,7 @@ ActiveRecord::Schema.define(version: 20180222043024) do
t.datetime "created_at"
t.datetime "updated_at"
t.integer "creator_id"
t.integer "namespace_id"
t.integer "namespace_id", null: false
t.datetime "last_activity_at"
t.string "import_url"
t.integer "visibility_level", default: 0, null: false
......
......@@ -327,7 +327,7 @@ step of the sync.
1. Run a group sync for this particular group.
```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)
below for more information about the output.
......@@ -336,11 +336,11 @@ step of the sync.
run the following query:
```ruby
adapter = Gitlab::LDAP::Adapter.new('ldapmain') # If `main` is the LDAP provider
ldap_group = EE::Gitlab::LDAP::Group.find_by_cn('group_cn_here', adapter)
adapter = Gitlab::Auth::LDAP::Adapter.new('ldapmain') # If `main` is the LDAP provider
ldap_group = EE::Gitlab::Auth::LDAP::Group.find_by_cn('group_cn_here', adapter)
# 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.
One of the DNs here should match the 'Identifier' from the LDAP identity
......
......@@ -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 |
| `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 |
| `path` | string | no | The file path |
| `all` | boolean | no | Retrieve every commit from the repository |
```bash
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
"project_id": 3,
"push_events": true,
"issues_events": true,
"confidential_issues_events": true,
"merge_requests_events": true,
"tag_push_events": true,
"note_events": true,
......@@ -1229,6 +1230,7 @@ POST /projects/:id/hooks
| `url` | string | yes | The hook URL |
| `push_events` | boolean | no | Trigger hook on push 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 |
| `tag_push_events` | boolean | no | Trigger hook on tag push events |
| `note_events` | boolean | no | Trigger hook on note events |
......@@ -1253,6 +1255,7 @@ PUT /projects/:id/hooks/:hook_id
| `url` | string | yes | The hook URL |
| `push_events` | boolean | no | Trigger hook on push 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 |
| `tag_push_events` | boolean | no | Trigger hook on tag push events |
| `note_events` | boolean | no | Trigger hook on note events |
......
......@@ -619,6 +619,7 @@ Example response:
"active": true,
"push_events": true,
"issues_events": true,
"confidential_issues_events": true,
"merge_requests_events": true,
"tag_push_events": true,
"note_events": true,
......
......@@ -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
### 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
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:
- `ee/app/validators/foo_attr_validator.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
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
`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.
### 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
### Page-specific JavaScript
......@@ -87,6 +96,7 @@ General tips:
- 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.
- 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:
[d3]: https://d3js.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
[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!
- Dutch
- Esperanto
- French
- Rémy Coutable - [GitLab](https://gitlab.com/rymai), [Crowdin](https://crowdin.com/profile/rymai)
- German
- Italian
- 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.
Remember to **Save** each translation.
## Translation Guidelines
## General Translation Guidelines
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 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.
Some technical terms should be treated like proper nouns and not be translated.
Technical terms that should always be in English are noted in the glossary when using
[translate.gitlab.com](https://translate.gitlab.com).
Technical terms that should always be in English are noted in the glossary when
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
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
suitable level of formality.
You can refer to other translated strings and notes in the glossary to assist
determining a suitable level of formality.
### Inclusive language
[Diversity] is one of GitLab's values.
We ask you to avoid translations which exclude people based on their gender or ethnicity.
In languages which distinguish between a male and female form,
use both or choose a neutral formulation.
We ask you to avoid translations which exclude people based on their gender or
ethnicity.
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).
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
[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.
# Download and compile from source
cd /tmp
curl --remote-name --progress https://www.kernel.org/pub/software/scm/git/git-2.14.3.tar.gz
echo '023ffff6d3ba8a1bea779dfecc0ed0bb4ad68ab8601d14435dd8c08416f78d7f git-2.14.3.tar.gz' | shasum -a256 -c - && tar -xzf git-2.14.3.tar.gz
cd git-2.14.3/
curl --remote-name --progress https://www.kernel.org/pub/software/scm/git/git-2.16.2.tar.gz
echo '9acc4339b7a2ab484eea69d705923271682b7058015219cf5a7e6ed8dee5b5fb git-2.16.2.tar.gz' | shasum -a256 -c - && tar -xzf git-2.16.2.tar.gz
cd git-2.16.2/
./configure
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';
document.addEventListener('DOMContentLoaded', () => {
const filteredSearchEnabled = FilteredSearchManager && document.querySelector('.filtered-search');
if (filteredSearchEnabled) {
const filteredSearchManager = new FilteredSearchManager({
page: 'epics',
filteredSearchTokenKeys: FilteredSearchTokenKeysEpics,
stateFiltersSelector: '.epics-state-filters',
});
filteredSearchManager.setup();
}
initFilteredSearch({
page: 'epics',
filteredSearchTokenKeys: FilteredSearchTokenKeysEpics,
stateFiltersSelector: '.epics-state-filters',
});
});
......@@ -28,4 +28,3 @@ export default class FilteredSearchServiceDesk extends FilteredSearchManager {
return onlyValidParams;
}
}
......@@ -59,7 +59,7 @@ module EE
def render_approvals_json
respond_to do |format|
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
end
end
......
......@@ -17,6 +17,6 @@ class Groups::LdapsController < Groups::ApplicationController
private
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
......@@ -337,7 +337,9 @@ module EE
end
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
def username_only_import_url
......
......@@ -29,14 +29,14 @@ class LdapGroupLink < ActiveRecord::Base
end
def config
Gitlab::LDAP::Config.new(provider)
rescue Gitlab::LDAP::Config::InvalidProvider
Gitlab::Auth::LDAP::Config.new(provider)
rescue Gitlab::Auth::LDAP::Config::InvalidProvider
nil
end
# default to the first LDAP server
def provider
read_attribute(:provider) || Gitlab::LDAP::Config.providers.first
read_attribute(:provider) || Gitlab::Auth::LDAP::Config.providers.first
end
def provider_label
......
......@@ -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 :refresh_remote, 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 :started, -> { with_update_status(:started) }
......
class GeoNodeStatusSerializer < BaseSerializer
entity API::Entities::GeoNodeStatus
entity EE::API::Entities::GeoNodeStatus
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
= link_to group_ldap_group_links_path(@group), title: 'LDAP Group' do
%span
......
- page_title "Epics"
- content_for :page_specific_javascripts do
= webpack_bundle_tag 'common_vue'
= webpack_bundle_tag 'filtered_search'
.top-area
= render 'shared/issuable/epic_nav', type: :epics
......
......@@ -5,8 +5,6 @@
- content_for :page_specific_javascripts do
= webpack_bundle_tag 'common_vue'
= webpack_bundle_tag 'filtered_search'
= webpack_bundle_tag 'service_desk_issues'
= webpack_bundle_tag 'issues'
- content_for :breadcrumbs_extra do
......
......@@ -3,10 +3,10 @@ class LdapAllGroupsSyncWorker
include CronjobQueue
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'
EE::Gitlab::LDAP::Sync::Groups.execute
EE::Gitlab::Auth::LDAP::Sync::Groups.execute
logger.info 'Finished LDAP group sync'
end
end
......@@ -2,12 +2,12 @@ class LdapGroupSyncWorker
include ApplicationWorker
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))
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)
end
else
......@@ -23,9 +23,9 @@ class LdapGroupSyncWorker
logger.info "Started LDAP group sync for group #{group.name} (#{group.id})"
if proxy
EE::Gitlab::LDAP::Sync::Group.execute(group, proxy)
EE::Gitlab::Auth::LDAP::Sync::Group.execute(group, proxy)
else
EE::Gitlab::LDAP::Sync::Group.execute_all_providers(group)
EE::Gitlab::Auth::LDAP::Sync::Group.execute_all_providers(group)
end
logger.info "Finished LDAP group sync for group #{group.name} (#{group.id})"
......
......@@ -3,14 +3,14 @@ class LdapSyncWorker
include CronjobQueue
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."
User.ldap.find_each(batch_size: 100).each do |ldap_user|
Rails.logger.debug "Syncing user #{ldap_user.username}, #{ldap_user.email}"
# Use the 'update_ldap_group_links_synchronously' option to avoid creating a ton
# 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
---
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
class UpdateGroupLinks < ActiveRecord::Migration
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")
end
end
......@@ -54,7 +54,7 @@ module API
# The issues list in the correct order in body will be returned as part of #4250
if result
present epic.issues(current_user),
with: Entities::EpicIssue,
with: EE::API::Entities::EpicIssue,
current_user: current_user
else
render_api_error!({ error: "Issue could not be moved!" }, 400)
......@@ -62,7 +62,7 @@ module API
end
desc 'Get issues assigned to the epic' do
success Entities::EpicIssue
success EE::API::Entities::EpicIssue
end
params do
requires :epic_iid, type: Integer, desc: 'The iid of the epic'
......@@ -71,12 +71,12 @@ module API
authorize_can_read!
present epic.issues(current_user),
with: Entities::EpicIssue,
with: EE::API::Entities::EpicIssue,
current_user: current_user
end
desc 'Assign an issue to the epic' do
success Entities::EpicIssueLink
success EE::API::Entities::EpicIssueLink
end
params do
requires :epic_iid, type: Integer, desc: 'The iid of the epic'
......@@ -93,14 +93,14 @@ module API
if result[:status] == :success
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
render_api_error!(result[:message], result[:http_status])
end
end
desc 'Remove an issue from the epic' do
success Entities::EpicIssueLink
success EE::API::Entities::EpicIssueLink
end
params do
requires :epic_iid, type: Integer, desc: 'The iid of the epic'
......@@ -112,7 +112,7 @@ module API
result = ::EpicIssues::DestroyService.new(link, current_user).execute
if result[:status] == :success
present link, with: Entities::EpicIssueLink
present link, with: EE::API::Entities::EpicIssueLink
else
render_api_error!(result[:message], result[:http_status])
end
......
......@@ -46,7 +46,7 @@ module API
resource :groups, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
desc 'Get epics for the group' do
success Entities::Epic
success EE::API::Entities::Epic
end
params do
optional :order_by, type: String, values: %w[created_at updated_at], default: 'created_at',
......@@ -58,11 +58,11 @@ module API
optional :labels, type: String, desc: 'Comma-separated list of label names'
end
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
desc 'Get details of an epic' do
success Entities::Epic
success EE::API::Entities::Epic
end
params do
requires :epic_iid, type: Integer, desc: 'The internal ID of an epic'
......@@ -70,11 +70,11 @@ module API
get ':id/-/epics/:epic_iid' do
authorize_can_read!
present epic, with: Entities::Epic
present epic, with: EE::API::Entities::Epic
end
desc 'Create a new epic' do
success Entities::Epic
success EE::API::Entities::Epic
end
params do
requires :title, type: String, desc: 'The title of an epic'
......@@ -88,14 +88,14 @@ module API
epic = ::Epics::CreateService.new(user_group, current_user, declared_params(include_missing: false)).execute
if epic.valid?
present epic, with: Entities::Epic
present epic, with: EE::API::Entities::Epic
else
render_validation_error!(epic)
end
end
desc 'Update an epic' do
success Entities::Epic
success EE::API::Entities::Epic
end
params do
requires :epic_iid, type: Integer, desc: 'The internal ID of an epic'
......@@ -114,14 +114,14 @@ module API
result = ::Epics::UpdateService.new(user_group, current_user, update_params).execute(epic)
if result.valid?
present result, with: Entities::Epic
present result, with: EE::API::Entities::Epic
else
render_validation_error!(result)
end
end
desc 'Destroy an epic' do
success Entities::Epic
success EE::API::Entities::Epic
end
params do
requires :epic_iid, type: Integer, desc: 'The internal ID of an epic'
......
......@@ -36,7 +36,7 @@ module API
authenticate_by_gitlab_geo_node_token!
status = ::GeoNodeStatus.current_node_status
present status, with: Entities::GeoNodeStatus
present status, with: EE::API::Entities::GeoNodeStatus
end
end
......
......@@ -12,13 +12,13 @@ module API
# Example request:
# GET /geo_nodes
desc 'Retrieves the available Geo nodes' do
success Entities::GeoNode
success EE::API::Entities::GeoNode
end
get do
nodes = GeoNode.all
present paginate(nodes), with: Entities::GeoNode
present paginate(nodes), with: EE::API::Entities::GeoNode
end
# Get all Geo node statuses
......@@ -26,12 +26,12 @@ module API
# Example request:
# GET /geo_nodes/status
desc 'Get status for all Geo nodes' do
success Entities::GeoNodeStatus
success EE::API::Entities::GeoNodeStatus
end
get '/status' do
status = GeoNodeStatus.all
present paginate(status), with: Entities::GeoNodeStatus
present paginate(status), with: EE::API::Entities::GeoNodeStatus
end
# Get project registry failures for the current Geo node
......@@ -78,12 +78,12 @@ module API
# Example request:
# GET /geo_nodes/:id
desc 'Get a single GeoNode' do
success Entities::GeoNode
success EE::API::Entities::GeoNode
end
get do
not_found!('GeoNode') unless geo_node
present geo_node, with: Entities::GeoNode
present geo_node, with: EE::API::Entities::GeoNode
end
# Get Geo metrics for a single node
......@@ -91,14 +91,14 @@ module API
# Example request:
# GET /geo_nodes/:id/status
desc 'Get metrics for a single Geo node' do
success Entities::GeoNodeStatus
success EE::API::Entities::GeoNodeStatus
end
get 'status' do
not_found!('GeoNode') unless geo_node
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
# Repair authentication of the Geo node
......@@ -106,14 +106,14 @@ module API
# Example request:
# POST /geo_nodes/:id/repair
desc 'Repair authentication of the Geo node' do
success Entities::GeoNodeStatus
success EE::API::Entities::GeoNodeStatus
end
post 'repair' do
not_found!('GeoNode') unless geo_node
if !geo_node.missing_oauth_application? || geo_node.repair
status 200
present geo_node_status, with: Entities::GeoNodeStatus
present geo_node_status, with: EE::API::Entities::GeoNodeStatus
else
render_validation_error!(geo_node)
end
......@@ -124,7 +124,7 @@ module API
# Example request:
# PUT /geo_nodes/:id
desc 'Edit an existing Geo secondary node' do
success Entities::GeoNode
success EE::API::Entities::GeoNode
end
params do
optional :enabled, type: Boolean, desc: 'Flag indicating if the Geo node is enabled'
......@@ -140,7 +140,7 @@ module API
if geo_node.primary?
forbidden!('Primary node cannot be edited')
elsif geo_node.update_attributes(update_params)
present geo_node, with: Entities::GeoNode
present geo_node, with: EE::API::Entities::GeoNode
else
render_validation_error!(geo_node)
end
......
......@@ -10,20 +10,20 @@ module API
end
resource :projects, requirements: { id: %r{[^/]+} } do
desc 'Get related issues' do
success Entities::RelatedIssue
success EE::API::Entities::RelatedIssue
end
get ':id/issues/:issue_iid/links' do
source_issue = find_project_issue(params[:issue_iid])
related_issues = source_issue.related_issues(current_user)
present related_issues,
with: Entities::RelatedIssue,
with: EE::API::Entities::RelatedIssue,
current_user: current_user,
project: user_project
end
desc 'Relate issues' do
success Entities::IssueLink
success EE::API::Entities::IssueLink
end
params do
requires :target_project_id, type: String, desc: 'The ID of the target project'
......@@ -43,14 +43,14 @@ module API
if result[:status] == :success
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
render_api_error!(result[:message], result[:http_status])
end
end
desc 'Remove issues relation' do
success Entities::IssueLink
success EE::API::Entities::IssueLink
end
params do
requires :issue_link_id, type: Integer, desc: 'The ID of an issue link'
......@@ -66,7 +66,7 @@ module API
.execute
if result[:status] == :success
present issue_link, with: Entities::IssueLink
present issue_link, with: EE::API::Entities::IssueLink
else
render_api_error!(result[:message], result[:http_status])
end
......
......@@ -6,7 +6,7 @@ module API
helpers do
def get_group_list(provider, 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
params :search_params do
......@@ -15,26 +15,26 @@ module API
end
desc 'Get a LDAP groups list. Limit size to 20 of them.' do
success Entities::LdapGroup
success EE::API::Entities::LdapGroup
end
params do
use :search_params
end
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])
present groups, with: Entities::LdapGroup
present groups, with: EE::API::Entities::LdapGroup
end
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
params do
use :search_params
end
get ':provider/groups' do
groups = get_group_list(params[:provider], params[:search])
present groups, with: Entities::LdapGroup
present groups, with: EE::API::Entities::LdapGroup
end
end
end
......
......@@ -7,7 +7,7 @@ module API
end
resource :groups do
desc 'Add a linked LDAP group to group' do
success Entities::LdapGroupLink
success EE::API::Entities::LdapGroupLink
end
params do
requires 'cn', type: String, desc: 'The CN of a LDAP group'
......@@ -21,7 +21,7 @@ module API
ldap_group_link = group.ldap_group_links.new(declared_params(include_missing: false))
if ldap_group_link.save
present ldap_group_link, with: Entities::LdapGroupLink
present ldap_group_link, with: EE::API::Entities::LdapGroupLink
else
render_api_error!(ldap_group_link.errors.full_messages.first, 409)
end
......
......@@ -4,16 +4,16 @@ module API
resource :license do
desc 'Get information on the currently active license' do
success Entities::GitlabLicense
success EE::API::Entities::GitlabLicense
end
get do
license = ::License.current
present license, with: Entities::GitlabLicense
present license, with: EE::API::Entities::GitlabLicense
end
desc 'Add a new license' do
success Entities::GitlabLicense
success EE::API::Entities::GitlabLicense
end
params do
requires :license, type: String, desc: 'The license text'
......@@ -21,7 +21,7 @@ module API
post do
license = ::License.new(data: params[:license])
if license.save
present license, with: Entities::GitlabLicense
present license, with: EE::API::Entities::GitlabLicense
else
render_api_error!(license.errors.full_messages.first, 400)
end
......
......@@ -25,15 +25,15 @@ module API
end
desc 'Get project push rule' do
success Entities::ProjectPushRule
success EE::API::Entities::ProjectPushRule
end
get ":id/push_rule" do
push_rule = user_project.push_rule
present push_rule, with: Entities::ProjectPushRule
present push_rule, with: EE::API::Entities::ProjectPushRule
end
desc 'Add a push rule to a project' do
success Entities::ProjectPushRule
success EE::API::Entities::ProjectPushRule
end
params do
use :push_rule_params
......@@ -43,12 +43,12 @@ module API
error!("Project push rule exists", 422)
else
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
desc 'Update an existing project push rule' do
success Entities::ProjectPushRule
success EE::API::Entities::ProjectPushRule
end
params do
use :push_rule_params
......@@ -58,7 +58,7 @@ module API
not_found!('Push Rule') unless push_rule
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
render_validation_error!(push_rule)
end
......
......@@ -20,16 +20,16 @@ module API
end
desc 'Get project push rule' do
success ::API::Entities::ProjectPushRule
success EE::API::Entities::ProjectPushRule
detail DEPRECATION_MESSAGE
end
get ":id/git_hook" do
push_rule = user_project.push_rule
present push_rule, with: ::API::Entities::ProjectPushRule
present push_rule, with: EE::API::Entities::ProjectPushRule
end
desc 'Add a push rule to a project' do
success ::API::Entities::ProjectPushRule
success EE::API::Entities::ProjectPushRule
detail DEPRECATION_MESSAGE
end
params do
......@@ -40,12 +40,12 @@ module API
error!("Project push rule exists", 422)
else
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
desc 'Update an existing project push rule' do
success ::API::Entities::ProjectPushRule
success EE::API::Entities::ProjectPushRule
detail DEPRECATION_MESSAGE
end
params do
......@@ -56,7 +56,7 @@ module API
not_found!('Push Rule') unless push_rule
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
render_validation_error!(push_rule)
end
......
......@@ -30,7 +30,7 @@ module Audit
when :remove
"Removed #{value}"
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
value
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