summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Gemfile1
-rw-r--r--Gemfile.lock3
-rw-r--r--app/assets/images/auth_buttons/authentiq_64.pngbin0 -> 17679 bytes
-rw-r--r--app/assets/javascripts/issuable.js.es614
-rw-r--r--app/assets/stylesheets/framework.scss1
-rw-r--r--app/assets/stylesheets/framework/badges.scss11
-rw-r--r--app/assets/stylesheets/framework/nav.scss9
-rw-r--r--app/assets/stylesheets/framework/variables.scss8
-rw-r--r--app/assets/stylesheets/pages/environments.scss9
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss2
-rw-r--r--app/controllers/groups_controller.rb2
-rw-r--r--app/helpers/auth_helper.rb2
-rw-r--r--app/models/namespace.rb4
-rw-r--r--app/services/groups/update_service.rb8
-rw-r--r--app/validators/project_path_validator.rb3
-rw-r--r--app/views/layouts/nav/_admin.html.haml2
-rw-r--r--app/views/layouts/nav/_group.html.haml4
-rw-r--r--app/views/layouts/nav/_project.html.haml4
-rw-r--r--app/views/projects/merge_requests/widget/_heading.html.haml2
-rw-r--r--app/views/shared/empty_states/_issues.html.haml6
-rw-r--r--app/views/shared/issuable/_milestone_dropdown.html.haml2
-rw-r--r--changelogs/unreleased/25895-fix-headers-in-ci-api-helpers.yml4
-rw-r--r--changelogs/unreleased/25898-ci-icon-color-mr.yml4
-rw-r--r--changelogs/unreleased/8038-authentiq-id-oauth-support.yml4
-rw-r--r--changelogs/unreleased/badge-color-on-white-bg.yml4
-rw-r--r--changelogs/unreleased/dz-rename-invalid-groups.yml4
-rw-r--r--changelogs/unreleased/dz-whitelist-dashboard-project-path.yml4
-rw-r--r--changelogs/unreleased/fix-copy-issues-empty-state.yml4
-rw-r--r--changelogs/unreleased/fix-group-path-rename-error.yml4
-rw-r--r--changelogs/unreleased/fix-import-labels-error.yml4
-rw-r--r--config/gitlab.yml.example10
-rw-r--r--db/migrate/20161220141214_remove_dot_git_from_group_names.rb82
-rw-r--r--db/schema.rb4
-rw-r--r--doc/administration/auth/README.md2
-rw-r--r--doc/administration/auth/authentiq.md69
-rw-r--r--doc/integration/README.md2
-rw-r--r--doc/integration/omniauth.md1
-rw-r--r--lib/ci/api/helpers.rb2
-rw-r--r--lib/gitlab/import_export/relation_factory.rb28
-rw-r--r--lib/gitlab/middleware/multipart.rb8
-rw-r--r--lib/gitlab/update_path_error.rb3
-rw-r--r--spec/controllers/groups_controller_spec.rb21
-rw-r--r--spec/javascripts/fixtures/issuable_filter.html.haml8
-rw-r--r--spec/javascripts/issuable_spec.js.es681
-rw-r--r--spec/lib/gitlab/import_export/project.json22
-rw-r--r--spec/lib/gitlab/middleware/multipart_spec.rb6
-rw-r--r--spec/requests/ci/api/builds_spec.rb5
-rw-r--r--spec/services/groups/update_service_spec.rb51
48 files changed, 479 insertions, 59 deletions
diff --git a/Gemfile b/Gemfile
index 774dceff4f4..9dfaf7a48a2 100644
--- a/Gemfile
+++ b/Gemfile
@@ -32,6 +32,7 @@ gem 'omniauth-saml', '~> 1.7.0'
gem 'omniauth-shibboleth', '~> 1.2.0'
gem 'omniauth-twitter', '~> 1.2.0'
gem 'omniauth_crowd', '~> 2.2.0'
+gem 'omniauth-authentiq', '~> 0.2.0'
gem 'rack-oauth2', '~> 1.2.1'
gem 'jwt'
diff --git a/Gemfile.lock b/Gemfile.lock
index 8bc22479346..9f8367b420a 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -428,6 +428,8 @@ GEM
rack (>= 1.0, < 3)
omniauth-auth0 (1.4.1)
omniauth-oauth2 (~> 1.1)
+ omniauth-authentiq (0.2.2)
+ omniauth-oauth2 (~> 1.3, >= 1.3.1)
omniauth-azure-oauth2 (0.0.6)
jwt (~> 1.0)
omniauth (~> 1.0)
@@ -897,6 +899,7 @@ DEPENDENCIES
oj (~> 2.17.4)
omniauth (~> 1.3.1)
omniauth-auth0 (~> 1.4.1)
+ omniauth-authentiq (~> 0.2.0)
omniauth-azure-oauth2 (~> 0.0.6)
omniauth-cas3 (~> 1.1.2)
omniauth-facebook (~> 4.0.0)
diff --git a/app/assets/images/auth_buttons/authentiq_64.png b/app/assets/images/auth_buttons/authentiq_64.png
new file mode 100644
index 00000000000..81767bbcc54
--- /dev/null
+++ b/app/assets/images/auth_buttons/authentiq_64.png
Binary files differ
diff --git a/app/assets/javascripts/issuable.js.es6 b/app/assets/javascripts/issuable.js.es6
index 1c10a7445bb..9c3c96c20ed 100644
--- a/app/assets/javascripts/issuable.js.es6
+++ b/app/assets/javascripts/issuable.js.es6
@@ -1,13 +1,13 @@
-/* eslint-disable func-names, no-var, camelcase, no-unused-vars, object-shorthand, space-before-function-paren, no-return-assign, comma-dangle, consistent-return, one-var, one-var-declaration-per-line, quotes, prefer-template, prefer-arrow-callback, prefer-const, padded-blocks, wrap-iife, max-len */
+/* eslint-disable no-param-reassign, func-names, no-var, camelcase, no-unused-vars, object-shorthand, space-before-function-paren, no-return-assign, comma-dangle, consistent-return, one-var, one-var-declaration-per-line, quotes, prefer-template, prefer-arrow-callback, prefer-const, padded-blocks, wrap-iife, max-len */
/* global Issuable */
/* global Turbolinks */
-(function() {
+((global) => {
var issuable_created;
issuable_created = false;
- this.Issuable = {
+ global.Issuable = {
init: function() {
Issuable.initTemplates();
Issuable.initSearch();
@@ -111,7 +111,11 @@
filterResults: (function(_this) {
return function(form) {
var formAction, formData, issuesUrl;
- formData = form.serialize();
+ formData = form.serializeArray();
+ formData = formData.filter(function(data) {
+ return data.value !== '';
+ });
+ formData = $.param(formData);
formAction = form.attr('action');
issuesUrl = formAction;
issuesUrl += "" + (formAction.indexOf('?') < 0 ? '?' : '&');
@@ -184,4 +188,4 @@
}
};
-}).call(this);
+})(window);
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index 40bc0579393..3cf49f4ff1b 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -9,6 +9,7 @@
@import "framework/asciidoctor.scss";
@import "framework/blocks.scss";
@import "framework/buttons.scss";
+@import "framework/badges.scss";
@import "framework/calendar.scss";
@import "framework/callout.scss";
@import "framework/common.scss";
diff --git a/app/assets/stylesheets/framework/badges.scss b/app/assets/stylesheets/framework/badges.scss
new file mode 100644
index 00000000000..e9d7cda0647
--- /dev/null
+++ b/app/assets/stylesheets/framework/badges.scss
@@ -0,0 +1,11 @@
+.badge {
+ font-weight: normal;
+ background-color: $badge-bg;
+ color: $badge-color;
+ vertical-align: baseline;
+}
+
+.badge-dark {
+ background-color: $badge-bg-dark;
+ color: $badge-color-dark;
+}
diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss
index e4affbb1be1..bbf9de06630 100644
--- a/app/assets/stylesheets/framework/nav.scss
+++ b/app/assets/stylesheets/framework/nav.scss
@@ -76,13 +76,6 @@
color: $black;
}
}
-
- .badge {
- font-weight: normal;
- background-color: $nav-badge-bg;
- color: $gl-gray-light;
- vertical-align: baseline;
- }
}
&.sub-nav {
@@ -434,4 +427,4 @@
border-bottom: none;
}
}
-}
+} \ No newline at end of file
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 460c5d995be..a5fb097a47f 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -280,6 +280,14 @@ $btn-active-gray: #ececec;
$btn-active-gray-light: e4e7ed;
/*
+* Badges
+*/
+$badge-bg: #f3f3f3;
+$badge-bg-dark: #eee;
+$badge-color: #929292;
+$badge-color-dark: #8f8f8f;
+
+/*
* Award emoji
*/
$award-emoji-menu-shadow: rgba(0,0,0,.175);
diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss
index 3d60426de01..5517dc5dcbd 100644
--- a/app/assets/stylesheets/pages/environments.scss
+++ b/app/assets/stylesheets/pages/environments.scss
@@ -121,13 +121,6 @@
.folder-name {
cursor: pointer;
-
- .badge {
- font-weight: normal;
- background-color: $gray-darker;
- color: $gl-gray-light;
- vertical-align: baseline;
- }
}
}
@@ -142,4 +135,4 @@
margin-right: 0;
}
}
-}
+} \ No newline at end of file
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index 621b780ce4d..566de8a4eba 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -889,4 +889,4 @@
min-height: 450px;
}
}
-}
+} \ No newline at end of file
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index b83c3a872cf..efe9c001bcf 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -82,6 +82,8 @@ class GroupsController < Groups::ApplicationController
if Groups::UpdateService.new(@group, current_user, group_params).execute
redirect_to edit_group_path(@group), notice: "Group '#{@group.name}' was successfully updated."
else
+ @group.reset_path!
+
render action: "edit"
end
end
diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb
index 92bac149313..1ee6c1d3afa 100644
--- a/app/helpers/auth_helper.rb
+++ b/app/helpers/auth_helper.rb
@@ -1,5 +1,5 @@
module AuthHelper
- PROVIDERS_WITH_ICONS = %w(twitter github gitlab bitbucket google_oauth2 facebook azure_oauth2).freeze
+ PROVIDERS_WITH_ICONS = %w(twitter github gitlab bitbucket google_oauth2 facebook azure_oauth2 authentiq).freeze
FORM_BASED_PROVIDERS = [/\Aldap/, 'crowd'].freeze
def ldap_enabled?
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index fd42f2328d8..b52f08c7081 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -98,7 +98,7 @@ class Namespace < ActiveRecord::Base
def move_dir
if any_project_has_container_registry_tags?
- raise Exception.new('Namespace cannot be moved, because at least one project has tags in container registry')
+ raise Gitlab::UpdatePathError.new('Namespace cannot be moved, because at least one project has tags in container registry')
end
# Move the namespace directory in all storages paths used by member projects
@@ -111,7 +111,7 @@ class Namespace < ActiveRecord::Base
# if we cannot move namespace directory we should rollback
# db changes in order to prevent out of sync between db and fs
- raise Exception.new('namespace directory cannot be moved')
+ raise Gitlab::UpdatePathError.new('namespace directory cannot be moved')
end
end
diff --git a/app/services/groups/update_service.rb b/app/services/groups/update_service.rb
index fff2273f402..4e878ec556a 100644
--- a/app/services/groups/update_service.rb
+++ b/app/services/groups/update_service.rb
@@ -14,7 +14,13 @@ module Groups
group.assign_attributes(params)
- group.save
+ begin
+ group.save
+ rescue Gitlab::UpdatePathError => e
+ group.errors.add(:base, e.message)
+
+ false
+ end
end
end
end
diff --git a/app/validators/project_path_validator.rb b/app/validators/project_path_validator.rb
index 927c67b65b0..d9ab8f167d8 100644
--- a/app/validators/project_path_validator.rb
+++ b/app/validators/project_path_validator.rb
@@ -14,7 +14,8 @@ class ProjectPathValidator < ActiveModel::EachValidator
# without tree as reserved name routing can match 'group/project' as group name,
# 'tree' as project name and 'deploy_keys' as route.
#
- RESERVED = (NamespaceValidator::RESERVED +
+ RESERVED = (NamespaceValidator::RESERVED -
+ %w[dashboard] +
%w[tree commits wikis new edit create update logs_tree
preview blob blame raw files create_dir find_file]).freeze
diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml
index ac04f57e217..b69114c96cc 100644
--- a/app/views/layouts/nav/_admin.html.haml
+++ b/app/views/layouts/nav/_admin.html.haml
@@ -31,7 +31,7 @@
= link_to admin_abuse_reports_path, title: "Abuse Reports" do
%span
Abuse Reports
- %span.badge.count= number_with_delimiter(AbuseReport.count(:all))
+ %span.badge.badge-dark.count= number_with_delimiter(AbuseReport.count(:all))
- if askimet_enabled?
= nav_link(controller: :spam_logs) do
diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml
index f3539fd372d..221f3ec1ffe 100644
--- a/app/views/layouts/nav/_group.html.haml
+++ b/app/views/layouts/nav/_group.html.haml
@@ -26,13 +26,13 @@
%span
Issues
- issues = IssuesFinder.new(current_user, group_id: @group.id, state: 'opened').execute
- %span.badge.count= number_with_delimiter(issues.count)
+ %span.badge.badge-dark.count= number_with_delimiter(issues.count)
= nav_link(path: 'groups#merge_requests') do
= link_to merge_requests_group_path(@group), title: 'Merge Requests' do
%span
Merge Requests
- merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened', non_archived: true).execute
- %span.badge.count= number_with_delimiter(merge_requests.count)
+ %span.badge.badge-dark.count= number_with_delimiter(merge_requests.count)
= nav_link(controller: [:group_members]) do
= link_to group_group_members_path(@group), title: 'Members' do
%span
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index 904d11c2cf4..cc1571cbb4f 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -61,14 +61,14 @@
%span
Issues
- if @project.default_issues_tracker?
- %span.badge.count.issue_counter= number_with_delimiter(IssuesFinder.new(current_user, project_id: @project.id).execute.opened.count)
+ %span.badge.badge-dark.count.issue_counter= number_with_delimiter(IssuesFinder.new(current_user, project_id: @project.id).execute.opened.count)
- if project_nav_tab? :merge_requests
= nav_link(controller: :merge_requests) do
= link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do
%span
Merge Requests
- %span.badge.count.merge_counter= number_with_delimiter(MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened.count)
+ %span.badge.badge-dark.count.merge_counter= number_with_delimiter(MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened.count)
- if project_nav_tab? :wiki
= nav_link(controller: :wikis) do
diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml
index 9ab7971b56c..5bc417d1760 100644
--- a/app/views/projects/merge_requests/widget/_heading.html.haml
+++ b/app/views/projects/merge_requests/widget/_heading.html.haml
@@ -17,7 +17,7 @@
- # TODO, remove in later versions when services like Jenkins will set CI status via Commit status API
.mr-widget-heading
- %w[success skipped canceled failed running pending].each do |status|
- .ci_widget{class: "ci-#{status}", style: "display:none"}
+ .ci_widget{class: "ci-#{status} ci-status-icon-#{status}", style: "display:none"}
= ci_icon_for_status(status)
%span
CI build
diff --git a/app/views/shared/empty_states/_issues.html.haml b/app/views/shared/empty_states/_issues.html.haml
index 07d4927b6c9..e2033654018 100644
--- a/app/views/shared/empty_states/_issues.html.haml
+++ b/app/views/shared/empty_states/_issues.html.haml
@@ -10,10 +10,10 @@
.text-content
- if has_button && current_user
%h4
- The Issue Tracker is a good place to add things that need to be improved or solved in a project!
+ The Issue Tracker is the place to add things that need to be improved or solved in a project
%p
- An issue can be a bug, a todo or a feature request that needs to be discussed in a project.
- Besides, issues are searchable and filterable.
+ Issues can be bugs, tasks or ideas to be discussed.
+ Also, issues are searchable and filterable.
- if project_select_button
= render 'shared/new_project_item_select', path: 'issues/new', label: 'New issue'
- else
diff --git a/app/views/shared/issuable/_milestone_dropdown.html.haml b/app/views/shared/issuable/_milestone_dropdown.html.haml
index 40fe53e6a8d..415361f8fbf 100644
--- a/app/views/shared/issuable/_milestone_dropdown.html.haml
+++ b/app/views/shared/issuable/_milestone_dropdown.html.haml
@@ -3,7 +3,7 @@
- show_menu_above = show_menu_above || false
- selected_text = selected.try(:title) || params[:milestone_title]
- dropdown_title = local_assigns.fetch(:dropdown_title, "Filter by milestone")
-- if selected.present?
+- if selected.present? || params[:milestone_title].present?
= hidden_field_tag(name, name == :milestone_title ? selected_text : selected.id)
= dropdown_tag(milestone_dropdown_label(selected_text), options: { title: dropdown_title, toggle_class: "js-milestone-select js-filter-submit #{extra_class}", filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone",
placeholder: "Search milestones", footer_content: project.present?, data: { show_no: true, show_menu_above: show_menu_above, show_any: show_any, show_upcoming: show_upcoming, field_name: name, selected: selected.try(:title), project_id: project.try(:id), milestones: milestones_filter_dropdown_path, default_label: "Milestone" } }) do
diff --git a/changelogs/unreleased/25895-fix-headers-in-ci-api-helpers.yml b/changelogs/unreleased/25895-fix-headers-in-ci-api-helpers.yml
new file mode 100644
index 00000000000..b9a8e17c64a
--- /dev/null
+++ b/changelogs/unreleased/25895-fix-headers-in-ci-api-helpers.yml
@@ -0,0 +1,4 @@
+---
+title: Ensure nil User-Agent doesn't break the CI API
+merge_request:
+author:
diff --git a/changelogs/unreleased/25898-ci-icon-color-mr.yml b/changelogs/unreleased/25898-ci-icon-color-mr.yml
new file mode 100644
index 00000000000..dd0f93e176f
--- /dev/null
+++ b/changelogs/unreleased/25898-ci-icon-color-mr.yml
@@ -0,0 +1,4 @@
+---
+title: Adds CSS class to status icon on MR widget to prevent non-colored icon
+merge_request: 8219
+author:
diff --git a/changelogs/unreleased/8038-authentiq-id-oauth-support.yml b/changelogs/unreleased/8038-authentiq-id-oauth-support.yml
new file mode 100644
index 00000000000..36f8ac9c840
--- /dev/null
+++ b/changelogs/unreleased/8038-authentiq-id-oauth-support.yml
@@ -0,0 +1,4 @@
+---
+title: Add Authentiq as Oauth provider
+merge_request: 8038
+author: Alexandros Keramidas
diff --git a/changelogs/unreleased/badge-color-on-white-bg.yml b/changelogs/unreleased/badge-color-on-white-bg.yml
new file mode 100644
index 00000000000..680d7ff11f0
--- /dev/null
+++ b/changelogs/unreleased/badge-color-on-white-bg.yml
@@ -0,0 +1,4 @@
+---
+title: Added lighter count badge background-color for on white backgrounds
+merge_request: 7873
+author:
diff --git a/changelogs/unreleased/dz-rename-invalid-groups.yml b/changelogs/unreleased/dz-rename-invalid-groups.yml
new file mode 100644
index 00000000000..90af42da01c
--- /dev/null
+++ b/changelogs/unreleased/dz-rename-invalid-groups.yml
@@ -0,0 +1,4 @@
+---
+title: Rename groups with .git in the end of the path
+merge_request: 8199
+author:
diff --git a/changelogs/unreleased/dz-whitelist-dashboard-project-path.yml b/changelogs/unreleased/dz-whitelist-dashboard-project-path.yml
new file mode 100644
index 00000000000..2787a5c57df
--- /dev/null
+++ b/changelogs/unreleased/dz-whitelist-dashboard-project-path.yml
@@ -0,0 +1,4 @@
+---
+title: Allow projects with 'dashboard' as path
+merge_request:
+author:
diff --git a/changelogs/unreleased/fix-copy-issues-empty-state.yml b/changelogs/unreleased/fix-copy-issues-empty-state.yml
new file mode 100644
index 00000000000..a87b7612217
--- /dev/null
+++ b/changelogs/unreleased/fix-copy-issues-empty-state.yml
@@ -0,0 +1,4 @@
+---
+title: Improve copy in Issue Tracker empty state
+merge_request: 8202
+author:
diff --git a/changelogs/unreleased/fix-group-path-rename-error.yml b/changelogs/unreleased/fix-group-path-rename-error.yml
new file mode 100644
index 00000000000..e3d97ae3987
--- /dev/null
+++ b/changelogs/unreleased/fix-group-path-rename-error.yml
@@ -0,0 +1,4 @@
+---
+title: Fix 500 error renaming group
+merge_request:
+author:
diff --git a/changelogs/unreleased/fix-import-labels-error.yml b/changelogs/unreleased/fix-import-labels-error.yml
new file mode 100644
index 00000000000..86cae3a49ff
--- /dev/null
+++ b/changelogs/unreleased/fix-import-labels-error.yml
@@ -0,0 +1,4 @@
+---
+title: Fix project import label priorities error
+merge_request:
+author:
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index b8b41a0d86c..2d1d48bf9da 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -368,6 +368,16 @@ production: &base
# login_url: '/cas/login',
# service_validate_url: '/cas/p3/serviceValidate',
# logout_url: '/cas/logout'} }
+ # - { name: 'authentiq',
+ # # for client credentials (client ID and secret), go to https://www.authentiq.com/
+ # app_id: 'YOUR_CLIENT_ID',
+ # app_secret: 'YOUR_CLIENT_SECRET',
+ # args: {
+ # scope: 'aq:name email~rs address aq:push'
+ # # redirect_uri parameter is optional except when 'gitlab.host' in this file is set to 'localhost'
+ # # redirect_uri: 'YOUR_REDIRECT_URI'
+ # }
+ # }
# - { name: 'github',
# app_id: 'YOUR_APP_ID',
# app_secret: 'YOUR_APP_SECRET',
diff --git a/db/migrate/20161220141214_remove_dot_git_from_group_names.rb b/db/migrate/20161220141214_remove_dot_git_from_group_names.rb
new file mode 100644
index 00000000000..bd0e4b2cc07
--- /dev/null
+++ b/db/migrate/20161220141214_remove_dot_git_from_group_names.rb
@@ -0,0 +1,82 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class RemoveDotGitFromGroupNames < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+ include Gitlab::ShellAdapter
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ def up
+ invalid_groups.each do |group|
+ path_was = group['path']
+ path_was_wildcard = quote_string("#{path_was}/%")
+ path = quote_string(rename_path(path_was))
+
+ move_namespace(group['id'], path_was, path)
+
+ execute "UPDATE routes SET path = '#{path}' WHERE source_type = 'Namespace' AND source_id = #{group['id']}"
+ execute "UPDATE namespaces SET path = '#{path}' WHERE id = #{group['id']}"
+
+ select_all("SELECT id, path FROM routes WHERE path LIKE '#{path_was_wildcard}'").each do |route|
+ new_path = "#{path}/#{route['path'].split('/').last}"
+ execute "UPDATE routes SET path = '#{new_path}' WHERE id = #{route['id']}"
+ end
+ end
+ end
+
+ def down
+ # nothing to do here
+ end
+
+ private
+
+ def invalid_groups
+ select_all("SELECT id, path FROM namespaces WHERE type = 'Group' AND path LIKE '%.git'")
+ end
+
+ def route_exists?(path)
+ select_all("SELECT id, path FROM routes WHERE path = '#{quote_string(path)}'").present?
+ end
+
+ # Accepts invalid path like test.git and returns test_git or
+ # test_git1 if test_git already taken
+ def rename_path(path)
+ # To stay closer with original name and reduce risk of duplicates
+ # we rename suffix instead of removing it
+ path = path.sub(/\.git\z/, '_git')
+
+ counter = 0
+ base = path
+
+ while route_exists?(path)
+ counter += 1
+ path = "#{base}#{counter}"
+ end
+
+ path
+ end
+
+ def move_namespace(group_id, path_was, path)
+ repository_storage_paths = select_all("SELECT distinct(repository_storage) FROM projects WHERE namespace_id = #{group_id}").map do |row|
+ Gitlab.config.repositories.storages[row['repository_storage']]
+ end
+
+ # Move the namespace directory in all storages paths used by member projects
+ repository_storage_paths.each do |repository_storage_path|
+ # Ensure old directory exists before moving it
+ gitlab_shell.add_namespace(repository_storage_path, path_was)
+
+ unless gitlab_shell.mv_namespace(repository_storage_path, path_was, path)
+ Rails.logger.error "Exception moving path #{repository_storage_path} from #{path_was} to #{path}"
+
+ # if we cannot move namespace directory we should rollback
+ # db changes in order to prevent out of sync between db and fs
+ raise Exception.new('namespace directory cannot be moved')
+ end
+ end
+
+ Gitlab::UploadsTransfer.new.rename_namespace(path_was, path)
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 14801b581e6..13a847827cc 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20161213172958) do
+ActiveRecord::Schema.define(version: 20161220141214) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -854,7 +854,7 @@ ActiveRecord::Schema.define(version: 20161213172958) do
t.datetime "expires_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
- t.string "scopes", default: "--- []\n", null: false
+ t.string "scopes", default: "--- []\n", null: false
end
add_index "personal_access_tokens", ["token"], name: "index_personal_access_tokens_on_token", unique: true, using: :btree
diff --git a/doc/administration/auth/README.md b/doc/administration/auth/README.md
index 2fc5d0355b5..13bd501e397 100644
--- a/doc/administration/auth/README.md
+++ b/doc/administration/auth/README.md
@@ -6,7 +6,7 @@ providers.
- [LDAP](ldap.md) Includes Active Directory, Apple Open Directory, Open LDAP,
and 389 Server
- [OmniAuth](../../integration/omniauth.md) Sign in via Twitter, GitHub, GitLab.com, Google,
- Bitbucket, Facebook, Shibboleth, Crowd and Azure
+ Bitbucket, Facebook, Shibboleth, Crowd, Azure and Authentiq ID
- [CAS](../../integration/cas.md) Configure GitLab to sign in using CAS
- [SAML](../../integration/saml.md) Configure GitLab as a SAML 2.0 Service Provider
- [Okta](okta.md) Configure GitLab to sign in using Okta
diff --git a/doc/administration/auth/authentiq.md b/doc/administration/auth/authentiq.md
new file mode 100644
index 00000000000..3f39539da95
--- /dev/null
+++ b/doc/administration/auth/authentiq.md
@@ -0,0 +1,69 @@
+# Authentiq OmniAuth Provider
+
+To enable the Authentiq OmniAuth provider for passwordless authentication you must register an application with Authentiq.
+
+Authentiq will generate a Client ID and the accompanying Client Secret for you to use.
+
+1. Get your Client credentials (Client ID and Client Secret) at [Authentiq](https://www.authentiq.com/register).
+
+2. On your GitLab server, open the configuration file:
+
+ For omnibus installation
+ ```sh
+ sudo editor /etc/gitlab/gitlab.rb
+ ```
+
+ For installations from source:
+
+ ```sh
+ sudo -u git -H editor /home/git/gitlab/config/gitlab.yml
+ ```
+
+3. See [Initial OmniAuth Configuration](../../integration/omniauth.md#initial-omniauth-configuration) for initial settings to enable single sign-on and add Authentiq as an OAuth provider.
+
+4. Add the provider configuration for Authentiq:
+
+ For Omnibus packages:
+
+ ```ruby
+ gitlab_rails['omniauth_providers'] = [
+ {
+ "name" => "authentiq",
+ "app_id" => "YOUR_CLIENT_ID",
+ "app_secret" => "YOUR_CLIENT_SECRET",
+ "args" => {
+ scope: 'aq:name email~rs aq:push'
+ }
+ }
+ ]
+ ```
+
+ For installations from source:
+
+ ```yaml
+ - { name: 'authentiq',
+ app_id: 'YOUR_CLIENT_ID',
+ app_secret: 'YOUR_CLIENT_SECRET',
+ args: {
+ scope: 'aq:name email~rs aq:push'
+ }
+ }
+ ```
+
+
+5. The `scope` is set to request the user's name, email (required and signed), and permission to send push notifications to sign in on subsequent visits.
+See [OmniAuth Authentiq strategy](https://github.com/AuthentiqID/omniauth-authentiq#scopes-and-redirect-uri-configuration) for more information on scopes and modifiers.
+
+6. Change 'YOUR_CLIENT_ID' and 'YOUR_CLIENT_SECRET' to the Client credentials you received in step 1.
+
+7. Save the configuration file.
+
+8. [Reconfigure](../restart_gitlab.md#omnibus-gitlab-reconfigure) or [restart GitLab](../restart_gitlab.md#installations-from-source)
+ for the changes to take effect if you installed GitLab via Omnibus or from source respectively.
+
+On the sign in page there should now be an Authentiq icon below the regular sign in form. Click the icon to begin the authentication process.
+
+- If the user has the Authentiq ID app installed in their iOS or Android device, they can scan the QR code, decide what personal details to share and sign in to your GitLab installation.
+- If not they will be prompted to download the app and then follow the procedure above.
+
+If everything goes right, the user will be returned to GitLab and will be signed in. \ No newline at end of file
diff --git a/doc/integration/README.md b/doc/integration/README.md
index f8ffa6dcb7f..ed843c0bfa9 100644
--- a/doc/integration/README.md
+++ b/doc/integration/README.md
@@ -8,7 +8,7 @@ See the documentation below for details on how to configure these services.
- [JIRA](../project_services/jira.md) Integrate with the JIRA issue tracker
- [External issue tracker](external-issue-tracker.md) Redmine, JIRA, etc.
- [LDAP](ldap.md) Set up sign in via LDAP
-- [OmniAuth](omniauth.md) Sign in via Twitter, GitHub, GitLab.com, Google, Bitbucket, Facebook, Shibboleth, SAML, Crowd and Azure
+- [OmniAuth](omniauth.md) Sign in via Twitter, GitHub, GitLab.com, Google, Bitbucket, Facebook, Shibboleth, SAML, Crowd, Azure and Authentiq ID
- [SAML](saml.md) Configure GitLab as a SAML 2.0 Service Provider
- [CAS](cas.md) Configure GitLab to sign in using CAS
- [OAuth2 provider](oauth_provider.md) OAuth2 application creation
diff --git a/doc/integration/omniauth.md b/doc/integration/omniauth.md
index 8a55fce96fe..4c933cef9b7 100644
--- a/doc/integration/omniauth.md
+++ b/doc/integration/omniauth.md
@@ -30,6 +30,7 @@ contains some settings that are common for all providers.
- [Crowd](crowd.md)
- [Azure](azure.md)
- [Auth0](auth0.md)
+- [Authentiq](../administration/auth/authentiq.md)
## Initial OmniAuth Configuration
diff --git a/lib/ci/api/helpers.rb b/lib/ci/api/helpers.rb
index 31fbd1da108..5ff25a3a9b2 100644
--- a/lib/ci/api/helpers.rb
+++ b/lib/ci/api/helpers.rb
@@ -60,7 +60,7 @@ module Ci
end
def build_not_found!
- if headers['User-Agent'].match(/gitlab-ci-multi-runner \d+\.\d+\.\d+(~beta\.\d+\.g[0-9a-f]+)? /)
+ if headers['User-Agent'].to_s.match(/gitlab-ci-multi-runner \d+\.\d+\.\d+(~beta\.\d+\.g[0-9a-f]+)? /)
no_content!
else
not_found!
diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb
index 65b229ca8ff..7a649f28340 100644
--- a/lib/gitlab/import_export/relation_factory.rb
+++ b/lib/gitlab/import_export/relation_factory.rb
@@ -22,7 +22,7 @@ module Gitlab
IMPORTED_OBJECT_MAX_RETRIES = 5.freeze
- EXISTING_OBJECT_CHECK = %i[milestone milestones label labels project_label project_labels project_label group_label].freeze
+ EXISTING_OBJECT_CHECK = %i[milestone milestones label labels project_label project_labels group_label group_labels].freeze
def self.create(*args)
new(*args).create
@@ -189,7 +189,7 @@ module Gitlab
# Otherwise always create the record, skipping the extra SELECT clause.
@existing_or_new_object ||= begin
if EXISTING_OBJECT_CHECK.include?(@relation_name)
- attribute_hash = attribute_hash_for(['events', 'priorities'])
+ attribute_hash = attribute_hash_for(['events'])
existing_object.assign_attributes(attribute_hash) if attribute_hash.any?
@@ -210,9 +210,8 @@ module Gitlab
def existing_object
@existing_object ||=
begin
- finder_attributes = @relation_name == :group_label ? %w[title group_id] : %w[title project_id]
- finder_hash = parsed_relation_hash.slice(*finder_attributes)
- existing_object = relation_class.find_or_create_by(finder_hash)
+ existing_object = find_or_create_object!
+
# Done in two steps, as MySQL behaves differently than PostgreSQL using
# the +find_or_create_by+ method and does not return the ID the second time.
existing_object.update!(parsed_relation_hash)
@@ -224,6 +223,25 @@ module Gitlab
@relation_name == :services && parsed_relation_hash['type'] &&
!Object.const_defined?(parsed_relation_hash['type'])
end
+
+ def find_or_create_object!
+ finder_attributes = @relation_name == :group_label ? %w[title group_id] : %w[title project_id]
+ finder_hash = parsed_relation_hash.slice(*finder_attributes)
+
+ if label?
+ label = relation_class.find_or_initialize_by(finder_hash)
+ parsed_relation_hash.delete('priorities') if label.persisted?
+
+ label.save!
+ label
+ else
+ relation_class.find_or_create_by(finder_hash)
+ end
+ end
+
+ def label?
+ @relation_name.to_s.include?('label')
+ end
end
end
end
diff --git a/lib/gitlab/middleware/multipart.rb b/lib/gitlab/middleware/multipart.rb
index 65713e73a59..dd99f9bb7d7 100644
--- a/lib/gitlab/middleware/multipart.rb
+++ b/lib/gitlab/middleware/multipart.rb
@@ -42,7 +42,7 @@ module Gitlab
key, value = parsed_field.first
if value.nil?
- value = File.open(tmp_path)
+ value = open_file(tmp_path)
@open_files << value
else
value = decorate_params_value(value, @request.params[key], tmp_path)
@@ -68,7 +68,7 @@ module Gitlab
case path_value
when nil
- value_hash[path_key] = File.open(tmp_path)
+ value_hash[path_key] = open_file(tmp_path)
@open_files << value_hash[path_key]
value_hash
when Hash
@@ -78,6 +78,10 @@ module Gitlab
raise "unexpected path value: #{path_value.inspect}"
end
end
+
+ def open_file(path)
+ ::UploadedFile.new(path, File.basename(path), 'application/octet-stream')
+ end
end
def initialize(app)
diff --git a/lib/gitlab/update_path_error.rb b/lib/gitlab/update_path_error.rb
new file mode 100644
index 00000000000..ce14cc887d0
--- /dev/null
+++ b/lib/gitlab/update_path_error.rb
@@ -0,0 +1,3 @@
+module Gitlab
+ class UpdatePathError < StandardError; end
+end
diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb
index a763e2c5ba8..98dfb3e5216 100644
--- a/spec/controllers/groups_controller_spec.rb
+++ b/spec/controllers/groups_controller_spec.rb
@@ -105,4 +105,25 @@ describe GroupsController do
end
end
end
+
+ describe 'PUT update' do
+ before do
+ sign_in(user)
+ end
+
+ it 'updates the path succesfully' do
+ post :update, id: group.to_param, group: { path: 'new_path' }
+
+ expect(response).to have_http_status(302)
+ expect(controller).to set_flash[:notice]
+ end
+
+ it 'does not update the path on error' do
+ allow_any_instance_of(Group).to receive(:move_dir).and_raise(Gitlab::UpdatePathError)
+ post :update, id: group.to_param, group: { path: 'new_path' }
+
+ expect(assigns(:group).errors).not_to be_empty
+ expect(assigns(:group).path).not_to eq('new_path')
+ end
+ end
end
diff --git a/spec/javascripts/fixtures/issuable_filter.html.haml b/spec/javascripts/fixtures/issuable_filter.html.haml
new file mode 100644
index 00000000000..ae745b292e6
--- /dev/null
+++ b/spec/javascripts/fixtures/issuable_filter.html.haml
@@ -0,0 +1,8 @@
+%form.js-filter-form{action: '/user/project/issues?scope=all&state=closed'}
+ %input{id: 'utf8', name: 'utf8', value: '✓'}
+ %input{id: 'check_all_issues', name: 'check_all_issues'}
+ %input{id: 'search', name: 'search'}
+ %input{id: 'author_id', name: 'author_id'}
+ %input{id: 'assignee_id', name: 'assignee_id'}
+ %input{id: 'milestone_title', name: 'milestone_title'}
+ %input{id: 'label_name', name: 'label_name'}
diff --git a/spec/javascripts/issuable_spec.js.es6 b/spec/javascripts/issuable_spec.js.es6
new file mode 100644
index 00000000000..d61601ee4fb
--- /dev/null
+++ b/spec/javascripts/issuable_spec.js.es6
@@ -0,0 +1,81 @@
+/* global Issuable */
+/* global Turbolinks */
+
+//= require issuable
+//= require turbolinks
+
+(() => {
+ const BASE_URL = '/user/project/issues?scope=all&state=closed';
+ const DEFAULT_PARAMS = '&utf8=%E2%9C%93';
+
+ function updateForm(formValues, form) {
+ $.each(formValues, (id, value) => {
+ $(`#${id}`, form).val(value);
+ });
+ }
+
+ function resetForm(form) {
+ $('input[name!="utf8"]', form).each((index, input) => {
+ input.setAttribute('value', '');
+ });
+ }
+
+ describe('Issuable', () => {
+ fixture.preload('issuable_filter');
+
+ beforeEach(() => {
+ fixture.load('issuable_filter');
+ Issuable.init();
+ });
+
+ it('should be defined', () => {
+ expect(window.Issuable).toBeDefined();
+ });
+
+ describe('filtering', () => {
+ let $filtersForm;
+
+ beforeEach(() => {
+ $filtersForm = $('.js-filter-form');
+ fixture.load('issuable_filter');
+ resetForm($filtersForm);
+ });
+
+ it('should contain only the default parameters', () => {
+ spyOn(Turbolinks, 'visit');
+
+ Issuable.filterResults($filtersForm);
+
+ expect(Turbolinks.visit).toHaveBeenCalledWith(BASE_URL + DEFAULT_PARAMS);
+ });
+
+ it('should filter for the phrase "broken"', () => {
+ spyOn(Turbolinks, 'visit');
+
+ updateForm({ search: 'broken' }, $filtersForm);
+ Issuable.filterResults($filtersForm);
+ const params = `${DEFAULT_PARAMS}&search=broken`;
+
+ expect(Turbolinks.visit).toHaveBeenCalledWith(BASE_URL + params);
+ });
+
+ it('should keep query parameters after modifying filter', () => {
+ spyOn(Turbolinks, 'visit');
+
+ // initial filter
+ updateForm({ milestone_title: 'v1.0' }, $filtersForm);
+
+ Issuable.filterResults($filtersForm);
+ let params = `${DEFAULT_PARAMS}&milestone_title=v1.0`;
+ expect(Turbolinks.visit).toHaveBeenCalledWith(BASE_URL + params);
+
+ // update filter
+ updateForm({ label_name: 'Frontend' }, $filtersForm);
+
+ Issuable.filterResults($filtersForm);
+ params = `${DEFAULT_PARAMS}&milestone_title=v1.0&label_name=Frontend`;
+ expect(Turbolinks.visit).toHaveBeenCalledWith(BASE_URL + params);
+ });
+ });
+ });
+})();
diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json
index 931d426c87f..2c0750c3377 100644
--- a/spec/lib/gitlab/import_export/project.json
+++ b/spec/lib/gitlab/import_export/project.json
@@ -15,6 +15,28 @@
"type": "ProjectLabel",
"priorities": [
]
+ },
+ {
+ "id": 3,
+ "title": "test3",
+ "color": "#428bca",
+ "group_id": 8,
+ "created_at": "2016-07-22T08:55:44.161Z",
+ "updated_at": "2016-07-22T08:55:44.161Z",
+ "template": false,
+ "description": "",
+ "project_id": null,
+ "type": "GroupLabel",
+ "priorities": [
+ {
+ "id": 1,
+ "project_id": 5,
+ "label_id": 1,
+ "priority": 1,
+ "created_at": "2016-10-18T09:35:43.338Z",
+ "updated_at": "2016-10-18T09:35:43.338Z"
+ }
+ ]
}
],
"issues": [
diff --git a/spec/lib/gitlab/middleware/multipart_spec.rb b/spec/lib/gitlab/middleware/multipart_spec.rb
index ab1ab22795c..8d925460f01 100644
--- a/spec/lib/gitlab/middleware/multipart_spec.rb
+++ b/spec/lib/gitlab/middleware/multipart_spec.rb
@@ -12,7 +12,7 @@ describe Gitlab::Middleware::Multipart do
expect(app).to receive(:call) do |env|
file = Rack::Request.new(env).params['file']
- expect(file).to be_a(File)
+ expect(file).to be_a(::UploadedFile)
expect(file.path).to eq(tempfile.path)
end
@@ -39,7 +39,7 @@ describe Gitlab::Middleware::Multipart do
expect(app).to receive(:call) do |env|
file = Rack::Request.new(env).params['user']['avatar']
- expect(file).to be_a(File)
+ expect(file).to be_a(::UploadedFile)
expect(file.path).to eq(tempfile.path)
end
@@ -54,7 +54,7 @@ describe Gitlab::Middleware::Multipart do
expect(app).to receive(:call) do |env|
file = Rack::Request.new(env).params['project']['milestone']['themesong']
- expect(file).to be_a(File)
+ expect(file).to be_a(::UploadedFile)
expect(file.path).to eq(tempfile.path)
end
diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb
index 79f12ace999..3b5dc98e4d5 100644
--- a/spec/requests/ci/api/builds_spec.rb
+++ b/spec/requests/ci/api/builds_spec.rb
@@ -37,6 +37,11 @@ describe Ci::API::Builds do
let(:user_agent) { 'Go-http-client/1.1' }
it { expect(response).to have_http_status(404) }
end
+
+ context "when runner doesn't have a User-Agent" do
+ let(:user_agent) { nil }
+ it { expect(response).to have_http_status(404) }
+ end
end
context 'when there is a pending build' do
diff --git a/spec/services/groups/update_service_spec.rb b/spec/services/groups/update_service_spec.rb
index 9c2331144a0..531180e48a1 100644
--- a/spec/services/groups/update_service_spec.rb
+++ b/spec/services/groups/update_service_spec.rb
@@ -1,15 +1,15 @@
require 'spec_helper'
describe Groups::UpdateService, services: true do
- let!(:user) { create(:user) }
- let!(:private_group) { create(:group, :private) }
- let!(:internal_group) { create(:group, :internal) }
- let!(:public_group) { create(:group, :public) }
+ let!(:user) { create(:user) }
+ let!(:private_group) { create(:group, :private) }
+ let!(:internal_group) { create(:group, :internal) }
+ let!(:public_group) { create(:group, :public) }
describe "#execute" do
context "project visibility_level validation" do
context "public group with public projects" do
- let!(:service) { described_class.new(public_group, user, visibility_level: Gitlab::VisibilityLevel::INTERNAL ) }
+ let!(:service) { described_class.new(public_group, user, visibility_level: Gitlab::VisibilityLevel::INTERNAL) }
before do
public_group.add_user(user, Gitlab::Access::MASTER)
@@ -23,7 +23,7 @@ describe Groups::UpdateService, services: true do
end
context "internal group with internal project" do
- let!(:service) { described_class.new(internal_group, user, visibility_level: Gitlab::VisibilityLevel::PRIVATE ) }
+ let!(:service) { described_class.new(internal_group, user, visibility_level: Gitlab::VisibilityLevel::PRIVATE) }
before do
internal_group.add_user(user, Gitlab::Access::MASTER)
@@ -39,7 +39,7 @@ describe Groups::UpdateService, services: true do
end
context "unauthorized visibility_level validation" do
- let!(:service) { described_class.new(internal_group, user, visibility_level: 99 ) }
+ let!(:service) { described_class.new(internal_group, user, visibility_level: 99) }
before do
internal_group.add_user(user, Gitlab::Access::MASTER)
end
@@ -49,4 +49,41 @@ describe Groups::UpdateService, services: true do
expect(internal_group.errors.count).to eq(1)
end
end
+
+ context 'rename group' do
+ let!(:service) { described_class.new(internal_group, user, path: 'new_path') }
+
+ before do
+ internal_group.add_user(user, Gitlab::Access::MASTER)
+ create(:project, :internal, group: internal_group)
+ end
+
+ it 'returns true' do
+ expect(service.execute).to eq(true)
+ end
+
+ context 'error moving group' do
+ before do
+ allow(internal_group).to receive(:move_dir).and_raise(Gitlab::UpdatePathError)
+ end
+
+ it 'does not raise an error' do
+ expect { service.execute }.not_to raise_error
+ end
+
+ it 'returns false' do
+ expect(service.execute).to eq(false)
+ end
+
+ it 'has the right error' do
+ service.execute
+
+ expect(internal_group.errors.full_messages.first).to eq('Gitlab::UpdatePathError')
+ end
+
+ it "hasn't changed the path" do
+ expect { service.execute}.not_to change { internal_group.reload.path}
+ end
+ end
+ end
end