diff options
464 files changed, 13108 insertions, 496 deletions
diff --git a/.gitattributes b/.gitattributes index 7e800609e6c..c34a24ca51a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,2 @@ -CHANGELOG merge=union
\ No newline at end of file +CHANGELOG merge=union +CHANGELOG-EE merge=union diff --git a/.license_encryption_key.pub b/.license_encryption_key.pub new file mode 100644 index 00000000000..68f241b9741 --- /dev/null +++ b/.license_encryption_key.pub @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0Hxv3MkkZbMrKtIs6np9 +ccP4OwGBkNhIvhPjcQP48hbbascv5RqsOquQGrYSD2ZrE/kbkRdkIcoHEeTZLif+ +bDKFZFI7o5x0H92o9/GSvxHJhQ8mkmvwxD7lssGShwZEm8WG+U7BZqUV/gGmCDqe +9W8H8Fq2B0ck8IXjbQ4Zz+JlyV/NHZTZcs69plFiLKh4N6GYVftOVwSomh0bbypP +OB9WnLC7RC9a2LRrhtf8sqa2rRFmtyMMfgFFzLMzS+w+1K4+QLnWP1gKQVzaFnzk +pnwKPrqbGFYbRztIVEWbs8jPYlLkGb8ME4C84YVtQgbQcbyisU/VW3wUGkhT+J0k +xwIDAQAB +-----END PUBLIC KEY----- diff --git a/CHANGELOG b/CHANGELOG index 45ef22e7e86..6528ca8c327 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,10 @@ Please view this file on the master branch, on stable branches it's out of date. +v 8.1.0 + - Fix: LDAP group links API is not working as expected. + +v 8.0.1 +v 8.1.0 (unreleased) v 8.2.0 (unreleased) - Remove CSS property preventing hard tabs from rendering in Chromium 45 (Stan Hu) - Fix Drone CI service template not saving properly (Stan Hu) @@ -256,6 +261,44 @@ v 8.0.0 - Redirect from incorrectly cased group or project path to correct one (Francesco Levorato) - Removed API calls from CE to CI +v 7.14.0 + - Allow displaying of archived projects in the admin interface (Artem Sidorenko) + - Allow configuration of import sources for new projects (Artem Sidorenko) + - Search for comments should be case insensetive + - Create cross-reference for closing references on commits pushed to non-default branches (Maël Valais) + - Ability to search milestones + - Gracefully handle SMTP user input errors (e.g. incorrect email addresses) to prevent Sidekiq retries (Stan Hu) + - Move dashboard activity to separate page (for your projects and starred projects) + - Improve performance of git blame + - Limit content width to 1200px for most of pages to improve readability on big screens + - Fix 500 error when submit project snippet without body + - Improve search page usability + - Bring more UI consistency in way how projects, snippets and groups lists are rendered + - Make all profiles and group public + - Fixed login failure when extern_uid changes (Joel Koglin) + - Don't notify users without access to the project when they are (accidentally) mentioned in a note. + - Retrieving oauth token with LDAP credentials + - Load Application settings from running database unless env var USE_DB=false + - Added Drone CI integration (Kirill Zaitsev) + - Allow developers to retry builds + - Hide advanced project options for non-admin users + - Fail builds if no .gitlab-ci.yml is found + - Refactored service API and added automatically service docs generator (Kirill Zaitsev) + - Added web_url key project hook_attrs (Kirill Zaitsev) + - Add ability to get user information by ID of an SSH key via the API + - Fix bug which IE cannot show image at markdown when the image is raw file of gitlab + - Add support for Crowd + - Global Labels that are available to all projects + - Fix highlighting of deleted lines in diffs. + - Project notification level can be set on the project page itself + - Added service API endpoint to retrieve service parameters (Petheő Bence) + - Add FogBugz project import (Jared Szechy) + - Sort users autocomplete lists by user (Allister Antosik) + - Webhook for issue now contains repository field (Jungkook Park) + - Add ability to add custom text to the help page (Jeroen van Baarsen) + - Add pg_schema to backup config + - Removed API calls from CE to CI + v 7.14.3 - No changes @@ -296,6 +339,7 @@ v 7.14.0 - Fix URL used for refreshing notes if relative_url is present (Bartłomiej Święcki) - Fix commit data retrieval when branch name has single quotes (Stan Hu) - Check that project was actually created rather than just validated in import:repos task (Stan Hu) + - Fix inability to save "Reset approvals on push" setting (Sandish Chen) - Fix full screen mode for snippet comments (Daniel Gerhardt) - Fix 404 error in files view after deleting the last file in a repository (Stan Hu) - Fix the "Reload with full diff" URL button (Stan Hu) @@ -310,6 +354,11 @@ v 7.14.0 - Add support for destroying project milestones (Stan Hu) - Allow custom backup archive permissions - Add project star and fork count, group avatar URL and user/group web URL attributes to API + - Fix bug causing Bitbucket importer to crash when OAuth application had been removed. + - Add fetch command to the MR page. + - Fix bug causing "Remove source-branch" option not to work for merge requests from the same project. + - Fix approvals for forks. Now you can change approvals settings on the new merge request form + - Suggested approvers are shown on the new merge request form - Show who last edited a comment if it wasn't the original author - Send notification to all participants when MR is merged. - Add ability to manage user email addresses via the API. @@ -647,6 +696,9 @@ v 7.10.0 - Restrict permissions on backup files - Improve oauth accounts UI in profile page - Add ability to unlink connected accounts + - Add changes to Deploy Keys to the Audit Logs + +v 7.9.0 (unreleased) - Replace commits calendar with faster contribution calendar that includes issues and merge requests - Add inifinite scroll to user page activity - Don't include system notes in issue/MR comment count. @@ -741,6 +793,7 @@ v 7.9.0 - Fix hidden diff comments in merge request discussion view - Allow user confirmation to be skipped for new users via API - Add a service to send updates to an Irker gateway (Romain Coltel) + - Ignore case of LDAP user DN when checking group membership. - Add brakeman (security scanner for Ruby on Rails) - Slack username and channel options - Add grouped milestones from all projects to dashboard. @@ -1216,6 +1269,9 @@ v 6.9.0 - Labels for merge requests (Drew Blessing) - Threaded emails by setting a Message-ID (Philip Blatter) +v 6.8.1 + - Bump required gitlab-shell version to 1.9.3 + v 6.8.0 - Ability to at mention users that are participating in issue and merge req. discussion - Enabled GZip Compression for assets in example Nginx, make sure that Nginx is compiled with --with-http_gzip_static_module flag (this is default in Ubuntu) diff --git a/CHANGELOG-EE b/CHANGELOG-EE new file mode 100644 index 00000000000..57ff9af6abe --- /dev/null +++ b/CHANGELOG-EE @@ -0,0 +1,234 @@ +v 8.2.0 + - Invalidate stored jira password if the endpoint URL is changed + - Fix: Page is not reloaded periodically to check if rebase is finished + - When someone as marked as a required approver for a merge request, an email should be sent + - Allow configuring the Jira API path (Alex Lossent) + - Fix "Rebase onto master" + - Ensure a comment is properly recorded in JIRA when a merge request is accepted + - Allow groups to appear in the `Share with group` share if the group owner allows it + +v 8.1.0 (unreleased) + - added an issues template (Hannes Rosenögger) + +v 8.1.0 + - Add documentation for "Share project with group" API call + - Abiliy to disable 'Share with Group' feature (via UI and API) + +v 8.0.1 + - Correct gem dependency versions + - Re-add the "Help Text" feature that was inadvertently removed + +v 8.0.0 + - Fix navigation issue when viewing Group Settings pages + - Guests and Reporters can approve merge request as well + - Add fast-forward merge option in project settings + - Separate rebase & fast-forward merge features + +v 7.14.3 + - No changes + +v 7.14.2 + - Fix the rebase before merge feature + +v 7.14.1 + - Fix sign in form when just Kerberos is enabled + +v 7.14.0 + - Disable adding, updating and removing members from a group that is synced with LDAP + - Don't send "Added to group" notifications when group is LDAP synched + - Fix importing projects from GitHub Enterprise Edition. + - Automatic approver suggestions (based on an authority of the code) + - Add support for Jenkins unstable status + - Automatic approver suggestions (based on an authority of the code) + - Support Kerberos ticket-based authentication for Git HTTP access + +v 7.13.3 + - Merge community edition changes for version 7.13.3 + - Improved validation for an approver + - Don't resend admin email to everyone if one delivery fails + - Added migration for removing of invalid approvers + +v 7.13.2 + - Fix group web hook + - Don't resend admin email to everyone if one delivery fails + +v 7.13.1 + - Merge community edition changes for version 7.13.1 + - Fix: "Rebase before merge" doesn't work when source branch is in the same project + +v 7.13 + - Fix git hook validation on initial push to master branch. + - Reset approvals on push + - Fix 500 error when the source project of an MR is deleted + - Ability to define merge request approvers + +v 7.12.2 + - Fixed the alignment of project settings icons + +v 7.12.1 + - No changes specific to EE + +v 7.12.0 + - Fix error when viewing merge request with a commit that includes "Closes #<issue id>". + - Enhance LDAP group synchronization to check also for member attributes that only contain "uid=<username>" + - Enhance LDAP group synchronization to check also for submember attributes + - Prevent LDAP group sync from removing a group's last owner + - Add Git hook to validate maximum file size. + - Project setting: approve merge request by N users before accept + - Support automatic branch jobs created by Jenkins in CI Status + - Add API support for adding and removing LDAP group links + +v 7.11.4 + - no changes specific to EE + +v 7.11.3 + - Fixed an issue with git annex + +v 7.11.2 + - Fixed license upload and verification mechanism + +v 7.11.0 + - Skip git hooks commit validation when pushing new tag. + - Add Two-factor authentication (2FA) for LDAP logins + +v 7.10.1 + - Check if comment exists in Jira before sending a reference + +v 7.10.0 + - Improve UI for next pages: Group LDAP sync, Project git hooks, Project share with groups, Admin -> Appearance settigns + - Default git hooks for new projects + - Fix LDAP group links page by using new group members route. + - Skip email confirmation when updated via LDAP. + +v 7.9.0 + - Strip prefixes and suffixes from synced SSH keys: + `SSHKey:ssh-rsa keykeykey` and `ssh-rsa keykeykey (SSH key)` will now work + - Check if LDAP admin group exists before querying for user membership + - Use one custom header logo for all GitLab themes in appearance settings + - Escape wildcards when searching LDAP by group name. + - Group level Web Hooks + - Don't allow project to be shared with the group it is already in. + +v 7.8.0 + - Improved Jira issue closing integration + - Improved message logging for Jira integration + - Added option of referencing JIRA issues from GitLab + - Update Sidetiq to 0.6.3 + - Added Github Enterprise importer + - When project has MR rebase enabled, MR will have rebase checkbox selected by default + - Minor UI fixes for sidebar navigation + - Manage large binaries with git annex + +v 7.7.0 + - Added custom header logo support (Drew Blessing) + - Fixed preview appearance bug + - Improve performance for selectboxes: project share page, admin email users page + +v 7.6.2 + - Fix failing migrations for MySQL, LDAP + +v 7.6.1 + - No changes + +v 7.6.0 + - Added Audit events related to membership changes for groups and projects + - Added option to attempt a rebase before merging merge request + - Dont show LDAP groups settings if LDAP disabled + - Added member lock for groups to disallow membership additions on project level + - Rebase on merge request. Introduced merge request option to rebase before merging + - Better message for failed pushes because of git hooks + - Kerberos support for web interface and git HTTP + +v 7.5.3 + - Only set up Sidetiq from a Sidekiq server process (fixes Redis::InheritedError) + +v 7.5.0 + - Added an ability to check each author commit's email by regex + - Added an ability to restrict commit authors to existing Gitlab users + - Add an option for automatic daily LDAP user sync + - Added git hook for preventing tag removal to API + - Added git hook for setting commit message regex to API + - Added an ability to block commits with certain filenames by regex expression + - Improved a jenkins parser + +v 7.4.4 + - Fix broken ldap migration + +v 7.4.0 + - Support for multiple LDAP servers + - Skip AD specific LDAP checks + - Do not show ldap users in dropdowns for groups with enabled ldap-sync + - Update the JIRA integration documentation + - Reset the homepage to show the GitLab logo by deleting the custom logo. + +v 7.3.0 + - Add an option to change the LDAP sync time from default 1 hour + - User will receive an email when unsubscribed from admin notifications + - Show group sharing members on /my/project/team + - Improve explanation of the LDAP permission reset + - Fix some navigation issues + - Added support for multiple LDAP groups per Gitlab group + +v 7.2.0 + - Improve Redmine integration + - Better logging for the JIRA issue closing service + - Administrators can now send email to all users through the admin interface + - JIRA issue transition ID is now customizable + - LDAP group settings are now visible in admin group show page and group members page + +v 7.1.0 + - Synchronize LDAP-enabled GitLab administrators with an LDAP group (Marvin Frick, sponsored by SinnerSchrader) + - Synchronize SSH keys with LDAP (Oleg Girko (Jolla) and Marvin Frick (SinnerSchrader)) + - Support Jenkins jobs with multiple modules (Marvin Frick, sponsored by SinnerSchrader) + +v 7.0.0 + - Fix: empty brand images are displayed as empty image_tag on login page (Marvin Frick, sponsored by SinnerSchrader) + +v 6.9.4 + - Fix bug in JIRA Issue closing triggered by commit messages + - Fix JIRA issue reference bug + +v 6.9.3 + - Fix check CI status only when CI service is enabled(Daniel Aquino) + +v 6.9.2 + - Merge community edition changes for version 6.9.2 + +v 6.9.1 + - Merge community edition changes for version 6.9.1 + +v 6.9.0 + - Add support for closing Jira tickets with commits and MR + - Template for Merge Request description can be added in project settings + - Jenkins CI service + - Fix LDAP email upper case bug + +v 6.8.0 + - Customise sign-in page with custom text and logo + +v 6.7.1 + - Handle LDAP errors in Adapter#dn_matches_filter? + +v 6.7.0 + - Improve LDAP sign-in speed by reusing connections + - Add support for Active Directory nested LDAP groups + - Git hooks: Commit message regex + - Git hooks: Deny git tag removal + - Fix group edit in admin area + +v 6.5.0 + - Add reset permissions button to Group#members page + +v 6.4.0 + - Respect existing group permissions during sync with LDAP group (d3844662ec7ce816b0a85c8b40f66ee6c5ae90a1) + +v 6.3.0 + - When looking up a user by DN, use single scope (bc8a875df1609728f1c7674abef46c01168a0d20) + - Try sAMAccountName if omniauth nickname is nil (9b7174c333fa07c44cc53b80459a115ef1856e38) + +v 6.2.0 + - API: expose ldap_cn and ldap_access group attributes + - Use omniauth-ldap nickname attribute as GitLab username + - Improve group sharing UI for installation with many groups + - Fix empty LDAP group raises exception + - Respect LDAP user filter for git access
\ No newline at end of file @@ -28,6 +28,7 @@ gem 'omniauth-saml', '~> 1.4.0' gem 'omniauth-shibboleth', '~> 1.2.0' gem 'omniauth-twitter', '~> 1.2.0' gem 'omniauth_crowd' +gem 'gssapi', group: :kerberos gem 'rack-oauth2', '~> 1.0.5' # Two-factor authentication @@ -46,6 +47,7 @@ gem "gitlab_git", '~> 7.2.20' # GitLab fork with several improvements to original library. For full list of changes # see https://github.com/intridea/omniauth-ldap/compare/master...gitlabhq:master gem 'gitlab_omniauth-ldap', '~> 1.2.1', require: "omniauth-ldap" +gem 'net-ldap' # Git Wiki gem 'gollum-lib', '~> 4.0.2' @@ -204,6 +206,8 @@ gem 'request_store', '~> 1.2.0' gem 'select2-rails', '~> 3.5.9' gem 'virtus', '~> 1.0.1' +gem "gitlab-license", "~> 0.0.2" + group :development do gem "foreman" gem 'brakeman', '3.0.1', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 99cdc2a50ae..0040ebe0c4c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -287,6 +287,7 @@ GEM diff-lcs (~> 1.1) mime-types (~> 1.15) posix-spawn (~> 0.3) + gitlab-license (0.0.3) gitlab_emoji (0.1.1) gemojione (~> 2.0) gitlab_git (7.2.20) @@ -325,6 +326,8 @@ GEM grape-entity (0.4.8) activesupport multi_json (>= 1.3.2) + gssapi (1.2.0) + ffi (>= 1.0.1) haml (4.0.7) tilt haml-rails (0.9.0) @@ -837,6 +840,7 @@ DEPENDENCIES github-linguist (~> 4.7.0) github-markup (~> 1.3.1) gitlab-flowdock-git-hook (~> 1.0.1) + gitlab-license (~> 0.0.2) gitlab_emoji (~> 0.1) gitlab_git (~> 7.2.20) gitlab_meta (= 7.0) @@ -845,6 +849,7 @@ DEPENDENCIES gon (~> 5.0.0) grape (~> 0.13.0) grape-entity (~> 0.4.2) + gssapi haml-rails (~> 0.9.0) hipchat (~> 1.5.0) html-pipeline (~> 1.11.0) @@ -861,6 +866,7 @@ DEPENDENCIES mousetrap-rails (~> 1.4.6) mysql2 (~> 0.3.16) nested_form (~> 0.3.2) + net-ldap newrelic-grape newrelic_rpm (~> 3.9.4.245) nprogress-rails (~> 0.1.6.7) @@ -1,19 +1,30 @@ +The GitLab Enterprise Edition (EE) license (the “EE License”) Copyright (c) 2011-2015 GitLab B.V. -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +This software and associated documentation files (the "Software") may only be +used if you (and any entity that you represent) have agreed to, and are in +compliance with, the GitLab Subscription Terms of Service, available at +https://about.gitlab.com/terms/#subscription (the “EE Terms”), and otherwise +have a valid GitLab Enterprise Edition subscription for the correct number of +user seats. Subject to the foregoing sentence, you are free to modify this +Software and publish patches to the Software. You agree that GitLab and/or its +licensors (as applicable) retain all right, title and interest in and to all +Software incorporated in such modifications and/or patches, and all such Software +may only be used, copied, modified, displayed, distributed, or otherwise exploited +with a valid GitLab Enterprise Edition subscription for the correct number of user +seats. Subject to the foregoing, it is forbidden to copy, merge, publish, distribute, +sublicense, and/or sell the Software. -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. +This EE License applies only to the part of this Software that is not distributed as +part of GitLab Community Edition (CE), and that is not a file that produces client-side +JavaScript, in whole or in part. Any part of this Software distributed as part of GitLab +CE or that is a file that produces client-side JavaScript, in whole or in part, is +copyrighted under the MIT Expat license. The full text of this EE License shall be +included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 52e2d977620..5ccccd47f6f 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,32 @@ The source of GitLab Community Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/) and there are mirrors to make [contributing](CONTRIBUTING.md) as easy as possible. +The source of GitLab Enterprise Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ee). + +#  GitLab + +## Subscriber onboarding information + +Thank you for purchasing a GitLab subscription! + +For standard subscribers, please see **emergency contact info and other useful information** in [the Standard subscribers README](https://gitlab.com/standard/standard-subscriber-information/tree/master#README). + +GitLab Enterprise Edition repository: +https://gitlab.com/gitlab-com/gitlab-ee + +Download GitLab Enterprise Edition: +https://about.gitlab.com/downloads-ee + +Documentation: +http://doc.gitlab.com/ee/ + +To upgrade from CE, just perform a normal upgrade, but use an EE package: +https://about.gitlab.com/update/#ee + +If you need help with your GitLab installation and for any technical questions please contact us at subscribers@gitlab.com + +For all other questions, contact us at sales@gitlab.com + ## Open source software to collaborate on code To see how GitLab looks please see the [features page on our website](https://about.gitlab.com/features/). @@ -90,7 +116,7 @@ For upgrading information please see our [update page](https://about.gitlab.com/ ## Documentation -All documentation can be found on [doc.gitlab.com/ce/](http://doc.gitlab.com/ce/). +All documentation can be found on [doc.gitlab.com/ee/](http://doc.gitlab.com/ee/). ## Getting help @@ -1 +1 @@ -8.1.0.pre +8.1.0.pre-ee diff --git a/app/assets/images/authbuttons/kerberos_32.png b/app/assets/images/authbuttons/kerberos_32.png Binary files differnew file mode 100644 index 00000000000..66b6f91d863 --- /dev/null +++ b/app/assets/images/authbuttons/kerberos_32.png diff --git a/app/assets/images/authbuttons/kerberos_64.png b/app/assets/images/authbuttons/kerberos_64.png Binary files differnew file mode 100644 index 00000000000..f22fbc57da3 --- /dev/null +++ b/app/assets/images/authbuttons/kerberos_64.png diff --git a/app/assets/javascripts/admin_email_select.js.coffee b/app/assets/javascripts/admin_email_select.js.coffee new file mode 100644 index 00000000000..1802bc33921 --- /dev/null +++ b/app/assets/javascripts/admin_email_select.js.coffee @@ -0,0 +1,62 @@ +class @AdminEmailSelect + constructor: -> + $('.ajax-admin-email-select').each (i, select) => + skip_ldap = $(select).hasClass('skip_ldap') + + $(select).select2 + placeholder: "Select group or project" + multiple: $(select).hasClass('multiselect') + minimumInputLength: 0 + query: (query) -> + group_result = Api.groups query.term, skip_ldap, (groups) -> + groups + + project_result = Api.projects query.term, (projects) -> + projects + + $.when(project_result, group_result).done (projects, groups) -> + all = {id: "all"} + data = $.merge([all], groups[0], projects[0]) + query.callback({ results: data}) + + id: (object) -> + if object.path_with_namespace + "project-#{object.id}" + else if object.path + "group-#{object.id}" + else + "all" + + formatResult: (args...) => + @formatResult(args...) + formatSelection: (args...) => + @formatSelection(args...) + dropdownCssClass: "ajax-admin-email-dropdown" + escapeMarkup: (m) -> # we do not want to escape markup since we are displaying html in results + m + + formatResult: (object) -> + if object.path_with_namespace + "<div class='project-result'> + <div class='project-name'>#{object.name}</div> + <div class='project-path'>#{object.path_with_namespace}</div> + </div>" + else if object.path + "<div class='group-result'> + <div class='group-name'>#{object.name}</div> + <div class='group-path'>#{object.path}</div> + </div>" + else + "<div class='group-result'> + <div class='group-name'>All</div> + <div class='group-path'>All groups and projects</div> + </div>" + + formatSelection: (object) -> + if object.path_with_namespace + "Project: #{object.name}" + else if object.path + "Group: #{object.name}" + else + "All groups and projects" + diff --git a/app/assets/javascripts/api.js.coffee b/app/assets/javascripts/api.js.coffee index 9e5d594c861..505cad8048f 100644 --- a/app/assets/javascripts/api.js.coffee +++ b/app/assets/javascripts/api.js.coffee @@ -1,6 +1,8 @@ @Api = groups_path: "/api/:version/groups.json" group_path: "/api/:version/groups/:id.json" + projects_path: "/api/:version/projects.json" + ldap_groups_path: "/api/:version/ldap/:provider/groups.json" namespaces_path: "/api/:version/namespaces.json" group: (group_id, callback) -> @@ -47,3 +49,33 @@ buildUrl: (url) -> url = gon.relative_url_root + url if gon.relative_url_root? return url.replace(':version', gon.api_version) + + # Return LDAP groups list. Filtered by query + ldap_groups: (query, provider, callback) -> + url = Api.buildUrl(Api.ldap_groups_path) + url = url.replace(':provider', provider); + + $.ajax( + url: url + data: + private_token: gon.api_token + search: query + per_page: 20 + active: true + dataType: "json" + ).done (groups) -> + callback(groups) + + # Return projects list. Filtered by query + projects: (query, callback) -> + project_url = Api.buildUrl(Api.projects_path) + + project_query = $.ajax( + url: project_url + data: + private_token: gon.api_token + search: query + per_page: 20 + dataType: "json" + ).done (projects) -> + callback(projects) diff --git a/app/assets/javascripts/appearances.js.coffee b/app/assets/javascripts/appearances.js.coffee new file mode 100644 index 00000000000..24f83d18bbd --- /dev/null +++ b/app/assets/javascripts/appearances.js.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index 945ffb660e6..c46a63fa0c4 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -100,6 +100,8 @@ document.addEventListener("page:fetch", unbindEvents) window.addEventListener "hashchange", shiftWindow +$.timeago.settings.allowFuture = true + window.onload = -> # Scroll the window to avoid the topnav bar # https://github.com/twitter/bootstrap/issues/1768 diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index 951173af5d5..45688c4f806 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -100,6 +100,10 @@ class Dispatcher when 'users:show' new User() new Activities() + when 'projects:group_links:index' + new GroupsSelect() + when 'admin:emails:show' + new AdminEmailSelect() switch path.first() when 'admin' diff --git a/app/assets/javascripts/groups_select.js.coffee b/app/assets/javascripts/groups_select.js.coffee index 1084e2a17d1..a5d5a0b84a9 100644 --- a/app/assets/javascripts/groups_select.js.coffee +++ b/app/assets/javascripts/groups_select.js.coffee @@ -1,15 +1,28 @@ class @GroupsSelect constructor: -> $('.ajax-groups-select').each (i, select) => - skip_ldap = $(select).hasClass('skip_ldap') + skip_group = $(select).data("skip-group") + url = $(select).data("url") $(select).select2 placeholder: "Search for a group" multiple: $(select).hasClass('multiselect') minimumInputLength: 0 query: (query) -> - Api.groups query.term, skip_ldap, (groups) -> - data = { results: groups } + $.ajax( + url: url + data: + search: query.term + per_page: 20 + dataType: "json" + ).done (groups) -> + data = { results: [] } + + for group in groups + continue if skip_group && group.path == skip_group + + data.results.push(group) + query.callback(data) initSelection: (element, callback) -> diff --git a/app/assets/javascripts/ldap_groups_select.js.coffee b/app/assets/javascripts/ldap_groups_select.js.coffee new file mode 100644 index 00000000000..5f0180d2db8 --- /dev/null +++ b/app/assets/javascripts/ldap_groups_select.js.coffee @@ -0,0 +1,31 @@ +$ -> + ldapGroupResult = (group) -> + group.cn + + groupFormatSelection = (group) -> + group.cn + + $('.ajax-ldap-groups-select').each (i, select) -> + $(select).select2 + id: (group) -> + group.cn + placeholder: "Search for a LDAP group" + minimumInputLength: 1 + query: (query) -> + provider = $('#ldap_group_link_provider').val(); + Api.ldap_groups query.term, provider, (groups) -> + data = { results: groups } + query.callback(data) + + initSelection: (element, callback) -> + id = $(element).val() + if id isnt "" + callback(cn: id) + + formatResult: ldapGroupResult + formatSelection: groupFormatSelection + dropdownCssClass: "ajax-groups-dropdown" + formatNoMatches: (nomatch) -> + "Match not found; try refining your search query." + $('#ldap_group_link_provider').on 'change', -> + $('.ajax-ldap-groups-select').select2('data', null)
\ No newline at end of file diff --git a/app/assets/javascripts/merge_request_widget.js.coffee b/app/assets/javascripts/merge_request_widget.js.coffee index 3176e5a8965..f15c328b730 100644 --- a/app/assets/javascripts/merge_request_widget.js.coffee +++ b/app/assets/javascripts/merge_request_widget.js.coffee @@ -23,6 +23,18 @@ class @MergeRequestWidget setTimeout(merge_request_widget.mergeInProgress, 2000) dataType: 'json' + rebaseInProgress: -> + $.ajax + type: 'GET' + url: $('.merge-request').data('url') + success: (data) => + debugger + if data["rebase_in_progress?"] + setTimeout(merge_request_widget.rebaseInProgress, 2000) + else + location.reload() + dataType: 'json' + getMergeStatus: -> $.get @opts.url_to_automerge_check, (data) -> $('.mr-state-widget').replaceWith(data) diff --git a/app/assets/javascripts/project_new.js.coffee b/app/assets/javascripts/project_new.js.coffee index fecdb9fc2e7..a11bd074b32 100644 --- a/app/assets/javascripts/project_new.js.coffee +++ b/app/assets/javascripts/project_new.js.coffee @@ -3,3 +3,12 @@ class @ProjectNew $('.project-edit-container').on 'ajax:before', => $('.project-edit-container').hide() $('.save-project-loader').show() + @toggleSettings() + + + toggleSettings: -> + checked = $("#project_merge_requests_enabled").prop("checked") + if checked + $('.merge-request-feature').show() + else + $('.merge-request-feature').hide() diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee index 9157562a5c5..bccdb2091f1 100644 --- a/app/assets/javascripts/users_select.js.coffee +++ b/app/assets/javascripts/users_select.js.coffee @@ -4,6 +4,7 @@ class @UsersSelect @userPath = "/autocomplete/users/:id.json" $('.ajax-users-select').each (i, select) => + @skipLdap = $(select).hasClass('skip_ldap') @projectId = $(select).data('project-id') @groupId = $(select).data('group-id') @showCurrentUser = $(select).data('current-user') @@ -109,6 +110,7 @@ class @UsersSelect active: true project_id: @projectId group_id: @groupId + skip_ldap: @skipLdap current_user: @showCurrentUser dataType: "json" ).done (users) -> diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index ddbacd7fd41..c1ada58628f 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -280,6 +280,15 @@ img.emoji { margin-bottom: 10px; } +.group-name { + font-size: 14px; + line-height: 24px; +} + +.available-groups form { + margin: 5px 0; +} + table { td.permission-x { background: #D9EDF7 !important; diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss index 78fff58d232..4e7feafe16b 100644 --- a/app/assets/stylesheets/framework/selects.scss +++ b/app/assets/stylesheets/framework/selects.scss @@ -122,6 +122,15 @@ } } +.project-result { + .project-name { + font-weight: bold; + } + .project-path { + color: #999; + } +} + .user-result { .user-image { float: left; diff --git a/app/assets/stylesheets/pages/appearances.scss b/app/assets/stylesheets/pages/appearances.scss new file mode 100644 index 00000000000..e2070f17c3b --- /dev/null +++ b/app/assets/stylesheets/pages/appearances.scss @@ -0,0 +1,11 @@ +.appearance-logo-preview { + max-width: 400px; + margin-bottom: 20px; +} + +.appearance-light-logo-preview { + background-color: $background-color; + max-width: 72px; + padding: 10px; + margin-bottom: 10px; +} diff --git a/app/controllers/admin/appearances_controller.rb b/app/controllers/admin/appearances_controller.rb new file mode 100644 index 00000000000..2e5c08e03ea --- /dev/null +++ b/app/controllers/admin/appearances_controller.rb @@ -0,0 +1,59 @@ +class Admin::AppearancesController < Admin::ApplicationController + before_action :set_appearance, except: :create + + def show + end + + def preview + end + + def create + @appearance = Appearance.new(appearance_params) + + if @appearance.save + redirect_to admin_appearances_path, notice: 'Appearance was successfully created.' + else + render action: 'show' + end + end + + def update + if @appearance.update(appearance_params) + redirect_to admin_appearances_path, notice: 'Appearance was successfully updated.' + else + render action: 'show' + end + end + + def logo + appearance = Appearance.last + appearance.remove_logo! + + appearance.save + + redirect_to admin_appearances_path, notice: 'Logo was succesfully removed.' + end + + def header_logos + appearance = Appearance.last + appearance.remove_light_logo! + appearance.save + + redirect_to admin_appearances_path, notice: 'Header logo were succesfully removed.' + end + + private + + # Use callbacks to share common setup or constraints between actions. + def set_appearance + @appearance = Appearance.last || Appearance.new + end + + # Only allow a trusted parameter "white list" through. + def appearance_params + params.require(:appearance).permit( + :title, :description, :logo, :logo_cache, :light_logo, :light_logo_cache, + :updated_by + ) + end +end diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index a9bcfc7456a..7cb696a1211 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -48,6 +48,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController :sign_in_text, :help_page_text, :home_page_url, + :help_text, :after_sign_out_path, :max_attachment_size, :session_expire_delay, diff --git a/app/controllers/admin/emails_controller.rb b/app/controllers/admin/emails_controller.rb new file mode 100644 index 00000000000..13b9e0c073f --- /dev/null +++ b/app/controllers/admin/emails_controller.rb @@ -0,0 +1,9 @@ +class Admin::EmailsController < Admin::ApplicationController + def show + end + + def create + AdminEmailsWorker.perform_async(params[:recipients], params[:subject], params[:body]) + redirect_to admin_email_path, notice: 'Email sent' + end +end diff --git a/app/controllers/admin/git_hooks_controller.rb b/app/controllers/admin/git_hooks_controller.rb new file mode 100644 index 00000000000..ac726659044 --- /dev/null +++ b/app/controllers/admin/git_hooks_controller.rb @@ -0,0 +1,29 @@ +class Admin::GitHooksController < Admin::ApplicationController + before_action :git_hook + + respond_to :html + + def index + end + + def update + @git_hook.update_attributes(git_hook_params.merge(is_sample: true)) + + if @git_hook.valid? + redirect_to admin_git_hooks_path + else + render :index + end + end + + private + + def git_hook_params + params.require(:git_hook).permit(:deny_delete_tag, :delete_branch_regex, + :commit_message_regex, :force_push_regex, :author_email_regex, :member_check, :file_name_regex, :max_file_size) + end + + def git_hook + @git_hook ||= GitHook.find_or_create_by(is_sample: true) + end +end diff --git a/app/controllers/admin/licenses_controller.rb b/app/controllers/admin/licenses_controller.rb new file mode 100644 index 00000000000..513599f94ed --- /dev/null +++ b/app/controllers/admin/licenses_controller.rb @@ -0,0 +1,68 @@ +class Admin::LicensesController < Admin::ApplicationController + before_action :license, only: [:show, :download, :destroy] + before_action :require_license, only: [:show, :download, :destroy] + + respond_to :html + + def show + @previous_licenses = License.previous + end + + def download + send_data @license.data, filename: @license.data_filename, disposition: 'attachment' + end + + def new + @license = License.new + end + + def create + unless params[:license] + flash.now[:alert] = "No license was selected." + + @license = License.new + render :new + return + end + + @license = License.new(license_params) + + respond_with(@license, location: admin_license_path) do + if @license.save + flash[:notice] = "The license was successfully uploaded and is now active. You can see the details below." + end + end + end + + def destroy + license.destroy + + if License.current + flash[:notice] = "The license was removed. GitLab has fallen back on the previous license." + else + flash[:alert] = "The license was removed. GitLab now no longer has a valid license." + end + + redirect_to admin_license_path + end + + private + + def license + @license ||= begin + License.reset_current + License.current + end + end + + def require_license + return if license + + flash.keep + redirect_to new_admin_license_path + end + + def license_params + params.require(:license).permit(:data_file) + end +end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 0d182e8eb04..fb855f9e073 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -263,6 +263,12 @@ class ApplicationController < ActionController::Base end end + def require_ldap_enabled + unless Gitlab.config.ldap.enabled + render_404 and return + end + end + def set_filters_params params[:sort] ||= 'created_desc' params[:scope] = 'all' if params[:scope].blank? diff --git a/app/controllers/audit_events_controller.rb b/app/controllers/audit_events_controller.rb new file mode 100644 index 00000000000..c62a5477a10 --- /dev/null +++ b/app/controllers/audit_events_controller.rb @@ -0,0 +1,39 @@ +class AuditEventsController < ApplicationController + # Authorize + before_action :repository, only: :project_log + before_action :authorize_admin_project!, only: :project_log + before_action :group, only: :group_log + before_action :authorize_admin_group!, only: :group_log + + layout :determine_layout + + def project_log + @events = AuditEvent.where(entity_type: "Project", entity_id: project.id).page(params[:page]).per(20) + end + + def group_log + @events = AuditEvent.where(entity_type: "Group", entity_id: group.id).page(params[:page]).per(20) + end + + private + + def group + @group ||= Group.find_by(path: params[:group_id]) + end + + def authorize_admin_group! + render_404 unless can?(current_user, :admin_group, group) + end + + def determine_layout + if @project + 'project_settings' + elsif @group + 'group_settings' + end + end + + def audit_events_params + params.permit(:project_id, :group_id) + end +end diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb index 202e9da9eee..90efc374152 100644 --- a/app/controllers/autocomplete_controller.rb +++ b/app/controllers/autocomplete_controller.rb @@ -30,6 +30,7 @@ class AutocompleteController < ApplicationController end @users ||= User.none + @users = @users.non_ldap if params[:skip_ldap] == 'true' @users = @users.search(params[:search]) if params[:search].present? @users = @users.active @users = @users.reorder(:name) diff --git a/app/controllers/groups/application_controller.rb b/app/controllers/groups/application_controller.rb index 6878d4bc07e..8c44f59cebe 100644 --- a/app/controllers/groups/application_controller.rb +++ b/app/controllers/groups/application_controller.rb @@ -2,7 +2,11 @@ class Groups::ApplicationController < ApplicationController layout 'group' private - + + def group + @group ||= Group.find_by(path: params[:group_id]) + end + def authorize_read_group! unless @group and can?(current_user, :read_group, @group) if current_user.nil? @@ -12,13 +16,13 @@ class Groups::ApplicationController < ApplicationController end end end - + def authorize_admin_group! unless can?(current_user, :admin_group, group) return render_404 end end - + def authorize_admin_group_member! unless can?(current_user, :admin_group_member, group) return render_403 diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb index 91518c44a98..3f1c2985455 100644 --- a/app/controllers/groups/group_members_controller.rb +++ b/app/controllers/groups/group_members_controller.rb @@ -22,7 +22,15 @@ class Groups::GroupMembersController < Groups::ApplicationController end def create - @group.add_users(params[:user_ids].split(','), params[:access_level], current_user) + access_level = params[:access_level] + user_ids = params[:user_ids].split(',') + + @group.add_users(user_ids, access_level, current_user) + group_members = @group.group_members.where(user_id: user_ids) + + group_members.each do |group_member| + log_audit_event(group_member, action: :create) + end redirect_to group_group_members_path(@group), notice: 'Users were successfully added.' end @@ -32,7 +40,11 @@ class Groups::GroupMembersController < Groups::ApplicationController return render_403 unless can?(current_user, :update_group_member, @member) - @member.update_attributes(member_params) + old_access_level = @member.human_access + + if @member.update_attributes(member_params) + log_audit_event(@member, action: :update, old_access_level: old_access_level) + end end def destroy @@ -40,6 +52,8 @@ class Groups::GroupMembersController < Groups::ApplicationController if can?(current_user, :destroy_group_member, @group_member) # May fail if last owner. @group_member.destroy + log_audit_event(@group_member, action: :destroy) + respond_to do |format| format.html { redirect_to group_group_members_path(@group), notice: 'User was successfully removed from group.' } format.js { render nothing: true } @@ -68,6 +82,8 @@ class Groups::GroupMembersController < Groups::ApplicationController if can?(current_user, :destroy_group_member, @group_member) @group_member.destroy + log_audit_event(@group_member, action: :destroy) + redirect_to(dashboard_groups_path, notice: "You left #{group.name} group.") else if @group.last_owner?(current_user) @@ -80,11 +96,12 @@ class Groups::GroupMembersController < Groups::ApplicationController protected - def group - @group ||= Group.find_by(path: params[:group_id]) - end - def member_params params.require(:group_member).permit(:access_level, :user_id) end + + def log_audit_event(member, options = {}) + AuditEventService.new(current_user, @group, options). + for_member(member).security_event + end end diff --git a/app/controllers/groups/hooks_controller.rb b/app/controllers/groups/hooks_controller.rb new file mode 100644 index 00000000000..dc507092ebc --- /dev/null +++ b/app/controllers/groups/hooks_controller.rb @@ -0,0 +1,59 @@ +class Groups::HooksController < Groups::ApplicationController + # Authorize + before_action :group + before_action :authorize_admin_group! + + respond_to :html + + layout 'group_settings' + + def index + @hooks = @group.hooks + @hook = GroupHook.new + end + + def create + @hook = @group.hooks.new(hook_params) + @hook.save + + if @hook.valid? + redirect_to group_hooks_path(@group) + else + @hooks = @group.hooks.select(&:persisted?) + render :index + end + end + + def test + if @group.first_non_empty_project + status = TestHookService.new.execute(hook, current_user) + + if status + flash[:notice] = 'Hook successfully executed.' + else + flash[:alert] = 'Hook execution failed. '\ + 'Ensure hook URL is correct and service is up.' + end + else + flash[:alert] = 'Hook execution failed. Ensure the group has a project with commits.' + end + + redirect_to :back + end + + def destroy + hook.destroy + + redirect_to group_hooks_path(@group) + end + + private + + def hook + @hook ||= @group.hooks.find(params[:id]) + end + + def hook_params + params.require(:hook).permit(:url, :push_events, :issues_events, :merge_requests_events, :tag_push_events) + end +end diff --git a/app/controllers/groups/ldap_group_links_controller.rb b/app/controllers/groups/ldap_group_links_controller.rb new file mode 100644 index 00000000000..664a49539e8 --- /dev/null +++ b/app/controllers/groups/ldap_group_links_controller.rb @@ -0,0 +1,34 @@ +class Groups::LdapGroupLinksController < Groups::ApplicationController + before_action :group + before_action :require_ldap_enabled + before_action :authorize_admin_group! + + layout 'group_settings' + + def index + end + + def create + ldap_group_link = @group.ldap_group_links.build(ldap_group_link_params) + if ldap_group_link.save + if request.referer && request.referer.include?('admin') + redirect_to [:admin, @group], notice: 'New LDAP link saved' + else + redirect_to :back, notice: 'New LDAP link saved' + end + else + redirect_to :back, alert: "Could not create new LDAP link: #{ldap_group_link.errors.full_messages * ', '}" + end + end + + def destroy + @group.ldap_group_links.where(id: params[:id]).destroy_all + redirect_to :back, notice: 'LDAP link removed' + end + + private + + def ldap_group_link_params + params.require(:ldap_group_link).permit(:cn, :group_access, :provider) + end +end diff --git a/app/controllers/groups/ldaps_controller.rb b/app/controllers/groups/ldaps_controller.rb new file mode 100644 index 00000000000..4354ce3a369 --- /dev/null +++ b/app/controllers/groups/ldaps_controller.rb @@ -0,0 +1,10 @@ +class Groups::LdapsController < Groups::ApplicationController + before_action :group + before_action :authorize_admin_group! + + def reset_access + LdapGroupResetService.new.execute(group, current_user) + + redirect_to group_group_members_path(@group), notice: 'Access reset complete' + end +end diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index fb4eb094f27..3e4f8add6cb 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -38,6 +38,8 @@ class GroupsController < Groups::ApplicationController @last_push = current_user.recent_push if current_user @projects = @projects.includes(:namespace) + @shared_projects = @group.shared_projects + respond_to do |format| format.html @@ -91,6 +93,12 @@ class GroupsController < Groups::ApplicationController redirect_to root_path, alert: "Group '#{@group.name}' was successfully deleted." end + def autocomplete + groups = GroupsFinder.new.execute(current_user).search(params[:search]).limit(params[:per_page]) + + render json: groups.to_json + end + protected def group @@ -133,7 +141,7 @@ class GroupsController < Groups::ApplicationController end def group_params - params.require(:group).permit(:name, :description, :path, :avatar, :public) + params.require(:group).permit(:name, :description, :path, :avatar, :membership_lock, :share_with_group_lock, :public) end def load_events diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb index f809fa7500a..5bc8e14218b 100644 --- a/app/controllers/omniauth_callbacks_controller.rb +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -1,4 +1,5 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController + include AuthenticatesWithTwoFactor protect_from_forgery except: [:kerberos, :saml] @@ -28,8 +29,12 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController # Do additional LDAP checks for the user filter and EE features if @user.allowed? - log_audit_event(gl_user, with: :ldap) - sign_in_and_redirect(gl_user) + if @user.otp_required_for_login? + prompt_for_two_factor(gl_user) + else + log_audit_event(gl_user, with: :ldap) + sign_in_and_redirect(gl_user) + end else flash[:alert] = "Access denied for your LDAP account." redirect_to new_user_session_path diff --git a/app/controllers/profiles/keys_controller.rb b/app/controllers/profiles/keys_controller.rb index f3224148fda..f1a5b5ba18e 100644 --- a/app/controllers/profiles/keys_controller.rb +++ b/app/controllers/profiles/keys_controller.rb @@ -25,7 +25,7 @@ class Profiles::KeysController < Profiles::ApplicationController def destroy @key = current_user.keys.find(params[:id]) - @key.destroy + @key.destroy unless @key.is_a? LDAPKey respond_to do |format| format.html { redirect_to profile_keys_url } diff --git a/app/controllers/projects/approvers_controller.rb b/app/controllers/projects/approvers_controller.rb new file mode 100644 index 00000000000..27f39aaa921 --- /dev/null +++ b/app/controllers/projects/approvers_controller.rb @@ -0,0 +1,14 @@ +class Projects::ApproversController < ApplicationController + def destroy + if params[:merge_request_id] + authorize_create_merge_request! + merge_request = project.merge_requests.find_by!(iid: params[:merge_request_id]) + merge_request.approvers.find(params[:id]).destroy + else + authorize_admin_project! + project.approvers.find(params[:id]).destroy + end + + redirect_to :back + end +end diff --git a/app/controllers/projects/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb index 7d09288bc80..99ed1c7df87 100644 --- a/app/controllers/projects/deploy_keys_controller.rb +++ b/app/controllers/projects/deploy_keys_controller.rb @@ -28,6 +28,8 @@ class Projects::DeployKeysController < Projects::ApplicationController @key = DeployKey.new(deploy_key_params) if @key.valid? && @project.deploy_keys << @key + log_audit_event(@key.title, action: :create) + redirect_to namespace_project_deploy_keys_path(@project.namespace, @project) else @@ -38,13 +40,16 @@ class Projects::DeployKeysController < Projects::ApplicationController def enable @key = accessible_keys.find(params[:id]) @project.deploy_keys << @key + log_audit_event(@key.title, action: :create) redirect_to namespace_project_deploy_keys_path(@project.namespace, @project) end def disable + @key = accessible_keys.find(params[:id]) @project.deploy_keys_projects.find_by(deploy_key_id: params[:id]).destroy + log_audit_event(@key.title, action: :destroy) redirect_back_or_default(default: { action: 'index' }) end @@ -58,4 +63,9 @@ class Projects::DeployKeysController < Projects::ApplicationController def deploy_key_params params.require(:deploy_key).permit(:key, :title) end + + def log_audit_event(key_title, options = {}) + AuditEventService.new(current_user, @project, options). + for_deploy_key(key_title).security_event + end end diff --git a/app/controllers/projects/git_hooks_controller.rb b/app/controllers/projects/git_hooks_controller.rb new file mode 100644 index 00000000000..27c90df871b --- /dev/null +++ b/app/controllers/projects/git_hooks_controller.rb @@ -0,0 +1,33 @@ +class Projects::GitHooksController < Projects::ApplicationController + # Authorize + before_action :authorize_admin_project! + + respond_to :html + + layout "project_settings" + + def index + project.create_git_hook unless project.git_hook + + @git_hook = project.git_hook + end + + def update + @git_hook = project.git_hook + @git_hook.update_attributes(git_hook_params) + + if @git_hook.valid? + redirect_to namespace_project_git_hooks_path(@project.namespace, @project) + else + render :index + end + end + + private + + # Only allow a trusted parameter "white list" through. + def git_hook_params + params.require(:git_hook).permit(:deny_delete_tag, :delete_branch_regex, + :commit_message_regex, :force_push_regex, :author_email_regex, :member_check, :file_name_regex, :max_file_size) + end +end diff --git a/app/controllers/projects/group_links_controller.rb b/app/controllers/projects/group_links_controller.rb new file mode 100644 index 00000000000..4159e53bfa9 --- /dev/null +++ b/app/controllers/projects/group_links_controller.rb @@ -0,0 +1,23 @@ +class Projects::GroupLinksController < Projects::ApplicationController + layout 'project_settings' + before_action :authorize_admin_project! + + def index + @group_links = project.project_group_links.all + end + + def create + link = project.project_group_links.new + link.group_id = params[:link_group_id] + link.group_access = params[:link_group_access] + link.save + + redirect_to namespace_project_group_links_path(project.namespace, project) + end + + def destroy + project.project_group_links.find(params[:id]).destroy + + redirect_to namespace_project_group_links_path(project.namespace, project) + end +end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index e74c2905e48..8ae27e82288 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -50,6 +50,12 @@ class Projects::IssuesController < Projects::ApplicationController ) @issue = @project.issues.new(issue_params) + + # Set Issue description based on project template + if @project.issues_template.present? + @issue.description = @project.issues_template + end + respond_with(@issue) end diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 188f0cc4cea..841bff21be8 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -2,7 +2,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController before_action :module_enabled before_action :merge_request, only: [ :edit, :update, :show, :diffs, :commits, :merge, :merge_check, - :ci_status, :toggle_subscription + :ci_status, :toggle_subscription, :approve, :ff_merge, :rebase ] before_action :closes_issues, only: [:edit, :update, :show, :diffs, :commits] before_action :validates_merge_request, only: [:show, :diffs, :commits] @@ -49,7 +49,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController respond_to do |format| format.html - format.json { render json: @merge_request } + format.json { render json: @merge_request, methods: :rebase_in_progress? } format.diff { render text: @merge_request.to_diff(current_user) } format.patch { render text: @merge_request.to_patch(current_user) } end @@ -97,12 +97,16 @@ class Projects::MergeRequestsController < Projects::ApplicationController @diffs = @merge_request.compare_diffs @note_counts = Note.where(commit_id: @commits.map(&:id)). group(:commit_id).count + + set_suggested_approvers end def edit @source_project = @merge_request.source_project @target_project = @merge_request.target_project @target_branches = @merge_request.target_project.repository.branch_names + + set_suggested_approvers end def create @@ -152,6 +156,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController def merge return access_denied! unless @merge_request.can_be_merged_by?(current_user) + return render_404 unless @merge_request.approved? if @merge_request.mergeable? @merge_request.update(merge_error: nil) @@ -204,6 +209,41 @@ class Projects::MergeRequestsController < Projects::ApplicationController render nothing: true end + def approve + unless @merge_request.can_approve?(current_user) + return render_404 + end + + @approval = @merge_request.approvals.new + @approval.user = current_user + + if @approval.save + SystemNoteService.approve_mr(@merge_request, current_user) + end + + redirect_to merge_request_path(@merge_request) + end + + def ff_merge + return access_denied! unless @merge_request.can_be_merged_by?(current_user) + return render_404 unless @merge_request.approved? + + if @merge_request.ff_merge_possible? + MergeRequests::FfMergeService.new(merge_request.target_project, current_user). + execute(merge_request) + @status = true + else + @status = false + end + end + + def rebase + return access_denied! unless @merge_request.can_be_merged_by?(current_user) + return render_404 unless @merge_request.approved? + + RebaseWorker.perform_async(@merge_request.id, current_user.id) + end + protected def selected_target_project @@ -275,10 +315,19 @@ class Projects::MergeRequestsController < Projects::ApplicationController render 'invalid' end + def set_suggested_approvers + if @merge_request.requires_approve? + @suggested_approvers = Gitlab::AuthorityAnalyzer.new( + @merge_request, + current_user + ).calculate(@merge_request.approvals_required) + end + end + def merge_request_params permitted = params.require(:merge_request).permit( :title, :assignee_id, :source_project_id, :source_branch, - :target_project_id, :target_branch, :milestone_id, + :target_project_id, :target_branch, :milestone_id, :approver_ids, :state_event, :description, :task_num, label_ids: [] ) params[:merge_request][:title].strip! if params[:merge_request][:title] diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 9de5269cd25..bd2f76acd4b 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -27,6 +27,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController end @project_member = @project.project_members.new + @project_group_links = @project.project_group_links end def new @@ -35,18 +36,28 @@ class Projects::ProjectMembersController < Projects::ApplicationController def create @project.team.add_users(params[:user_ids].split(','), params[:access_level], current_user) + members = @project.project_members.where(user_id: params[:user_ids].split(',')) + + members.each do |member| + log_audit_event(member, action: :create) + end redirect_to namespace_project_project_members_path(@project.namespace, @project) end def update @project_member = @project.project_members.find(params[:id]) - @project_member.update_attributes(member_params) + old_access_level = @project_member.human_access + + if @project_member.update_attributes(member_params) + log_audit_event(@project_member, action: :update, old_access_level: old_access_level) + end end def destroy @project_member = @project.project_members.find(params[:id]) @project_member.destroy + log_audit_event(@project_member, action: :destroy) respond_to do |format| format.html do @@ -76,7 +87,9 @@ class Projects::ProjectMembersController < Projects::ApplicationController return redirect_back_or_default(default: { action: 'index' }, options: { alert: message }) end - @project.project_members.find_by(user_id: current_user).destroy + @project_member = @project.project_members.find_by(user_id: current_user) + @project_member.destroy + log_audit_event(@project_member, action: :destroy) respond_to do |format| format.html { redirect_to dashboard_projects_path } @@ -98,4 +111,9 @@ class Projects::ProjectMembersController < Projects::ApplicationController def member_params params.require(:project_member).permit(:user_id, :access_level) end + + def log_audit_event(member, options = {}) + AuditEventService.new(current_user, @project, options). + for_member(member).security_event + end end diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb index 42dbb497e01..404b5a5c43e 100644 --- a/app/controllers/projects/services_controller.rb +++ b/app/controllers/projects/services_controller.rb @@ -1,5 +1,5 @@ class Projects::ServicesController < Projects::ApplicationController - ALLOWED_PARAMS = [:title, :token, :type, :active, :api_key, :api_version, :subdomain, + ALLOWED_PARAMS = [:title, :token, :type, :active, :api_key, :api_url, :api_version, :subdomain, :room, :recipients, :project_url, :webhook, :user_key, :device, :priority, :sound, :bamboo_url, :username, :password, :build_key, :server, :teamcity_url, :drone_url, :build_type, @@ -7,8 +7,10 @@ class Projects::ServicesController < Projects::ApplicationController :colorize_messages, :channels, :push_events, :issues_events, :merge_requests_events, :tag_push_events, :note_events, :send_from_committer_email, :disable_diffs, :external_wiki_url, + :jira_issue_transition_id, :notify, :color, - :server_host, :server_port, :default_irc_uri, :enable_ssl_verification] + :server_host, :server_port, :default_irc_uri, :enable_ssl_verification, + :multiproject_enabled, :pass_unstable] # Parameters to ignore if no value is specified FILTER_BLANK_PARAMS = [:password] diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 23453195e85..947616af3e7 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -210,10 +210,32 @@ class ProjectsController < ApplicationController def project_params params.require(:project).permit( - :name, :path, :description, :issues_tracker, :tag_list, - :issues_enabled, :merge_requests_enabled, :snippets_enabled, :issues_tracker_id, :default_branch, - :wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id, :avatar, - :builds_enabled + :avatar, + :builds_enabled, + :default_branch, + :description, + :import_url, + :issues_enabled, + :issues_tracker, + :issues_tracker_id, + :last_activity_at, + :merge_requests_enabled, + :name, + :namespace_id, + :path, + :snippets_enabled, + :tag_list, + :visibility_level, + :wiki_enabled, + + # EE-only + :approvals_before_merge, + :approver_ids, + :issues_template, + :merge_requests_ff_only_enabled, + :merge_requests_rebase_enabled, + :merge_requests_template, + :reset_approvals_on_push ) end diff --git a/app/controllers/unsubscribes_controller.rb b/app/controllers/unsubscribes_controller.rb new file mode 100644 index 00000000000..a65e37acc30 --- /dev/null +++ b/app/controllers/unsubscribes_controller.rb @@ -0,0 +1,25 @@ +class UnsubscribesController < ApplicationController + skip_before_action :authenticate_user!, + :reject_blocked, :set_current_user_for_observers, + :add_abilities + + def show + @user = get_user + end + + def create + @user = get_user + if @user + @user.admin_unsubscribe! + Notify.send_unsubscribed_notification(@user).deliver + end + redirect_to new_user_session_path, notice: 'You have been unsubscribed' + end + + protected + + def get_user + @email = Base64.urlsafe_decode64(params[:email]) + User.where(email: @email).first + end +end diff --git a/app/controllers/uploads_controller.rb b/app/controllers/uploads_controller.rb index 868b05929d7..ac0560a1a7b 100644 --- a/app/controllers/uploads_controller.rb +++ b/app/controllers/uploads_controller.rb @@ -55,14 +55,15 @@ class UploadsController < ApplicationController "user" => User, "project" => Project, "note" => Note, - "group" => Group + "group" => Group, + "appearance" => Appearance } upload_models[params[:model]] end def upload_mount - upload_mounts = %w(avatar attachment file) + upload_mounts = %w(avatar attachment file logo light_logo) if upload_mounts.include?(params[:mounted_as]) params[:mounted_as] diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb index c81bb51583a..910fc01dca6 100644 --- a/app/finders/projects_finder.rb +++ b/app/finders/projects_finder.rb @@ -37,12 +37,24 @@ class ProjectsFinder ) else # User has no access to group or group projects + # or has access through shared project # # Return only: # public projects # internal projects - # - group.projects.public_and_internal_only + # shared projects + projects_ids = [] + ProjectGroupLink.where(project_id: group.projects).each do |shared_project| + if shared_project.group.users.include?(current_user) || shared_project.project.users.include?(current_user) + projects_ids << shared_project.project.id + end + end + + group.projects.where( + "projects.id IN (?) OR projects.visibility_level IN (?)", + projects_ids, + Project.public_and_internal_levels + ) end end else diff --git a/app/helpers/appearances_helper.rb b/app/helpers/appearances_helper.rb index c5820bf4c50..4ea4100ede3 100644 --- a/app/helpers/appearances_helper.rb +++ b/app/helpers/appearances_helper.rb @@ -1,21 +1,33 @@ module AppearancesHelper - def brand_item - nil - end - def brand_title - 'GitLab Community Edition' + if brand_item + brand_item.title + else + 'GitLab Enterprise Edition' + end end def brand_image - nil + if brand_item.logo? + image_tag brand_item.logo + else + nil + end end def brand_text - nil + markdown(brand_item.description) + end + + def brand_item + @appearance ||= Appearance.first end def brand_header_logo - render 'shared/logo.svg' + if brand_item && brand_item.light_logo? + image_tag brand_item.light_logo + else + render 'shared/logo.svg' + end end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 8ecdeaf8e76..c4182c84bd0 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -205,7 +205,7 @@ module ApplicationHelper def time_ago_with_tooltip(time, placement: 'top', html_class: 'time_ago', skip_js: false) element = content_tag :time, time.to_s, class: "#{html_class} js-timeago", - datetime: time.getutc.iso8601, + datetime: time.to_time.getutc.iso8601, title: time.in_time_zone.stamp('Aug 21, 2011 9:23pm'), data: { toggle: 'tooltip', placement: placement, container: 'body' } diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 7d6b58ee21a..f4a47d7144c 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -19,6 +19,10 @@ module ApplicationSettingsHelper current_application_settings.sign_in_text end + def help_text + current_application_settings.help_text + end + def user_oauth_applications? current_application_settings.user_oauth_applications end diff --git a/app/helpers/audit_events_helper.rb b/app/helpers/audit_events_helper.rb new file mode 100644 index 00000000000..d4f05563e5c --- /dev/null +++ b/app/helpers/audit_events_helper.rb @@ -0,0 +1,14 @@ +module AuditEventsHelper + + def human_text(details) + details.map{ |key, value| select_keys(key, value) }.join(" ").humanize + end + + def select_keys(key, value) + if key.match(/^target_.*/) + "" + else + "#{key.to_s} <strong>#{value}</strong>" + end + end +end diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb index 2c81ea1623c..6eae3d453f3 100644 --- a/app/helpers/auth_helper.rb +++ b/app/helpers/auth_helper.rb @@ -1,11 +1,15 @@ module AuthHelper PROVIDERS_WITH_ICONS = %w(twitter github gitlab bitbucket google_oauth2 facebook).freeze - FORM_BASED_PROVIDERS = [/\Aldap/, 'crowd'].freeze + FORM_BASED_PROVIDERS = [/\Aldap/, 'kerberos', 'crowd'].freeze def ldap_enabled? Gitlab.config.ldap.enabled end + def kerberos_enabled? + auth_providers.include?(:kerberos) + end + def provider_has_icon?(name) PROVIDERS_WITH_ICONS.include?(name.to_s) end diff --git a/app/helpers/branches_helper.rb b/app/helpers/branches_helper.rb index d6eaa7d57bc..ba68931661b 100644 --- a/app/helpers/branches_helper.rb +++ b/app/helpers/branches_helper.rb @@ -14,4 +14,14 @@ module BranchesHelper ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(branch_name) end + + def can_rebase?(project, branch_name) + if project.protected_branch? branch_name + can?(current_user, :push_code_to_protected_branches, project) + elsif can?(current_user, :push_code, project) + true + else + false + end + end end diff --git a/app/helpers/license_helper.rb b/app/helpers/license_helper.rb new file mode 100644 index 00000000000..a9ed65e5d50 --- /dev/null +++ b/app/helpers/license_helper.rb @@ -0,0 +1,70 @@ +module LicenseHelper + def license_message(signed_in: signed_in?, is_admin: (current_user && current_user.is_admin?)) + @license_message ||= + if License.current + yes_license_message(signed_in, is_admin) + else + no_license_message(signed_in, is_admin) + end + end + + private + + def no_license_message(signed_in, is_admin) + message = [] + + message << "No GitLab Enterprise Edition license has been provided yet." + message << "Pushing code and creation of issues and merge requests has been disabled." + + message << + if is_admin + "Upload a license in the admin area" + else + "Ask an admin to upload a license" + end + + message << "to activate this functionality." + + message.join(" ") + end + + def yes_license_message(signed_in, is_admin) + license = License.current + + return unless signed_in + + return unless (license.notify_admins? && is_admin) || license.notify_users? + + message = [] + + message << "The GitLab Enterprise Edition license" + message << (license.expired? ? "expired" : "will expire") + message << "on #{license.expires_at}." + + if license.expired? && license.will_block_changes? + message << "Pushing code and creation of issues and merge requests" + + message << + if license.block_changes? + "has been disabled." + else + "will be disabled on #{license.block_changes_at}." + end + end + + message << + if is_admin + "Upload a new license in the admin area" + else + "Ask an admin to upload a new license" + end + + message << "to" + message << (license.block_changes? ? "restore" : "ensure uninterrupted") + message << "service." + + message.join(" ") + end + + extend self +end diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index 728d877ace2..e19a59c345f 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -35,7 +35,14 @@ module MergeRequestsHelper end def ci_build_details_path(merge_request) - merge_request.source_project.ci_service.build_page(merge_request.last_commit.sha, merge_request.source_branch) + build_url = merge_request.source_project.ci_service.build_page(merge_request.last_commit.sha, merge_request.source_branch) + parsed_url = URI.parse(build_url) + + unless parsed_url.userinfo.blank? + parsed_url.userinfo = '' + end + + parsed_url.to_s end def merge_path_description(merge_request, separator) @@ -62,6 +69,44 @@ module MergeRequestsHelper ) end + def render_items_list(items, separator = "and") + items_cnt = items.size + + case items_cnt + when 1 + items.first + when 2 + "#{items.first} #{separator} #{items.last}" + else + last_item = items.pop + "#{items.join(", ")} #{separator} #{last_item}" + end + end + + def render_require_section(merge_request) + str = if merge_request.approvals_left == 1 + "Requires one more approval" + else + "Requires #{merge_request.approvals_left} more approvals" + end + + if merge_request.approvers_left.any? + more_approvals = merge_request.approvals_left - merge_request.approvers_left.count + approvers_names = merge_request.approvers_left.map(&:name) + + case true + when more_approvals > 0 + str << " (from #{render_items_list(approvers_names + ["#{more_approvals} more"])})" + when more_approvals < 0 + str << " (from #{render_items_list(approvers_names, "or")})" + else + str << " (from #{render_items_list(approvers_names)})" + end + end + + str + end + def source_branch_with_namespace(merge_request) if merge_request.for_fork? namespace = link_to(merge_request.source_project_namespace, diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 690ae2090db..00795594ad0 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -277,6 +277,14 @@ module ProjectsHelper end end + def membership_locked? + if @project.group && @project.group.membership_lock + true + else + false + end + end + def user_max_access_in_project(user, project) level = project.team.max_member_access(user) diff --git a/app/helpers/selects_helper.rb b/app/helpers/selects_helper.rb index 12fce8db701..c2ec6426c8c 100644 --- a/app/helpers/selects_helper.rb +++ b/app/helpers/selects_helper.rb @@ -2,6 +2,7 @@ module SelectsHelper def users_select_tag(id, opts = {}) css_class = "ajax-users-select " css_class << "multiselect " if opts[:multiple] + css_class << "skip_ldap " if opts[:skip_ldap] css_class << (opts[:class] || '') value = opts[:selected] || '' placeholder = opts[:placeholder] || 'Search for a user' @@ -34,12 +35,29 @@ module SelectsHelper hidden_field_tag(id, value, html) end + def ldap_server_select_options + options_from_collection_for_select( + Gitlab::LDAP::Config.servers, + 'provider_name', + 'label' + ) + end + def groups_select_tag(id, opts = {}) css_class = "ajax-groups-select " css_class << "multiselect " if opts[:multiple] css_class << (opts[:class] || '') value = opts[:selected] || '' + hidden_field_tag(id, value, class: css_class, data: { skip_group: opts[:skip_group], url: autocomplete_groups_path }) + end + + def admin_email_select_tag(id, opts = {}) + css_class = "ajax-admin-email-select " + css_class << "multiselect " if opts[:multiple] + css_class << (opts[:class] || '') + value = opts[:selected] || '' + hidden_field_tag(id, value, class: css_class) end end diff --git a/app/helpers/tab_helper.rb b/app/helpers/tab_helper.rb index 04e53fe7c61..5d376825b27 100644 --- a/app/helpers/tab_helper.rb +++ b/app/helpers/tab_helper.rb @@ -97,7 +97,7 @@ module TabHelper def project_tab_class return "active" if current_page?(controller: "/projects", action: :edit, id: @project) - if ['services', 'hooks', 'deploy_keys', 'protected_branches'].include? controller.controller_name + if ['services', 'hooks', 'deploy_keys', 'protected_branches', 'git_hooks'].include? controller.controller_name "active" end end diff --git a/app/mailers/emails/admin_notification.rb b/app/mailers/emails/admin_notification.rb new file mode 100644 index 00000000000..aa9990a2f83 --- /dev/null +++ b/app/mailers/emails/admin_notification.rb @@ -0,0 +1,15 @@ +module Emails + module AdminNotification + def send_admin_notification(user_id, subject, body) + email = recipient(user_id) + @unsubscribe_url = unsubscribe_url(email: Base64.urlsafe_encode64(email)) + @body = body + mail to: email, subject: subject + end + + def send_unsubscribed_notification(user_id) + email = recipient(user_id) + mail to: email, subject: "Unsubscribed from GitLab administrator notifications" + end + end +end diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index 50a409c3754..8340f5a7093 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -1,6 +1,7 @@ class Notify < BaseMailer include ActionDispatch::Routing::PolymorphicRoutes + include Emails::AdminNotification include Emails::Issues include Emails::MergeRequests include Emails::Notes diff --git a/app/models/ability.rb b/app/models/ability.rb index 5ae28d5133e..72b27daa76e 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -5,18 +5,34 @@ class Ability return [] unless user.kind_of?(User) return [] if user.blocked? - case subject.class.name - when "Project" then project_abilities(user, subject) - when "Issue" then issue_abilities(user, subject) - when "Note" then note_abilities(user, subject) - when "ProjectSnippet" then project_snippet_abilities(user, subject) - when "PersonalSnippet" then personal_snippet_abilities(user, subject) - when "MergeRequest" then merge_request_abilities(user, subject) - when "Group" then group_abilities(user, subject) - when "Namespace" then namespace_abilities(user, subject) - when "GroupMember" then group_member_abilities(user, subject) - else [] - end.concat(global_abilities(user)) + abilities = + case subject.class.name + when "Project" then project_abilities(user, subject) + when "Issue" then issue_abilities(user, subject) + when "Note" then note_abilities(user, subject) + when "ProjectSnippet" then project_snippet_abilities(user, subject) + when "PersonalSnippet" then personal_snippet_abilities(user, subject) + when "MergeRequest" then merge_request_abilities(user, subject) + when "Group" then group_abilities(user, subject) + when "Namespace" then namespace_abilities(user, subject) + when "GroupMember" then group_member_abilities(user, subject) + else [] + end + + abilities.concat(global_abilities(user)) + + abilities -= license_blocked_abilities if License.block_changes? + + abilities + end + + def license_blocked_abilities + [ + :create_issue, + :create_merge_request, + :push_code, + :push_code_to_protected_branches + ] end # List of possible abilities @@ -243,6 +259,10 @@ class Ability :admin_namespace, :admin_group_member ]) + + unless group.ldap_synced? + rules << :admin_group_member + end end rules.flatten diff --git a/app/models/appearance.rb b/app/models/appearance.rb new file mode 100644 index 00000000000..95f2ab3d28f --- /dev/null +++ b/app/models/appearance.rb @@ -0,0 +1,24 @@ +# == Schema Information +# +# Table name: appearances +# +# id :integer not null, primary key +# title :string(255) +# description :text +# logo :string(255) +# updated_by :integer +# created_at :datetime +# updated_at :datetime +# dark_logo :string(255) +# light_logo :string(255) +# + +class Appearance < ActiveRecord::Base + validates :title, presence: true + validates :description, presence: true + validates :logo, file_size: { maximum: 1000.kilobytes.to_i } + validates :light_logo, file_size: { maximum: 1000.kilobytes.to_i } + + mount_uploader :logo, AttachmentUploader + mount_uploader :light_logo, AttachmentUploader +end diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 9e70247ef51..3d5573eff60 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -13,6 +13,7 @@ # home_page_url :string(255) # default_branch_protection :integer default(2) # twitter_sharing_enabled :boolean default(TRUE) +# help_text :text # restricted_visibility_levels :text # version_check_enabled :boolean default(TRUE) # max_attachment_size :integer default(10), not null diff --git a/app/models/approval.rb b/app/models/approval.rb new file mode 100644 index 00000000000..5ac0cab9bc8 --- /dev/null +++ b/app/models/approval.rb @@ -0,0 +1,18 @@ +# == Schema Information +# +# Table name: approvals +# +# id :integer not null, primary key +# merge_request_id :integer not null +# user_id :integer not null +# created_at :datetime +# updated_at :datetime +# + +class Approval < ActiveRecord::Base + belongs_to :user + belongs_to :merge_request + + validates :merge_request_id, presence: true + validates :user_id, presence: true, uniqueness: { scope: [:merge_request_id] } +end diff --git a/app/models/approver.rb b/app/models/approver.rb new file mode 100644 index 00000000000..f06c53026ba --- /dev/null +++ b/app/models/approver.rb @@ -0,0 +1,18 @@ +# == Schema Information +# +# Table name: approvers +# +# id :integer not null, primary key +# target_id :integer not null +# target_type :string(255) +# user_id :integer not null +# created_at :datetime +# updated_at :datetime +# + +class Approver < ActiveRecord::Base + belongs_to :target, polymorphic: true + belongs_to :user + + validates :user, presence: true +end diff --git a/app/models/git_hook.rb b/app/models/git_hook.rb new file mode 100644 index 00000000000..e48a399d600 --- /dev/null +++ b/app/models/git_hook.rb @@ -0,0 +1,25 @@ +class GitHook < ActiveRecord::Base + belongs_to :project + + validates :project, presence: true, unless: "is_sample?" + + def commit_message_allowed?(message) + if commit_message_regex.present? + if message =~ Regexp.new(commit_message_regex) + true + else + false + end + else + true + end + end + + def commit_validation? + commit_message_regex.present? || + author_email_regex.present? || + member_check || + file_name_regex.present? || + max_file_size > 0 + end +end diff --git a/app/models/group.rb b/app/models/group.rb index 793a3b5ef2e..4c4f127ca6f 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -23,6 +23,10 @@ class Group < Namespace has_many :group_members, dependent: :destroy, as: :source, class_name: 'GroupMember' has_many :users, through: :group_members + has_many :project_group_links, dependent: :destroy + has_many :shared_projects, through: :project_group_links, source: :project + has_many :ldap_group_links, foreign_key: 'group_id', dependent: :destroy + has_many :hooks, dependent: :destroy, class_name: 'GroupHook' validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? } validates :avatar, file_size: { maximum: 200.kilobytes.to_i } @@ -68,14 +72,18 @@ class Group < Namespace @owners ||= group_members.owners.includes(:user).map(&:user) end - def add_users(user_ids, access_level, current_user = nil) + def add_users(user_ids, access_level, current_user = nil, skip_notification: false) user_ids.each do |user_id| - Member.add_user(self.group_members, user_id, access_level, current_user) + Member.add_user(self.group_members, user_id, access_level, current_user, skip_notification: skip_notification) end end - def add_user(user, access_level, current_user = nil) - add_users([user], access_level, current_user) + def add_user(user, access_level, current_user = nil, skip_notification: false) + add_users([user], access_level, current_user, skip_notification: skip_notification) + end + + def add_owner(user, current_user = nil, skip_notification: false) + self.add_user(user, Gitlab::Access::OWNER, current_user, skip_notification: skip_notification) end def add_guest(user, current_user = nil) @@ -94,10 +102,6 @@ class Group < Namespace add_user(user, Gitlab::Access::MASTER, current_user) end - def add_owner(user, current_user = nil) - add_user(user, Gitlab::Access::OWNER, current_user) - end - def has_owner?(user) owners.include?(user) end @@ -120,10 +124,27 @@ class Group < Namespace end end + def human_ldap_access + Gitlab::Access.options_with_owner.key ldap_access + end + def public_profile? self.public || projects.public_only.any? end + # NOTE: Backwards compatibility with old ldap situation + def ldap_cn + ldap_group_links.first.try(:cn) + end + + def ldap_access + ldap_group_links.first.try(:group_access) + end + + def ldap_synced? + Gitlab.config.ldap.enabled && ldap_cn.present? + end + def post_create_hook Gitlab::AppLogger.info("Group \"#{name}\" was created") @@ -139,4 +160,8 @@ class Group < Namespace def system_hook_service SystemHooksService.new end + + def first_non_empty_project + projects.detect{ |project| !project.empty_repo? } + end end diff --git a/app/models/historical_data.rb b/app/models/historical_data.rb new file mode 100644 index 00000000000..a62ae8780b1 --- /dev/null +++ b/app/models/historical_data.rb @@ -0,0 +1,22 @@ +class HistoricalData < ActiveRecord::Base + validate :date, presence: true + + # HistoricalData.during((Date.today - 1.year)..Date.today).average(:active_user_count) + scope :during, ->(range) { where(date: range) } + # HistoricalData.up_until(Date.today - 1.month).average(:active_user_count) + scope :up_until, ->(date) { where("date <= :date", date: date) } + + class << self + def track! + create!( + date: Date.today, + active_user_count: User.active.count + ) + end + + # HistoricalData.at(Date.new(2014, 1, 1)).active_user_count + def at(date) + find_by(date: date) + end + end +end diff --git a/app/models/hooks/group_hook.rb b/app/models/hooks/group_hook.rb new file mode 100644 index 00000000000..dae2e56effa --- /dev/null +++ b/app/models/hooks/group_hook.rb @@ -0,0 +1,20 @@ +# == Schema Information +# +# Table name: web_hooks +# +# id :integer not null, primary key +# url :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# type :string(255) default("ProjectHook") +# service_id :integer +# push_events :boolean default(TRUE), not null +# issues_events :boolean default(FALSE), not null +# merge_requests_events :boolean default(FALSE), not null +# tag_push_events :boolean default(FALSE) +# + +class GroupHook < ProjectHook + belongs_to :group +end diff --git a/app/models/jira_issue.rb b/app/models/jira_issue.rb new file mode 100644 index 00000000000..5b21aac5e43 --- /dev/null +++ b/app/models/jira_issue.rb @@ -0,0 +1,2 @@ +class JiraIssue < ExternalIssue +end diff --git a/app/models/key.rb b/app/models/key.rb index 406a1257b5d..d1e6655659b 100644 --- a/app/models/key.rb +++ b/app/models/key.rb @@ -27,6 +27,8 @@ class Key < ActiveRecord::Base validates :key, format: { without: /\n|\r/, message: 'should be a single line' } validates :fingerprint, uniqueness: true, presence: { message: 'cannot be generated' } + scope :ldap, -> { where(type: 'LDAPKey') } + delegate :name, :email, to: :user, prefix: true after_create :add_to_shell diff --git a/app/models/ldap_group_link.rb b/app/models/ldap_group_link.rb new file mode 100644 index 00000000000..541471469a4 --- /dev/null +++ b/app/models/ldap_group_link.rb @@ -0,0 +1,30 @@ +class LdapGroupLink < ActiveRecord::Base + include Gitlab::Access + belongs_to :group + + validates :cn, :group_access, :group_id, presence: true + validates :cn, uniqueness: { scope: [:group_id, :provider] } + validates :group_access, inclusion: { in: Gitlab::Access.all_values } + validates :provider, presence: true + + scope :with_provider, ->(provider) { where(provider: provider) } + + def access_field + group_access + end + + def config + Gitlab::LDAP::Config.new(provider) + rescue Gitlab::LDAP::Config::InvalidProvider + nil + end + + # default to the first LDAP server + def provider + read_attribute(:provider) || Gitlab::LDAP::Config.providers.first + end + + def provider_label + config.label + end +end diff --git a/app/models/ldap_key.rb b/app/models/ldap_key.rb new file mode 100644 index 00000000000..4a344ee0fa0 --- /dev/null +++ b/app/models/ldap_key.rb @@ -0,0 +1,16 @@ +# == Schema Information +# +# Table name: keys +# +# id :integer not null, primary key +# user_id :integer +# created_at :datetime +# updated_at :datetime +# key :text +# title :string(255) +# identifier :string(255) +# type :string(255) +# + +class LDAPKey < Key +end diff --git a/app/models/license.rb b/app/models/license.rb new file mode 100644 index 00000000000..81b02593d9c --- /dev/null +++ b/app/models/license.rb @@ -0,0 +1,125 @@ +class License < ActiveRecord::Base + include ActionView::Helpers::NumberHelper + + validate :valid_license + validate :active_user_count, unless: :persisted? + validate :not_expired, unless: :persisted? + + before_validation :reset_license, if: :data_changed? + + after_create :reset_current + after_destroy :reset_current + + scope :previous, -> { order(created_at: :desc).offset(1) } + + class << self + def current + return @current if @current + + license = self.last + return unless license && license.valid? + + @current = license + end + + def reset_current + @current = nil + end + + def block_changes? + !current || current.block_changes? + end + end + + def data_filename + company_name = self.licensee["Company"] || self.licensee.values.first + clean_company_name = company_name.gsub(/[^A-Za-z0-9]/, "") + "#{clean_company_name}.gitlab-license" + end + + def data_file=(file) + self.data = file.read + end + + def license + return nil unless self.data + + @license ||= + begin + Gitlab::License.import(self.data) + rescue Gitlab::License::ImportError + nil + end + end + + def license? + self.license && self.license.valid? + end + + def method_missing(method_name, *arguments, &block) + if License.column_names.include?(method_name.to_s) + super + elsif license && license.respond_to?(method_name) + license.send(method_name, *arguments, &block) + else + super + end + end + + def respond_to_missing?(method_name, include_private = false) + if License.column_names.include?(method_name.to_s) + super + elsif license && license.respond_to?(method_name) + true + else + super + end + end + + private + + def reset_current + self.class.reset_current + end + + def reset_license + @license = nil + end + + def valid_license + return if license? + + self.errors.add(:base, "The license file is invalid. Make sure it is exactly as you received it from GitLab B.V..") + end + + def active_user_count + return unless self.license? && self.restricted?(:active_user_count) + + restricted_user_count = self.restrictions[:active_user_count] + + date_range = (self.starts_at - 1.year)..self.starts_at + active_user_count = HistoricalData.during(date_range).maximum(:active_user_count) || 0 + + return unless active_user_count + + return if active_user_count < restricted_user_count + + overage = active_user_count - restricted_user_count + + message = "" + message << "During the year before this license started, this GitLab installation had " + message << "#{number_with_delimiter active_user_count} active #{"user".pluralize(active_user_count)}, " + message << "exceeding this license's limit of #{number_with_delimiter restricted_user_count} by " + message << "#{number_with_delimiter overage} #{"user".pluralize(overage)}. " + message << "Please upload a license for at least " + message << "#{number_with_delimiter active_user_count} #{"user".pluralize(active_user_count)}." + + self.errors.add(:base, message) + end + + def not_expired + return unless self.license? && self.expired? + + self.errors.add(:base, "This license has already expired.") + end +end diff --git a/app/models/member.rb b/app/models/member.rb index cae8caa23fb..c9cd984de7c 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -23,6 +23,7 @@ class Member < ActiveRecord::Base include Gitlab::Access attr_accessor :raw_invite_token + attr_accessor :skip_notification belongs_to :created_by, class_name: "User" belongs_to :user @@ -71,7 +72,7 @@ class Member < ActiveRecord::Base user end - def add_user(members, user_id, access_level, current_user = nil) + def add_user(members, user_id, access_level, current_user = nil, skip_notification: false) user = user_for_id(user_id) # `user` can be either a User object or an email to be invited @@ -85,6 +86,8 @@ class Member < ActiveRecord::Base member.created_by ||= current_user member.access_level = access_level + member.skip_notification = skip_notification + member.save end end diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb index 65d2ea00570..a6bae8e9716 100644 --- a/app/models/members/group_member.rb +++ b/app/models/members/group_member.rb @@ -30,6 +30,7 @@ class GroupMember < Member scope :with_group, ->(group) { where(source_id: group.id) } scope :with_user, ->(user) { where(user_id: user.id) } + scope :with_ldap_dn, -> { joins(user: :identities).where("identities.provider LIKE ?", 'ldap%') } def self.access_level_roles Gitlab::Access.options_with_owner @@ -46,33 +47,33 @@ class GroupMember < Member private def send_invite - notification_service.invite_group_member(self, @raw_invite_token) + notification_service.invite_group_member(self, @raw_invite_token) unless @skip_notification super end def post_create_hook - notification_service.new_group_member(self) + notification_service.new_group_member(self) unless @skip_notification super end def post_update_hook if access_level_changed? - notification_service.update_group_member(self) + notification_service.update_group_member(self) unless @skip_notification end super end def after_accept_invite - notification_service.accept_group_invite(self) + notification_service.accept_group_invite(self) unless @skip_notification super end def after_decline_invite - notification_service.decline_group_invite(self) + notification_service.decline_group_invite(self) unless @skip_notification super end diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb index 1b0c76917aa..0ed43b60d1d 100644 --- a/app/models/members/project_member.rb +++ b/app/models/members/project_member.rb @@ -123,7 +123,7 @@ class ProjectMember < Member private def send_invite - notification_service.invite_project_member(self, @raw_invite_token) + notification_service.invite_project_member(self, @raw_invite_token) unless @skip_notification super end @@ -131,7 +131,7 @@ class ProjectMember < Member def post_create_hook unless owner? event_service.join_project(self.project, self.user) - notification_service.new_project_member(self) + notification_service.new_project_member(self) unless @skip_notification end super @@ -139,7 +139,7 @@ class ProjectMember < Member def post_update_hook if access_level_changed? - notification_service.update_project_member(self) + notification_service.update_project_member(self) unless @skip_notification end super @@ -152,13 +152,13 @@ class ProjectMember < Member end def after_accept_invite - notification_service.accept_project_invite(self) + notification_service.accept_project_invite(self) unless @skip_notification super end def after_decline_invite - notification_service.decline_project_invite(self) + notification_service.decline_project_invite(self) unless @skip_notification super end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 2eb03b8ba5b..524e8bf6f86 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -37,6 +37,8 @@ class MergeRequest < ActiveRecord::Base belongs_to :source_project, foreign_key: :source_project_id, class_name: "Project" has_one :merge_request_diff, dependent: :destroy + has_many :approvals, dependent: :destroy + has_many :approvers, as: :target, dependent: :destroy after_create :create_merge_request_diff after_update :update_merge_request_diff @@ -134,6 +136,8 @@ class MergeRequest < ActiveRecord::Base scope :closed, -> { with_state(:closed) } scope :closed_and_merged, -> { with_states(:closed, :merged) } + participant :approvers_left + def self.reference_prefix '!' end @@ -412,6 +416,52 @@ class MergeRequest < ActiveRecord::Base locked_at.nil? || locked_at < (Time.now - 1.day) end + def approvals_left + approvals_required - approvals.count + end + + def approvers_left + user_ids = overall_approvers.map(&:user_id) - approvals.map(&:user_id) + User.where id: user_ids + end + + def approvals_required + target_project.approvals_before_merge + end + + def requires_approve? + approvals_required.nonzero? + end + + def overall_approvers + if approvers.any? + approvers + else + target_project.approvers + end + end + + def approved? + approvals_left.zero? + end + + def approved_by?(user) + approved_by_users.include?(user) + end + + def approved_by_users + approvals.map(&:user) + end + + def can_approve?(user) + approvers_left.include?(user) || + (any_approver_allowed? && !approved_by?(user)) + end + + def any_approver_allowed? + approvals_left > approvers_left.count + end + def has_ci? source_project.ci_service && commits.any? end @@ -434,6 +484,12 @@ class MergeRequest < ActiveRecord::Base end end + def approver_ids=(value) + value.split(",").map(&:strip).each do |user_id| + approvers.find_or_initialize_by(user_id: user_id, target_id: id) + end + end + def target_sha @target_sha ||= target_project. repository.commit(target_branch).sha @@ -472,6 +528,22 @@ class MergeRequest < ActiveRecord::Base end end + def source_sha_parent + source_project.repository.commit(commits.last.sha).parents.first.sha + end + + def ff_merge_possible? + target_sha == source_sha_parent + end + + def rebase_dir_path + Rails.root.join('tmp', 'rebase', source_project.id.to_s, id.to_s).to_s + end + + def rebase_in_progress? + File.exist?(rebase_dir_path) + end + def ci_commit if last_commit source_project.ci_commit(last_commit.id) diff --git a/app/models/project.rb b/app/models/project.rb index 9ea0d15497a..0c0991c426c 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -73,6 +73,7 @@ class Project < ActiveRecord::Base belongs_to :group, -> { where(type: Group) }, foreign_key: 'namespace_id' belongs_to :namespace + has_one :git_hook, dependent: :destroy has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event', foreign_key: 'project_id' # Project services @@ -89,6 +90,7 @@ class Project < ActiveRecord::Base has_one :asana_service, dependent: :destroy has_one :gemnasium_service, dependent: :destroy has_one :slack_service, dependent: :destroy + has_one :jenkins_service, dependent: :destroy has_one :buildkite_service, dependent: :destroy has_one :bamboo_service, dependent: :destroy has_one :teamcity_service, dependent: :destroy @@ -99,6 +101,7 @@ class Project < ActiveRecord::Base has_one :gitlab_issue_tracker_service, dependent: :destroy has_one :external_wiki_service, dependent: :destroy + has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id" has_one :forked_from_project, through: :forked_project_link @@ -121,12 +124,15 @@ class Project < ActiveRecord::Base has_many :deploy_keys, through: :deploy_keys_projects has_many :users_star_projects, dependent: :destroy has_many :starrers, through: :users_star_projects, source: :user + has_many :approvers, as: :target, dependent: :destroy has_many :ci_commits, dependent: :destroy, class_name: 'Ci::Commit', foreign_key: :gl_project_id has_many :ci_builds, through: :ci_commits, source: :builds, dependent: :destroy, class_name: 'Ci::Build' has_many :releases, dependent: :destroy has_many :lfs_objects_projects, dependent: :destroy has_many :lfs_objects, through: :lfs_objects_projects + has_many :project_group_links, dependent: :destroy + has_many :invited_groups, through: :project_group_links, source: :group has_one :import_data, dependent: :destroy, class_name: "ProjectImportData" has_one :gitlab_ci_project, dependent: :destroy, class_name: "Ci::Project", foreign_key: :gitlab_id @@ -160,6 +166,7 @@ class Project < ActiveRecord::Base validate :avatar_type, if: ->(project) { project.avatar.present? && project.avatar_changed? } validates :avatar, file_size: { maximum: 200.kilobytes.to_i } + validates :approvals_before_merge, numericality: true, allow_blank: true mount_uploader :avatar, AvatarUploader @@ -468,6 +475,14 @@ class Project < ActiveRecord::Base @ci_service ||= ci_services.select(&:activated?).first end + def jira_tracker? + issues_tracker.to_param == 'jira' + end + + def redmine_tracker? + issues_tracker.to_param == 'redmine' + end + def avatar_type unless self.avatar.image? self.errors.add :avatar, 'only images allowed' @@ -547,6 +562,11 @@ class Project < ActiveRecord::Base hooks.send(hooks_scope).each do |hook| hook.async_execute(data, hooks_scope.to_s) end + if group + group.hooks.send(hooks_scope).each do |hook| + hook.async_execute(data, hooks_scope.to_s) + end + end end def execute_services(data, hooks_scope = :push_hooks) @@ -743,6 +763,14 @@ class Project < ActiveRecord::Base merge_requests.where(source_project_id: self.id) end + def group_ldap_synced? + if group + group.ldap_synced? + else + false + end + end + def create_repository # Forked import is handled asynchronously unless forked? @@ -767,6 +795,24 @@ class Project < ActiveRecord::Base false end + def reference_issue_tracker? + default_issues_tracker? || jira_tracker_active? + end + + def jira_tracker_active? + jira_tracker? && jira_service.active + end + + def approver_ids=(value) + value.split(",").map(&:strip).each do |user_id| + approvers.find_or_create_by(user_id: user_id, target_id: id) + end + end + + def allowed_to_share_with_group? + !namespace.share_with_group_lock + end + def ci_commit(sha) ci_commits.find_by(sha: sha) end diff --git a/app/models/project_group_link.rb b/app/models/project_group_link.rb new file mode 100644 index 00000000000..e52a6bd7c84 --- /dev/null +++ b/app/models/project_group_link.rb @@ -0,0 +1,36 @@ +class ProjectGroupLink < ActiveRecord::Base + GUEST = 10 + REPORTER = 20 + DEVELOPER = 30 + MASTER = 40 + + belongs_to :project + belongs_to :group + + validates :project_id, presence: true + validates :group_id, presence: true + validates :group_id, uniqueness: { scope: [:project_id], message: "already shared with this group" } + validates :group_access, presence: true + validates :group_access, inclusion: { in: Gitlab::Access.values }, presence: true + validate :different_group + + def self.access_options + Gitlab::Access.options + end + + def self.default_access + DEVELOPER + end + + def human_access + self.class.access_options.key(self.group_access) + end + + private + + def different_group + if self.group && self.project && self.project.group == self.group + errors.add(:base, "Project cannot be shared with the project it is in.") + end + end +end diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb index 936e574cccd..c77a5229f79 100644 --- a/app/models/project_services/issue_tracker_service.rb +++ b/app/models/project_services/issue_tracker_service.rb @@ -30,6 +30,10 @@ class IssueTrackerService < Service false end + def create_cross_reference_note + # implement inside child + end + def issue_url(iid) self.issues_url.gsub(':id', iid.to_s) end diff --git a/app/models/project_services/jenkins_service.rb b/app/models/project_services/jenkins_service.rb new file mode 100644 index 00000000000..6b589d6b54d --- /dev/null +++ b/app/models/project_services/jenkins_service.rb @@ -0,0 +1,114 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# +require 'uri' + +class JenkinsService < CiService + prop_accessor :project_url + prop_accessor :multiproject_enabled + prop_accessor :pass_unstable + + validates :project_url, presence: true, if: :activated? + + delegate :execute, to: :service_hook, prefix: nil + + after_save :compose_service_hook, if: :activated? + + def compose_service_hook + hook = service_hook || build_service_hook + jenkins_url = project_url.sub(/job\/.*/, '') + hook.url = jenkins_url + "/gitlab/build_now" + hook.save + end + + def title + 'Jenkins CI' + end + + def description + 'An extendable open source continuous integration server' + end + + def help + 'You must have installed GitLab Hook plugin into Jenkins.' + end + + def to_param + 'jenkins' + end + + def fields + [ + { type: 'text', name: 'project_url', placeholder: 'Jenkins project URL like http://jenkins.example.com/job/my-project/' }, + { type: 'checkbox', name: 'multiproject_enabled', title: "Multi-project setup enabled?", + help: "Multi-project mode is configured in Jenkins Gitlab Hook plugin." }, + { type: 'checkbox', name: 'pass_unstable', title: 'Should unstable builds be treated as passing?', + help: 'Unstable builds will be treated as passing.' } + ] + end + + def multiproject_enabled? + self.multiproject_enabled == '1' + end + + def pass_unstable? + self.pass_unstable == '1' + end + + def build_page(sha, ref = nil) + if multiproject_enabled? && ref.present? + URI.encode("#{base_project_url}/#{project.name}_#{ref.gsub('/', '_')}/scm/bySHA1/#{sha}").to_s + else + "#{project_url}/scm/bySHA1/#{sha}" + end + end + + # When multi-project is enabled we need to have a different URL. Rather than + # relying on the user to provide the proper URL depending on multi-project + # we just parse the URL and make sure it's how we want it. + def base_project_url + url = URI.parse(project_url) + URI.join(url, '/job').to_s + end + + def commit_status(sha, ref = nil) + parsed_url = URI.parse(build_page(sha, ref)) + + if parsed_url.userinfo.blank? + response = HTTParty.get(build_page(sha, ref), verify: false) + else + get_url = build_page(sha, ref).gsub("#{parsed_url.userinfo}@", "") + auth = { + username: URI.decode(parsed_url.user), + password: URI.decode(parsed_url.password), + } + response = HTTParty.get(get_url, verify: false, basic_auth: auth) + end + + if response.code == 200 + # img.build-caption-status-icon for old jenkins version + src = Nokogiri.parse(response).css('img.build-caption-status-icon,.build-caption>img').first.attributes['src'].value + if src =~ /blue\.png$/ || (src =~ /yellow\.png/ && pass_unstable?) + 'success' + elsif src =~ /(red|aborted|yellow)\.png$/ + 'failed' + elsif src =~ /anime\.gif$/ + 'running' + else + 'pending' + end + else + :error + end + end +end diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index 35e30b1cb0b..e216f406e1c 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -19,9 +19,24 @@ # class JiraService < IssueTrackerService + include HTTParty include Gitlab::Application.routes.url_helpers - prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url + DEFAULT_API_VERSION = 2 + + prop_accessor :username, :password, :api_url, :jira_issue_transition_id, + :title, :description, :project_url, :issues_url, :new_issue_url + + before_validation :set_api_url, :set_jira_issue_transition_id + + before_update :reset_password + + def reset_password + # don't reset the password if a new one is provided + if api_url_changed? && !password_touched? + self.password = nil + end + end def help line1 = 'Setting `project_url`, `issues_url` and `new_issue_url` will '\ @@ -54,4 +69,228 @@ class JiraService < IssueTrackerService def to_param 'jira' end + + def fields + super.push( + { type: 'text', name: 'api_url', placeholder: 'https://jira.example.com/rest/api/2' }, + { type: 'text', name: 'username', placeholder: '' }, + { type: 'password', name: 'password', placeholder: '' }, + { type: 'text', name: 'jira_issue_transition_id', placeholder: '2' } + ) + end + + def execute(push, issue = nil) + if issue.nil? + # No specific issue, that means + # we just want to test settings + test_settings + else + close_issue(push, issue) + end + end + + def create_cross_reference_note(mentioned, noteable, author) + issue_name = mentioned.id + project = self.project + noteable_name = noteable.class.name.underscore.downcase + noteable_id = if noteable.is_a?(Commit) + noteable.id + else + noteable.iid + end + + entity_url = build_entity_url(noteable_name.to_sym, noteable_id) + + data = { + user: { + name: author.name, + url: resource_url(user_path(author)), + }, + project: { + name: project.path_with_namespace, + url: resource_url(namespace_project_path(project.namespace, project)) + }, + entity: { + name: noteable_name.humanize.downcase, + url: entity_url + } + } + + add_comment(data, issue_name) + end + + def test_settings + result = JiraService.get( + jira_api_test_url, + headers: { + 'Content-Type' => 'application/json', + 'Authorization' => "Basic #{auth}" + } + ) + + case result.code + when 201, 200 + Rails.logger.info("#{self.class.name} SUCCESS #{result.code}: Successfully connected to #{api_url}.") + true + else + Rails.logger.info("#{self.class.name} ERROR #{result.code}: #{result.parsed_response}") + false + end + rescue Errno::ECONNREFUSED => e + Rails.logger.info "#{self.class.name} ERROR: #{e.message}. API URL: #{api_url}." + false + end + + private + + def build_api_url_from_project_url + server = URI(project_url) + default_ports = [["http",80],["https",443]].include?([server.scheme,server.port]) + server_url = "#{server.scheme}://#{server.host}" + server_url.concat(":#{server.port}") unless default_ports + "#{server_url}/rest/api/#{DEFAULT_API_VERSION}" + rescue + "" # looks like project URL was not valid + end + + def set_api_url + self.api_url = build_api_url_from_project_url if self.api_url.blank? + end + + def set_jira_issue_transition_id + self.jira_issue_transition_id ||= "2" + end + + def close_issue(entity, issue) + commit_id = if entity.is_a?(Commit) + entity.id + elsif entity.is_a?(MergeRequest) + entity.last_commit.id + end + commit_url = build_entity_url(:commit, commit_id) + + # Depending on the JIRA project's workflow, a comment during transition + # may or may not be allowed. Split the operation in to two calls so the + # comment always works. + transition_issue(issue) + add_issue_solved_comment(issue, commit_id, commit_url) + end + + def transition_issue(issue) + message = { + transition: { + id: jira_issue_transition_id + } + } + send_message(close_issue_url(issue.iid), message.to_json) + end + + def add_issue_solved_comment(issue, commit_id, commit_url) + comment = { + body: "Issue solved with [#{commit_id}|#{commit_url}]." + } + + send_message(comment_url(issue.iid), comment.to_json) + end + + def add_comment(data, issue_name) + url = comment_url(issue_name) + user_name = data[:user][:name] + user_url = data[:user][:url] + entity_name = data[:entity][:name] + entity_url = data[:entity][:url] + project_name = data[:project][:name] + + message = { + body: "[#{user_name}|#{user_url}] mentioned this issue in [a #{entity_name} of #{project_name}|#{entity_url}]." + } + + unless existing_comment?(issue_name, message[:body]) + send_message(url, message.to_json) + end + end + + + def auth + require 'base64' + Base64.urlsafe_encode64("#{self.username}:#{self.password}") + end + + def send_message(url, message) + result = JiraService.post( + url, + body: message, + headers: { + 'Content-Type' => 'application/json', + 'Authorization' => "Basic #{auth}" + } + ) + + message = case result.code + when 201, 200, 204 + "#{self.class.name} SUCCESS #{result.code}: Successfully posted to #{url}." + when 401 + "#{self.class.name} ERROR 401: Unauthorized. Check the #{self.username} credentials and JIRA access permissions and try again." + else + "#{self.class.name} ERROR #{result.code}: #{result.parsed_response}" + end + + Rails.logger.info(message) + message + rescue URI::InvalidURIError, Errno::ECONNREFUSED => e + Rails.logger.info "#{self.class.name} ERROR: #{e.message}. Hostname: #{url}." + end + + def existing_comment?(issue_name, new_comment) + result = JiraService.get( + comment_url(issue_name), + headers: { + 'Content-Type' => 'application/json', + 'Authorization' => "Basic #{auth}" + } + ) + + case result.code + when 201, 200 + existing_comments = JSON.parse(result.body)['comments'] + + if existing_comments.present? + return existing_comments.map { |comment| comment['body'].include?(new_comment) }.any? + end + end + + false + rescue JSON::ParserError + false + end + + def resource_url(resource) + "#{Settings.gitlab['url'].chomp("/")}#{resource}" + end + + def build_entity_url(entity_name, entity_id) + resource_url( + polymorphic_url( + [ + self.project.namespace.becomes(Namespace), + self.project, + entity_name + ], + id: entity_id, + routing_type: :path + ) + ) + end + + def close_issue_url(issue_name) + "#{self.api_url}/issue/#{issue_name}/transitions" + end + + def comment_url(issue_name) + "#{self.api_url}/issue/#{issue_name}/comment" + end + + def jira_api_test_url + "#{self.api_url}/myself" + end end diff --git a/app/models/project_team.rb b/app/models/project_team.rb index 9f380a382cb..3bf9898460a 100644 --- a/app/models/project_team.rb +++ b/app/models/project_team.rb @@ -44,6 +44,8 @@ class ProjectTeam end def add_users(users, access, current_user = nil) + return false if group_member_lock + ProjectMember.add_users_into_projects( [project.id], users, @@ -160,7 +162,27 @@ class ProjectTeam end end - access.max + if project.invited_groups.any? && project.allowed_to_share_with_group? + access << max_invited_level(user_id) + end + + access.compact.max + end + + + def max_invited_level(user_id) + project.project_group_links.map do |group_link| + invited_group = group_link.group + access = invited_group.group_members.find_by(user_id: user_id).try(:access_field) + + # If group member has higher access level we should restrict it + # to max allowed access level + if access && access > group_link.group_access + access = group_link.group_access + end + + access + end.compact.max end private @@ -168,6 +190,35 @@ class ProjectTeam def fetch_members(level = nil) project_members = project.project_members group_members = group ? group.group_members : [] + invited_members = [] + + if project.invited_groups.any? && project.allowed_to_share_with_group? + project.project_group_links.each do |group_link| + invited_group = group_link.group + im = invited_group.group_members + + if level + int_level = GroupMember.access_level_roles[level.to_s.singularize.titleize] + + # Skip group members if we ask for masters + # but max group access is developers + next if int_level > group_link.group_access + + # If we ask for developers and max + # group access is developers we need to provide + # both group master, developers as devs + if int_level == group_link.group_access + im.where("access_level >= ?)", group_link.group_access) + else + im.send(level) + end + end + + invited_members << im + end + + invited_members = invited_members.flatten.compact + end if level project_members = project_members.send(level) @@ -175,6 +226,7 @@ class ProjectTeam end user_ids = project_members.pluck(:user_id) + user_ids.push(*invited_members.map(&:user_id)) if invited_members.any? user_ids.push(*group_members.pluck(:user_id)) if group User.where(id: user_ids) @@ -183,4 +235,8 @@ class ProjectTeam def group project.group end + + def group_member_lock + group && group.membership_lock + end end diff --git a/app/models/repository.rb b/app/models/repository.rb index f76b770e867..54e4ed55526 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -458,6 +458,18 @@ class Repository end end + def ff_merge(user, source_sha, target_branch, options = {}) + our_commit = rugged.branches[target_branch].target + their_commit = rugged.lookup(source_sha) + + raise "Invalid merge target" if our_commit.nil? + raise "Invalid merge source" if their_commit.nil? + + commit_with_hooks(user, target_branch) do |ref| + source_sha + end + end + def merge(user, source_sha, target_branch, options = {}) our_commit = rugged.branches[target_branch].target their_commit = rugged.lookup(source_sha) diff --git a/app/models/service.rb b/app/models/service.rb index d610abd1683..6d928093dad 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -173,6 +173,7 @@ class Service < ActiveRecord::Base gitlab_ci hipchat irker + jenkins jira pivotaltracker pushover diff --git a/app/models/user.rb b/app/models/user.rb index 61abea1f6ea..8c8af841018 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -132,6 +132,7 @@ class User < ActiveRecord::Base has_many :assigned_issues, dependent: :destroy, foreign_key: :assignee_id, class_name: "Issue" has_many :assigned_merge_requests, dependent: :destroy, foreign_key: :assignee_id, class_name: "MergeRequest" has_many :oauth_applications, class_name: 'Doorkeeper::Application', as: :owner, dependent: :destroy + has_many :approvals, dependent: :destroy has_one :abuse_report, dependent: :destroy has_many :ci_builds, dependent: :nullify, class_name: 'Ci::Build' @@ -208,6 +209,8 @@ class User < ActiveRecord::Base scope :active, -> { with_state(:active) } scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all } scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM members)') } + scope :subscribed_for_admin_email, -> { where(admin_email_unsubscribed_at: nil) } + scope :ldap, -> { joins(:identities).where('identities.provider LIKE ?', 'ldap%') } scope :with_two_factor, -> { where(two_factor_enabled: true) } scope :without_two_factor, -> { where(two_factor_enabled: false) } @@ -248,6 +251,10 @@ class User < ActiveRecord::Base User.find_by_sql([sql, { email: email }]).first end + def existing_member?(email) + User.where(email: email).any? || Email.where(email: email).any? + end + def filter(filter_name) case filter_name when 'admins' @@ -291,6 +298,27 @@ class User < ActiveRecord::Base User.new(attrs) end + def non_ldap + joins('LEFT JOIN identities ON identities.user_id = users.id'). + where('identities.provider IS NULL OR identities.provider NOT LIKE ?', 'ldap%') + end + + def clean_username(username) + username.gsub!(/@.*\z/, "") + username.gsub!(/\.git\z/, "") + username.gsub!(/\A-/, "") + username.gsub!(/[^a-zA-Z0-9_\-\.]/, "") + + counter = 0 + base = username + while User.by_login(username).present? || Namespace.by_path(username).present? + counter += 1 + username = "#{base}#{counter}" + end + + username + end + def reference_prefix '@' end @@ -402,6 +430,7 @@ class User < ActiveRecord::Base project_ids = personal_projects.pluck(:id) project_ids.push(*groups_projects.pluck(:id)) project_ids.push(*projects.pluck(:id).uniq) + project_ids.push(*groups.joins(:shared_projects).pluck(:project_id)) end end @@ -599,7 +628,7 @@ class User < ActiveRecord::Base if !Gitlab.config.ldap.enabled false elsif ldap_user? - !last_credential_check_at || (last_credential_check_at + 1.hour) < Time.now + !last_credential_check_at || (last_credential_check_at + Gitlab.config.ldap['sync_time']) < Time.now else false end @@ -708,6 +737,10 @@ class User < ActiveRecord::Base SystemHooksService.new end + def admin_unsubscribe! + update_column :admin_email_unsubscribed_at, Time.now + end + def starred?(project) starred_projects.exists?(project) end diff --git a/app/services/audit_event_service.rb b/app/services/audit_event_service.rb index a7f090655e1..df6df4e5a6b 100644 --- a/app/services/audit_event_service.rb +++ b/app/services/audit_event_service.rb @@ -3,6 +3,67 @@ class AuditEventService @author, @entity, @details = author, entity, details end + def for_member(member) + action = @details[:action] + old_access_level = @details[:old_access_level] + user_id = member.id + user_name = member.user.name + + @details = + case action + when :destroy + { + remove: "user_access", + target_id: user_id, + target_type: "User", + target_details: user_name, + } + when :create + { + add: "user_access", + as: Gitlab::Access.options_with_owner.key(member.access_level.to_i), + target_id: user_id, + target_type: "User", + target_details: user_name, + } + when :update + { + change: "access_level", + from: old_access_level, + to: member.human_access, + target_id: user_id, + target_type: "User", + target_details: user_name, + } + end + + self + end + + def for_deploy_key(key_title) + action = @details[:action] + + @details = + case action + when :destroy + { + remove: "deploy_key", + target_id: key_title, + target_type: "DeployKey", + target_details: key_title, + } + when :create + { + add: "deploy_key", + target_id: key_title, + target_type: "DeployKey", + target_details: key_title, + } + end + + self + end + def for_authentication @details = { with: @details[:with], diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb index 008833eed80..80d45e1848a 100644 --- a/app/services/files/base_service.rb +++ b/app/services/files/base_service.rb @@ -71,5 +71,9 @@ module Files raise_error("Something went wrong when we tried to create #{@target_branch} for you") end end + + def git_hook + project.git_hook + end end end diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index ccb6b97858c..f9beeeb569d 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -99,7 +99,7 @@ class GitPushService Issues::CloseService.new(project, authors[commit], {}).execute(issue, commit) end end - + commit.create_cross_references!(authors[commit], closed_issues) end end diff --git a/app/services/issues/close_service.rb b/app/services/issues/close_service.rb index 3d85f97b7e5..a1a20e47681 100644 --- a/app/services/issues/close_service.rb +++ b/app/services/issues/close_service.rb @@ -1,6 +1,11 @@ module Issues class CloseService < Issues::BaseService def execute(issue, commit = nil) + if project.jira_tracker? && project.jira_service.active + project.jira_service.execute(commit, issue) + return issue + end + if project.default_issues_tracker? && issue.close event_service.close_issue(issue, current_user) create_note(issue, commit) diff --git a/app/services/ldap_group_reset_service.rb b/app/services/ldap_group_reset_service.rb new file mode 100644 index 00000000000..a1eafac9300 --- /dev/null +++ b/app/services/ldap_group_reset_service.rb @@ -0,0 +1,16 @@ +class LdapGroupResetService + def execute(group, current_user) + # Only for ldap connected users + # reset last_credential_check_at to force LDAP::Access::update_permissions + # set Gitlab::Access::Guest to later on upgrade the access of a user + + # trigger the lowest access possible for all LDAP connected users + group.members.with_ldap_dn.map do |member| + # don't unauthorize the current user + next if current_user == member.user + member.update_attribute :access_level, Gitlab::Access::GUEST + end + + group.users.ldap.update_all last_credential_check_at: nil + end +end diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb index a9b29f9654d..474b2f04b6d 100644 --- a/app/services/merge_requests/build_service.rb +++ b/app/services/merge_requests/build_service.rb @@ -61,6 +61,11 @@ module MergeRequests merge_request.title = merge_request.source_branch.titleize.humanize end + # Set MR description based on project template + if merge_request.target_project.merge_requests_template.present? + merge_request.description = merge_request.target_project.merge_requests_template + end + merge_request end diff --git a/app/services/merge_requests/ff_merge_service.rb b/app/services/merge_requests/ff_merge_service.rb new file mode 100644 index 00000000000..c8035e9eba3 --- /dev/null +++ b/app/services/merge_requests/ff_merge_service.rb @@ -0,0 +1,38 @@ +module MergeRequests + # MergeService class + # + # Do git merge and in case of success + # mark merge request as merged and execute all hooks and notifications + # Executed when you do merge via GitLab UI + # + class FfMergeService < MergeRequests::BaseService + attr_reader :merge_request + + def execute(merge_request) + @merge_request = merge_request + + unless @merge_request.ff_merge_possible? + return error('Merge request is not mergeable') + end + + merge_request.in_locked_state do + if update_head + after_merge + success + else + error('Can not merge changes') + end + end + end + + private + + def update_head + repository.ff_merge(current_user, merge_request.source_sha, merge_request.target_branch) + end + + def after_merge + MergeRequests::PostMergeService.new(project, current_user).execute(merge_request) + end + end +end diff --git a/app/services/merge_requests/rebase_service.rb b/app/services/merge_requests/rebase_service.rb new file mode 100644 index 00000000000..d0f09a99428 --- /dev/null +++ b/app/services/merge_requests/rebase_service.rb @@ -0,0 +1,87 @@ +module MergeRequests + # MergeService class + # + # Do git merge and in case of success + # mark merge request as merged and execute all hooks and notifications + # Executed when you do merge via GitLab UI + # + class RebaseService < MergeRequests::BaseService + include Gitlab::Popen + + attr_reader :merge_request + + def execute(merge_request) + @merge_request = merge_request + + if rebase + success + else + error('Failed to rebase. Should be done manually') + end + end + + def rebase + Gitlab::ShellEnv.set_env(current_user) + + if merge_request.rebase_in_progress? + log('Rebase task canceled: Another rebase is already in progress') + return false + end + + # Clone + output, status = popen(%W(git clone -b #{merge_request.source_branch} -- #{source_project.repository.path_to_repo} #{tree_path})) + + unless status.zero? + log('Failed to clone repository for rebase:') + log(output) + return false + end + + # Rebase + output, status = popen(%W(git pull --rebase #{target_project.repository.path_to_repo} #{merge_request.target_branch}), tree_path) + + unless status.zero? + log('Failed to rebase branch:') + log(output) + return false + end + + # Push + output, status = popen(%W(git push -f origin #{merge_request.source_branch}), tree_path) + + unless status.zero? + log('Failed to push rebased branch:') + log(output) + return false + end + + true + rescue => ex + log('Failed to rebase branch:') + log(ex.message) + ensure + clean_dir + Gitlab::ShellEnv.reset_env + end + + def source_project + @source_project ||= merge_request.source_project + end + + def target_project + @target_project ||= merge_request.target_project + end + + def tree_path + @tree_path ||= merge_request.rebase_dir_path + end + + def log(message) + Gitlab::GitLogger.error(message) + end + + def clean_dir + FileUtils.rm_rf(tree_path) if File.exist?(tree_path) + end + end +end diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb index e180edb4bf3..08f1aa3af09 100644 --- a/app/services/merge_requests/refresh_service.rb +++ b/app/services/merge_requests/refresh_service.rb @@ -19,6 +19,7 @@ module MergeRequests comment_mr_with_commits execute_mr_web_hooks + reset_approvals_for_merge_requests true end @@ -76,6 +77,20 @@ module MergeRequests end end + # Reset approvals for merge request + # Note: we should reset approvals for merge requests from forks too + def reset_approvals_for_merge_requests + if @project.approvals_before_merge.nonzero? && @project.reset_approvals_on_push + merge_requests = @project.merge_requests.opened.where(source_branch: @branch_name).to_a + merge_requests += @fork_merge_requests.where(source_branch: @branch_name).to_a + merge_requests = filter_merge_requests(merge_requests) + + merge_requests.each do |merge_request| + merge_request.approvals.destroy_all + end + end + end + def find_new_commits if branch_added? @commits = [] diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb index 5b84527eccf..cef76a51cba 100644 --- a/app/services/projects/create_service.rb +++ b/app/services/projects/create_service.rb @@ -97,6 +97,13 @@ module Projects if @project.import? @project.import_start end + + predefined_git_hook = GitHook.find_by(is_sample: true) + + if predefined_git_hook + git_hook = predefined_git_hook.dup.tap{ |gh| gh.is_sample = false } + project.git_hook = git_hook + end end end end diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 708c2f00486..7f245c404a7 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -227,7 +227,11 @@ class SystemNoteService note_options.merge!(noteable: noteable) end - create_note(note_options) + if noteable.is_a?(ExternalIssue) + noteable.project.issues_tracker.create_cross_reference_note(noteable, mentioner, author) + else + create_note(note_options) + end end def self.cross_reference?(note_text) @@ -245,7 +249,7 @@ class SystemNoteService # # Returns Boolean def self.cross_reference_disallowed?(noteable, mentioner) - return true if noteable.is_a?(ExternalIssue) + return true if noteable.is_a?(ExternalIssue) && !noteable.project.jira_tracker_active? return false unless mentioner.is_a?(MergeRequest) return false unless noteable.is_a?(Commit) @@ -278,6 +282,22 @@ class SystemNoteService notes.count > 0 end + # Called when the merge request is approved by user + # + # noteable - Noteable object + # user - User performing approve + # + # Example Note text: + # + # "Approved this merge request" + # + # Returns the created Note object + def self.approve_mr(noteable, user) + body = "Approved this merge request" + + create_note(noteable: noteable, project: noteable.project, author: user, note: body) + end + private def self.create_note(args = {}) diff --git a/app/services/test_hook_service.rb b/app/services/test_hook_service.rb index e85e58751e7..5b447909def 100644 --- a/app/services/test_hook_service.rb +++ b/app/services/test_hook_service.rb @@ -1,6 +1,16 @@ class TestHookService def execute(hook, current_user) - data = Gitlab::PushDataBuilder.build_sample(hook.project, current_user) + data = Gitlab::PushDataBuilder.build_sample(project(hook), current_user) hook.execute(data, 'push_hooks') end + + private + + def project(hook) + if hook.is_a? GroupHook + hook.group.first_non_empty_project + else + hook.project + end + end end diff --git a/app/views/admin/appearances/_form.html.haml b/app/views/admin/appearances/_form.html.haml new file mode 100644 index 00000000000..b719377c9bf --- /dev/null +++ b/app/views/admin/appearances/_form.html.haml @@ -0,0 +1,58 @@ += form_for @appearance, url: admin_appearances_path, html: { class: 'form-horizontal'} do |f| + - if @appearance.errors.any? + .alert.alert-danger + - @appearance.errors.full_messages.each do |msg| + %p= msg + + %fieldset.sign-in + %legend + Sign in/Sign up pages: + .form-group + = f.label :title, class: 'control-label' + .col-sm-10 + = f.text_field :title, class: "form-control" + .form-group + = f.label :description, class: 'control-label' + .col-sm-10 + = f.text_area :description, class: "form-control", rows: 10 + .hint + Description parsed with #{link_to "GitLab Flavored Markdown", help_page_path('markdown', 'markdown'), target: '_blank'}. + .form-group + = f.label :logo, class: 'control-label' + .col-sm-10 + - if @appearance.logo? + = image_tag @appearance.logo_url, class: 'appearance-logo-preview' + - if @appearance.persisted? + %br + = link_to 'Remove logo', logo_admin_appearances_path, data: { confirm: "Logo will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-small remove-logo" + %hr + = f.hidden_field :logo_cache + = f.file_field :logo, class: "" + .hint + Maximum logo size is 1MB, page optimized for logo size 640x360px + + %fieldset.app_logo + %legend + Navigation bar: + .form-group + = f.label :light_logo, 'Header logo', class: 'control-label' + .col-sm-10 + - if @appearance.light_logo? + = image_tag @appearance.light_logo_url, class: 'appearance-light-logo-preview' + - if @appearance.persisted? + %br + = link_to 'Remove header logo', header_logos_admin_appearances_path, data: { confirm: "Header logo will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-small remove-logo" + %hr + = f.hidden_field :light_logo_cache + = f.file_field :light_logo, class: "" + .hint + Maximum size is 1MB, page optimized for logo size 72x72px + + .form-actions + = f.submit 'Save', class: 'btn btn-save' + - if @appearance.persisted? + = link_to 'Preview last save', preview_admin_appearances_path, class: 'btn', target: '_blank' + + - if @appearance.updated_at + %span.pull-right + Last edit #{time_ago_with_tooltip(@appearance.updated_at)} diff --git a/app/views/admin/appearances/preview.html.haml b/app/views/admin/appearances/preview.html.haml new file mode 100644 index 00000000000..dd4a64e80bc --- /dev/null +++ b/app/views/admin/appearances/preview.html.haml @@ -0,0 +1,29 @@ +- page_title "Preview | Appearance" +%h3.page-title + Appearance settings - Preview +%hr + +.ui-box + .title + Sign-in page + %div + .login-page + .container + .content + .login-title + %h1= brand_title + %hr + .container + .content + .row + .col-sm-7 + .brand-image + = brand_image + .brand_text + = brand_text + .col-sm-4 + .login-box + %h3.page-title Sign in + = text_field_tag :login, nil, class: "form-control top", placeholder: "Username or Email" + = password_field_tag :password, nil, class: "form-control bottom", placeholder: "Password" + = button_tag "Sign in", class: "btn-create btn" diff --git a/app/views/admin/appearances/show.html.haml b/app/views/admin/appearances/show.html.haml new file mode 100644 index 00000000000..089e8e4cb7a --- /dev/null +++ b/app/views/admin/appearances/show.html.haml @@ -0,0 +1,7 @@ +- page_title "Appearance" +%h3.page-title + Appearance settings +%p.light + You can modify the look and feel of GitLab here + += render 'form' diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index ddaf0e0e8ff..a6215960269 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -125,6 +125,11 @@ = f.text_area :sign_in_text, class: 'form-control', rows: 4 .help-block Markdown enabled .form-group + = f.label :help_text, class: 'control-label' + .col-sm-10 + = f.text_area :help_text, class: 'form-control', rows: 4 + .help-block Markdown enabled + .form-group = f.label :help_page_text, class: 'control-label col-sm-2' .col-sm-10 = f.text_area :help_page_text, class: 'form-control', rows: 4 diff --git a/app/views/admin/emails/show.html.haml b/app/views/admin/emails/show.html.haml new file mode 100644 index 00000000000..a2f421a8536 --- /dev/null +++ b/app/views/admin/emails/show.html.haml @@ -0,0 +1,23 @@ +- page_title "Email Notification" +%h3.page-title + Send email notification +%p.light + You can notify the app / group or a project by sending them an email notification + += form_tag admin_email_path, class: 'form-horizontal', id: 'new-admin-email' do + .form-group + %label.control-label{for: :subject} Subject + .col-sm-10 + = text_field_tag :subject, '', class: 'form-control', required: true + + .form-group + %label.control-label{for: :body} Body + .col-sm-10 + = text_area_tag :body, '', class: 'form-control', rows: 15, required: true + + .form-group + %label.control-label{for: :recipients} Recipient group + .col-sm-10 + = admin_email_select_tag(:recipients) + .form-actions + = submit_tag 'Send message', class: 'btn btn-create' diff --git a/app/views/admin/git_hooks/index.html.haml b/app/views/admin/git_hooks/index.html.haml new file mode 100644 index 00000000000..276de761ed2 --- /dev/null +++ b/app/views/admin/git_hooks/index.html.haml @@ -0,0 +1,14 @@ +- page_title "Git Hooks" +%h3.page-title + Pre-defined git hooks. +%p.light + Rules that define what git pushes are accepted for this project. All newly created projects will use this settings. + +%hr.clearfix + += form_for [:admin, @git_hook], html: { class: 'form-horizontal' } do |f| + -if @git_hook.errors.any? + .alert.alert-danger + - @git_hook.errors.full_messages.each do |msg| + %p= msg + = render "shared/git_hooks_form", f: f diff --git a/app/views/admin/groups/_form.html.haml b/app/views/admin/groups/_form.html.haml index 8de2ba74a79..f2235800640 100644 --- a/app/views/admin/groups/_form.html.haml +++ b/app/views/admin/groups/_form.html.haml @@ -24,3 +24,7 @@ = f.submit 'Save changes', class: "btn btn-primary" = link_to 'Cancel', admin_group_path(@group), class: "btn btn-cancel" +- if @group.persisted? + %h3.page-title Linked LDAP groups + = render 'ldap_group_links/form', group: @group + = render 'ldap_group_links/ldap_group_links', group: @group diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml index 296497a4cd4..901eabeb3b9 100644 --- a/app/views/admin/groups/show.html.haml +++ b/app/views/admin/groups/show.html.haml @@ -33,6 +33,17 @@ = @group.created_at.stamp("March 1, 1999") .panel.panel-default + .panel-heading Linked LDAP groups + %ul.well-list + - if @group.ldap_group_links.any? + - @group.ldap_group_links.each do |ldap_group_link| + %li + cn: + %strong= ldap_group_link.cn + as + %strong= ldap_group_link.human_access + + .panel.panel-default .panel-heading %h3.panel-title Projects @@ -50,6 +61,22 @@ .panel-footer = paginate @projects, param_name: 'projects_page', theme: 'gitlab' + - if @group.shared_projects.any? + .panel.panel-default + .panel-heading + Projects shared with #{@group.name} + %span.badge + #{@group.shared_projects.count} + %ul.well-list + - @group.shared_projects.sort_by(&:name).each do |project| + %li + %strong + = link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project] + %span.label.label-gray + = repository_size(project) + %span.pull-right.light + %span.monospace= project.path_with_namespace + ".git" + .col-md-6 - if can?(current_user, :admin_group_member, @group) .panel.panel-default @@ -62,7 +89,7 @@ = form_tag members_update_admin_group_path(@group), id: "new_project_member", class: "bulk_import", method: :put do %div - = users_select_tag(:user_ids, multiple: true, email_user: true, scope: :all) + = users_select_tag(:user_ids, multiple: true, email_user: true, skip_ldap: @group.ldap_synced?, scope: :all) %div.prepend-top-10 = select_tag :access_level, options_for_select(GroupMember.access_level_roles), class: "project-access-select select2" %hr diff --git a/app/views/admin/licenses/new.html.haml b/app/views/admin/licenses/new.html.haml new file mode 100644 index 00000000000..da5c6a7cc40 --- /dev/null +++ b/app/views/admin/licenses/new.html.haml @@ -0,0 +1,21 @@ +- page_title "Upload License" +%h3.page-title Upload License + +%p.light + To #{License.current ? "continue" : "start"} using GitLab Enterprise Edition, upload the <code>.gitlab-license</code> file you have received from GitLab B.V.. + +%hr += form_for @license, url: admin_license_path, html: { multipart: true, class: 'form-horizontal fieldset-form' } do |f| + - if @license.errors.any? + #error_explanation + .alert.alert-danger + - @license.errors.full_messages.each do |msg| + %p= msg + + .form-group + = f.label :data_file, "License", class: 'control-label col-sm-2' + .col-sm-10 + = f.file_field :data_file, accept: ".gitlab-license,.gitlab_license,.txt" + + .form-actions + = f.submit 'Upload license', class: 'btn btn-primary' diff --git a/app/views/admin/licenses/show.html.haml b/app/views/admin/licenses/show.html.haml new file mode 100644 index 00000000000..6e28fbc22f0 --- /dev/null +++ b/app/views/admin/licenses/show.html.haml @@ -0,0 +1,133 @@ +- page_title "License" +%h3.page-title + Your License + = link_to 'Upload New License', new_admin_license_path, class: "btn btn-new pull-right" + +%hr + +.row + .col-md-6 + .panel.panel-default + .panel-heading + Licensed to + %ul.well-list + - @license.licensee.each do |label, value| + %li + %span.light #{label}: + %strong= value + + .panel.panel-default + .panel-heading + Details + %ul.well-list + %li + %span.light Uploaded: + %strong= time_ago_with_tooltip @license.created_at + %li + %span.light Started: + %strong= time_ago_with_tooltip @license.starts_at + %li + %span.light + - if @license.expired? + Expired: + - else + Expires: + %strong + - if @license.will_expire? + = time_ago_with_tooltip @license.expires_at + - else + Never + + - if @license.expired? + %span.label.label-danger.pull-right + %strong Expired + + .panel.panel-default + .panel-heading + Users + %ul.well-list + %li + %span.light License limit: + %strong + - if @license.restricted?(:active_user_count) + - restricted = @license.restrictions[:active_user_count] + #{number_with_delimiter restricted} #{"users".pluralize(restricted)} + - else + Unlimited + %li + %span.light Current active users: + %strong + - current = User.active.count + #{number_with_delimiter current} #{"users".pluralize(current)} + + - if restricted && current > restricted + %span.label.label-danger.pull-right + %strong + Exceeds license limit + + - date_range = (Date.today - 1.year)..Date.today + - historical = HistoricalData.during(date_range).maximum(:active_user_count) + - if historical + %li + %span.light Maximum active users: + %strong + #{number_with_delimiter historical} #{"users".pluralize(historical)} + + - if restricted && historical > restricted + %span.label.label-danger.pull-right + %strong + Exceeds license limit + + .col-md-6 + .panel.panel-info + .panel-heading + Download license + .panel-body + %p Your license will be included in your GitLab backup and will survive upgrades, so in normal usage you should never need to re-upload your <code>.gitlab-license</code> file. + %p Still, we recommend keeping a backup saved somewhere. Otherwise, if you ever need it and have lost it, you will need to request GitLab B.V. to send it to you again. + %br + = link_to 'Download license', download_admin_license_path, class: "btn btn-info" + + + .panel.panel-danger + .panel-heading + Remove license + .panel-body + %p If you remove this license, GitLab will fall back on the previous license, if any. + %p If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded. + %br + = link_to 'Remove license', admin_license_path, data: { confirm: "Are you sure you want to remove the license?" }, method: :delete, class: "btn btn-remove" + +- if @previous_licenses.any? + %h4 License History + + .panel.panel-default#license_history + %table.table + %thead.panel-heading + %tr + - @license.licensee.keys.each do |label| + %th= label + %th Uploaded at + %th Started at + %th Expired at + %th Active users + %tbody + - @previous_licenses.each do |license| + %tr + - @license.licensee.keys.each do |label| + %td= license.licensee[label] + %td + %span + = license.created_at + %td + %span + = license.starts_at + %td + %span + = license.expires_at || "Never" + %td + %span + - if license.restricted?(:active_user_count) + #{license.restrictions[:active_user_count]} users + - else + Unlimited diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml index bc08458312c..eb687fe84bb 100644 --- a/app/views/admin/users/index.html.haml +++ b/app/views/admin/users/index.html.haml @@ -44,6 +44,7 @@ .panel-heading Users (#{@users.total_count}) .panel-head-actions + = link_to 'Send email to users', admin_email_path, class: 'btn btn-sm' .dropdown.inline %a.dropdown-toggle.btn.btn-sm{href: '#', "data-toggle" => "dropdown"} %span.light sort: diff --git a/app/views/audit_events/_event_table.html.haml b/app/views/audit_events/_event_table.html.haml new file mode 100644 index 00000000000..a0814531ec3 --- /dev/null +++ b/app/views/audit_events/_event_table.html.haml @@ -0,0 +1,19 @@ +- if defined?(events) + %table.table#audits + %thead + %tr + %th Author + %th Action + %th Target + %th At + + %tbody + - events.each do |event| + %tr + %td #{event.author_name} + %td + %span + #{raw human_text(event.details)} + %td #{event.details[:target_details]} + %td #{event.created_at} + = paginate events, theme: "gitlab" diff --git a/app/views/audit_events/group_log.html.haml b/app/views/audit_events/group_log.html.haml new file mode 100644 index 00000000000..92a10e355e8 --- /dev/null +++ b/app/views/audit_events/group_log.html.haml @@ -0,0 +1,5 @@ +- page_title "Audit Events" +%h3.page-title Group Audit Events +%p.light Events in #{@group.name} + += render 'event_table', events: @events diff --git a/app/views/audit_events/project_log.html.haml b/app/views/audit_events/project_log.html.haml new file mode 100644 index 00000000000..5fa7b705421 --- /dev/null +++ b/app/views/audit_events/project_log.html.haml @@ -0,0 +1,5 @@ +- page_title "Audit Events" +%h3.page-title Project Audit Events +%p.light Events in #{@project.path_with_namespace} + += render 'event_table', events: @events diff --git a/app/views/devise/sessions/_new_kerberos.html.haml b/app/views/devise/sessions/_new_kerberos.html.haml new file mode 100644 index 00000000000..73b5130675e --- /dev/null +++ b/app/views/devise/sessions/_new_kerberos.html.haml @@ -0,0 +1,5 @@ += form_tag(user_omniauth_callback_path(provider), id: 'new_kerberos_user' ) do + = text_field_tag :username, nil, {class: "form-control top", placeholder: "Kerberos Login", autofocus: "autofocus"} + = password_field_tag :password, nil, {class: "form-control bottom", placeholder: "Password"} + %br/ + = button_tag "Kerberos Sign in", class: "btn-save btn"
\ No newline at end of file diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml index dbc8eda6196..d155f218917 100644 --- a/app/views/devise/sessions/new.html.haml +++ b/app/views/devise/sessions/new.html.haml @@ -4,7 +4,7 @@ = render 'devise/shared/signin_box' -# Omniauth fits between signin/ldap signin and signup and does not have a surrounding box - - if Gitlab.config.omniauth.enabled && devise_mapping.omniauthable? + - if Gitlab.config.omniauth.enabled && devise_mapping.omniauthable? && button_based_providers.any? .clearfix.prepend-top-20 = render 'devise/shared/omniauth_box' diff --git a/app/views/devise/shared/_signin_box.html.haml b/app/views/devise/shared/_signin_box.html.haml index 41ad2c231d4..8492cbb1d25 100644 --- a/app/views/devise/shared/_signin_box.html.haml +++ b/app/views/devise/shared/_signin_box.html.haml @@ -8,25 +8,33 @@ .login-body - if form_based_providers.any? %ul.nav.nav-tabs + - if kerberos_enabled? + %li{class: (:active unless crowd_enabled? || ldap_enabled?)} + = link_to "Kerberos", "#tab-kerberos", 'data-toggle' => 'tab' - if crowd_enabled? %li.active = link_to "Crowd", "#tab-crowd", 'data-toggle' => 'tab' - - @ldap_servers.each_with_index do |server, i| - %li{class: (:active if i.zero? && !crowd_enabled?)} - = link_to server['label'], "#tab-#{server['provider_name']}", 'data-toggle' => 'tab' + - if ldap_enabled? + - @ldap_servers.each_with_index do |server, i| + %li{class: (:active if i.zero? && !crowd_enabled?)} + = link_to server['label'], "#tab-#{server['provider_name']}", 'data-toggle' => 'tab' - if signin_enabled? %li = link_to 'Standard', '#tab-signin', 'data-toggle' => 'tab' .tab-content + - if kerberos_enabled? + %div#tab-kerberos.tab-pane{class: (:active unless crowd_enabled? || ldap_enabled?)} + = render 'devise/sessions/new_kerberos', provider: :kerberos - if crowd_enabled? %div.tab-pane.active{id: "tab-crowd"} = render 'devise/sessions/new_crowd' - - @ldap_servers.each_with_index do |server, i| - %div.tab-pane{id: "tab-#{server['provider_name']}", class: (:active if i.zero? && !crowd_enabled?)} - = render 'devise/sessions/new_ldap', server: server + - if ldap_enabled? + - @ldap_servers.each_with_index do |server, i| + %div.tab-pane{id: "tab-#{server['provider_name']}", class: (:active if i.zero? && !crowd_enabled?)} + = render 'devise/sessions/new_ldap', server: server - if signin_enabled? %div#tab-signin.tab-pane = render 'devise/sessions/new_base' - elsif signin_enabled? - = render 'devise/sessions/new_base' + = render 'devise/sessions/new_base'
\ No newline at end of file diff --git a/app/views/groups/_shared_projects.html.haml b/app/views/groups/_shared_projects.html.haml new file mode 100644 index 00000000000..d707ad4272d --- /dev/null +++ b/app/views/groups/_shared_projects.html.haml @@ -0,0 +1,18 @@ +- if projects.present? + .panel.panel-default + .panel-heading + Projects shared with + %strong #{@group.name} + (#{projects.count}) + %ul.well-list + - projects.each do |project| + %li.project-row + = link_to namespace_project_path(project.namespace, project), class: dom_class(project) do + %span.namespace-name + - if project.namespace + = project.namespace.human_name + \/ + %span.project-name + = truncate(project.name, length: 25) + %span.arrow + %i.icon-angle-right diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index 57308a661c0..6f55e9b0181 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -27,6 +27,24 @@ .form-group %hr + = f.label :membership_lock, class: 'control-label' do + Member lock + .col-sm-10 + .checkbox + = f.check_box :membership_lock + %span.descr Prevent adding new members to project membership within this group + + .form-group + %hr + = f.label :share_with_group_lock, class: 'control-label' do + Share with group lock + .col-sm-10 + .checkbox + = f.check_box :share_with_group_lock + %span.descr Prevent sharing a project with another group within this group + + .form-group + %hr = f.label :public, class: 'control-label' do Public .col-sm-10 @@ -44,5 +62,4 @@ Removing group will cause all child projects and resources to be removed. %br %strong Removed group can not be restored! - = link_to 'Remove Group', @group, data: {confirm: 'Removed group can not be restored! Are you sure?'}, method: :delete, class: "btn btn-remove" diff --git a/app/views/groups/group_members/_new_group_member.html.haml b/app/views/groups/group_members/_new_group_member.html.haml index 3361d7e2a8d..cea0bbad7d1 100644 --- a/app/views/groups/group_members/_new_group_member.html.haml +++ b/app/views/groups/group_members/_new_group_member.html.haml @@ -2,7 +2,7 @@ .form-group = f.label :user_ids, "People", class: 'control-label' .col-sm-10 - = users_select_tag(:user_ids, multiple: true, class: 'input-large', scope: :all, email_user: true) + = users_select_tag(:user_ids, multiple: true, class: 'input-large', scope: :all, email_user: true, skip_ldap: @group.ldap_synced?) .help-block Search for existing users or invite new ones using their email address. diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml index 15d289471c9..a943d10f46b 100644 --- a/app/views/groups/group_members/index.html.haml +++ b/app/views/groups/group_members/index.html.haml @@ -17,6 +17,10 @@ - if current_user && current_user.can?(:admin_group_member, @group) .pull-right + - if @group.ldap_synced? + = link_to reset_access_group_ldap_path(@group), class: 'btn btn-grouped', data: { confirm: "Force GitLab to do LDAP permission checks for all group members? All members besides yourself will be reduced to 'Guest' access until their next interaction with GitLab." }, method: :put do + Clear LDAP permission cache + = button_tag class: 'btn btn-new js-toggle-button', type: 'button' do Add members %i.fa.fa-chevron-down @@ -24,6 +28,19 @@ .js-toggle-content.hide.new-group-member-holder = render "new_group_member" +- if @group.ldap_synced? + .bs-callout.bs-callout-info + The members of this group are managed using LDAP and cannot be added, changed or removed here. + Because LDAP permissions in GitLab get updated one user at a time and because GitLab caches LDAP check results, changes on your LDAP server or in this group's LDAP sync settings may take up to #{Gitlab.config.ldap['sync_time']}s to show in the list below. + %ul + - @group.ldap_group_links.each do |ldap_group_link| + %li + People in cn + %code= ldap_group_link.cn + are given + %code= ldap_group_link.human_access + access. + .panel.panel-default.prepend-top-20 .panel-heading %strong #{@group.name} diff --git a/app/views/groups/hooks/index.html.haml b/app/views/groups/hooks/index.html.haml new file mode 100644 index 00000000000..d8a2f0b19d9 --- /dev/null +++ b/app/views/groups/hooks/index.html.haml @@ -0,0 +1,69 @@ +- page_title "Web Hooks" +%h3.page-title + Web hooks + +%p.light + #{link_to "Web hooks ", help_page_path("web_hooks", "web_hooks"), class: "vlink"} can be + used for binding events when something is happening within any project inside this group. + +%hr.clearfix + += form_for [@group, @hook], as: :hook, url: group_hooks_path(@group), html: { class: 'form-horizontal' } do |f| + -if @hook.errors.any? + .alert.alert-danger + - @hook.errors.full_messages.each do |msg| + %p= msg + .form-group + = f.label :url, "URL", class: 'control-label' + .col-sm-10 + = f.text_field :url, class: "form-control", placeholder: 'http://example.com/trigger-ci.json' + .form-group + = f.label :url, "Trigger", class: 'control-label' + .col-sm-10 + %div + = f.check_box :push_events, class: 'pull-left' + .prepend-left-20 + = f.label :push_events, class: 'list-label' do + %strong Push events + %p.light + This url will be triggered by a push to the repository + %div + = f.check_box :tag_push_events, class: 'pull-left' + .prepend-left-20 + = f.label :tag_push_events, class: 'list-label' do + %strong Tag push events + %p.light + This url will be triggered when a new tag is pushed to the repository + %div + = f.check_box :issues_events, class: 'pull-left' + .prepend-left-20 + = f.label :issues_events, class: 'list-label' do + %strong Issues events + %p.light + This url will be triggered when an issue is created + %div + = f.check_box :merge_requests_events, class: 'pull-left' + .prepend-left-20 + = f.label :merge_requests_events, class: 'list-label' do + %strong Merge Request events + %p.light + This url will be triggered when a merge request is created + .form-actions + = f.submit "Add Web Hook", class: "btn btn-create" + +-if @hooks.any? + .panel.panel-default + .panel-heading + Web hooks (#{@hooks.count}) + %ul.well-list + - @hooks.each do |hook| + %li + .pull-right + = link_to 'Test Hook', test_group_hook_path(@group, hook), class: "btn btn-small btn-grouped" + = link_to 'Remove', group_hook_path(@group, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-small btn-grouped" + .clearfix + %span.monospace= hook.url + %p + - %w(push_events tag_push_events issues_events merge_requests_events).each do |trigger| + - if hook.send(trigger) + %span.label.label-gray= trigger.titleize diff --git a/app/views/groups/ldap_group_links/index.html.haml b/app/views/groups/ldap_group_links/index.html.haml new file mode 100644 index 00000000000..44791bc7cee --- /dev/null +++ b/app/views/groups/ldap_group_links/index.html.haml @@ -0,0 +1,5 @@ +- page_title "LDAP Groups" +%h3.page-title Linked LDAP groups += render 'ldap_group_links/form', group: @group += render 'ldap_group_links/ldap_group_links', group: @group + diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index dc8e81323a6..66106949f76 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -36,6 +36,8 @@ = spinner %aside.side.col-md-5 = render "projects", projects: @projects + %br + = render "shared_projects", projects: @shared_projects - else %p This group does not have public projects diff --git a/app/views/help/index.html.haml b/app/views/help/index.html.haml index 57bc91ea5a9..ed1f455a8c9 100644 --- a/app/views/help/index.html.haml +++ b/app/views/help/index.html.haml @@ -1,23 +1,30 @@ %div %h1 GitLab - Community Edition + Enterprise Edition - if user_signed_in? %span= Gitlab::VERSION %small= Gitlab::REVISION - = version_status_badge - %p.slead - GitLab is open source software to collaborate on code. - %br - Manage git repositories with fine-grained access controls that keep your code secure. - %br - Perform code reviews and enhance collaboration with merge requests. - %br - Each project can also have an issue tracker and a wiki. - %br - Used by more than 100,000 organizations, GitLab is the most popular solution to manage git repositories on-premises. - %br + - if current_application_settings.version_check_enabled + = version_status_badge + %br Read more about GitLab at #{link_to promo_host, promo_url, target: '_blank'}. + - if help_text.present? + %hr + %p.slead + = markdown(help_text) + - else + %p.slead + GitLab is open source software to collaborate on code. + %br + Manage git repositories with fine grained access controls that keep your code secure. + %br + Perform code reviews and enhance collaboration with merge requests. + %br + Each project can also have an issue tracker and a wiki. + %br + Used by more than 100,000 organizations, GitLab is the most popular solution to manage git repositories on-premises. + - if current_application_settings.help_page_text.present? %hr = markdown(current_application_settings.help_page_text) diff --git a/app/views/layouts/_broadcast.html.haml b/app/views/layouts/_broadcast.html.haml index e7d477c225e..e01663e0592 100644 --- a/app/views/layouts/_broadcast.html.haml +++ b/app/views/layouts/_broadcast.html.haml @@ -1,4 +1,9 @@ - if broadcast_message.present? .broadcast-message{ style: broadcast_styling(broadcast_message) } - %i.fa.fa-bullhorn + = icon('bullhorn') = broadcast_message.message + +- if license_message.present? + .broadcast-message + = icon('bullhorn') + = license_message diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index 74174a72f5a..b70c0bca664 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -2,9 +2,8 @@ %head %meta{charset: "utf-8"} %meta{'http-equiv' => 'X-UA-Compatible', content: 'IE=edge'} - %meta{content: "GitLab Community Edition", name: "description"} + %meta{content: "GitLab Enterprise Edition", name: "description"} %meta{name: 'referrer', content: 'origin-when-cross-origin'} - %title= page_title = favicon_link_tag 'favicon.ico' diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml index 95e077c339f..6e2656ac681 100644 --- a/app/views/layouts/devise.html.haml +++ b/app/views/layouts/devise.html.haml @@ -26,6 +26,11 @@ - if extra_sign_in_text.present? = markdown(extra_sign_in_text) + - if help_text.present? + %h3 Need help? + %hr + %p.slead + = markdown(help_text) %hr .container diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml index 2079feeeab6..3ea1462e68b 100644 --- a/app/views/layouts/nav/_admin.html.haml +++ b/app/views/layouts/nav/_admin.html.haml @@ -44,11 +44,21 @@ = icon('external-link fw') %span Hooks + = nav_link(controller: :git_hooks) do + = link_to admin_git_hooks_path, title: 'Git Hooks', data: {placement: 'right'} do + = icon('git-square fw') + %span + Git Hooks = nav_link(controller: :background_jobs) do = link_to admin_background_jobs_path, title: 'Background Jobs', data: {placement: 'right'} do = icon('cog fw') %span Background Jobs + = nav_link(controller: :appearances) do + = link_to admin_appearances_path do + %i.fa.fa-image + %span + Appearance = nav_link(controller: :applications) do = link_to admin_applications_path, title: 'Applications', data: {placement: 'right'} do @@ -80,3 +90,9 @@ = icon('cogs fw') %span Settings + + = nav_link(controller: :licenses) do + = link_to admin_license_path, title: 'License', data: {placement: 'right'} do + = icon('check fw') + %span + License diff --git a/app/views/layouts/nav/_group_settings.html.haml b/app/views/layouts/nav/_group_settings.html.haml index c8411521f36..bb0234cf11a 100644 --- a/app/views/layouts/nav/_group_settings.html.haml +++ b/app/views/layouts/nav/_group_settings.html.haml @@ -18,3 +18,19 @@ = icon('folder fw') %span Projects + - if ldap_enabled? + = nav_link(controller: :ldap_group_links) do + = link_to group_ldap_group_links_path(@group) do + %i.fa.fa-exchange + %span + LDAP Groups + = nav_link(controller: :hooks) do + = link_to group_hooks_path(@group) do + %i.fa.fa-link + %span + Web Hooks + = nav_link(controller: :audit_events) do + = link_to group_audit_events_path(@group) do + %i.fa.fa-file-text-o + %span + Audit Events diff --git a/app/views/layouts/nav/_project_settings.html.haml b/app/views/layouts/nav/_project_settings.html.haml index 377a99e719a..d74a23650a3 100644 --- a/app/views/layouts/nav/_project_settings.html.haml +++ b/app/views/layouts/nav/_project_settings.html.haml @@ -13,6 +13,12 @@ = icon('pencil-square-o fw') %span Project Settings + - if @project.allowed_to_share_with_group? + = nav_link(controller: :group_links) do + = link_to namespace_project_group_links_path(@project.namespace, @project) do + = icon('share-square-o fw') + %span + Groups = nav_link(controller: :deploy_keys) do = link_to namespace_project_deploy_keys_path(@project.namespace, @project), title: 'Deploy Keys', data: {placement: 'right'} do = icon('key fw') @@ -23,6 +29,11 @@ = icon('link fw') %span Web Hooks + = nav_link(controller: :git_hooks) do + = link_to namespace_project_git_hooks_path(@project.namespace, @project) do + = icon('git-square fw') + %span + Git Hooks = nav_link(controller: :services) do = link_to namespace_project_services_path(@project.namespace, @project), title: 'Services', data: {placement: 'right'} do = icon('cogs fw') @@ -33,6 +44,11 @@ = icon('lock fw') %span Protected Branches + = nav_link(controller: :audit_events) do + = link_to namespace_project_audit_events_path(@project.namespace, @project) do + = icon('file-text-o fw') + %span + Audit Events - if @project.builds_enabled? = nav_link(controller: :runners) do diff --git a/app/views/ldap_group_links/_form.html.haml b/app/views/ldap_group_links/_form.html.haml new file mode 100644 index 00000000000..02437611615 --- /dev/null +++ b/app/views/ldap_group_links/_form.html.haml @@ -0,0 +1,30 @@ +%section.ldap-group-links + = form_for [group, LdapGroupLink.new], html: { class: 'form-horizontal' } do |f| + .form-holder + .form-group.clearfix + = f.label :cn, class: 'control-label' do + LDAP Server + .col-sm-10 + = f.select :provider, ldap_server_select_options, {}, class: 'form-control' + .form-group.clearfix + = f.label :cn, class: 'control-label' do + LDAP Group cn + .col-sm-10 + = f.hidden_field :cn, placeholder: "Ex. QA group", class: "xxlarge ajax-ldap-groups-select input-mn-300" + .help-block + Synchronize #{group.name}'s members with this LDAP group. + %br + If you select an LDAP group you do not belong to you will lose ownership of #{group.name}. + + .form-group.clearfix + = f.label :group_access, class: 'control-label' do + LDAP Access + .col-sm-10 + = f.select :group_access, options_for_select(GroupMember.access_level_roles), {}, class: 'form-control' + .help-block + Default, minimum permission level for LDAP group members of #{group.name}. + %br + You can manage permission levels for individual group members in the Members tab. + + .form-actions + = f.submit 'Add synchronization', class: "btn btn-create" diff --git a/app/views/ldap_group_links/_ldap_group_link.html.haml b/app/views/ldap_group_links/_ldap_group_link.html.haml new file mode 100644 index 00000000000..a3f36ba9263 --- /dev/null +++ b/app/views/ldap_group_links/_ldap_group_link.html.haml @@ -0,0 +1,15 @@ +%li + .pull-right + = link_to group_ldap_group_link_path(group, ldap_group_link), method: :delete, class: 'btn btn-danger btn-sm' do + = fa_icon('unlink', text: 'unlink') + %strong= ldap_group_link.cn + + - if ldap_group_link.config + %p.light + As #{ldap_group_link.human_access} on #{ldap_group_link.provider_label} server + - else + %p.cred + %i.fa.fa-warning + Config for + %code #{ldap_group_link.provider} + does not present in GitLab diff --git a/app/views/ldap_group_links/_ldap_group_links.html.haml b/app/views/ldap_group_links/_ldap_group_links.html.haml new file mode 100644 index 00000000000..6b8fe0f27f6 --- /dev/null +++ b/app/views/ldap_group_links/_ldap_group_links.html.haml @@ -0,0 +1,12 @@ +.panel.panel-default + .panel-heading + %h4.panel-title + Linked LDAP groups + == (#{group.ldap_group_links.count}) + + - if group.ldap_group_links.any? + %ul.well-list + = render collection: group.ldap_group_links, partial: 'ldap_group_links/ldap_group_link', locals: { group: group } + - else + .panel-body + No linked LDAP groups diff --git a/app/views/notify/new_merge_request_email.html.haml b/app/views/notify/new_merge_request_email.html.haml index 90ebdfc3fe2..09ed6740d51 100644 --- a/app/views/notify/new_merge_request_email.html.haml +++ b/app/views/notify/new_merge_request_email.html.haml @@ -5,5 +5,9 @@ %p Assignee: #{@merge_request.author_name} → #{@merge_request.assignee_name} +- if @merge_request.approvers.any? + %p + Approvers: #{render_items_list(@merge_request.approvers_left.map(&:name))} + -if @merge_request.description = markdown(@merge_request.description, pipeline: :email) diff --git a/app/views/notify/new_merge_request_email.text.erb b/app/views/notify/new_merge_request_email.text.erb index bdcca6e4ab7..79b27390b31 100644 --- a/app/views/notify/new_merge_request_email.text.erb +++ b/app/views/notify/new_merge_request_email.text.erb @@ -5,4 +5,7 @@ New Merge Request #<%= @merge_request.iid %> <%= merge_path_description(@merge_request, 'to') %> Author: <%= @merge_request.author_name %> Assignee: <%= @merge_request.assignee_name %> +<% if @merge_request.approvers.any? %> +Approvers: <%= render_items_list(@merge_request.approvers_left.map(&:name)) %> +<% end %> diff --git a/app/views/notify/send_admin_notification.html.haml b/app/views/notify/send_admin_notification.html.haml new file mode 100644 index 00000000000..5180d573aa0 --- /dev/null +++ b/app/views/notify/send_admin_notification.html.haml @@ -0,0 +1,7 @@ += simple_format @body + +\---- + +%p + Don't want to receive updates from GitLab administrators? + = link_to 'Unsubscribe', @unsubscribe_url
\ No newline at end of file diff --git a/app/views/notify/send_admin_notification.text.haml b/app/views/notify/send_admin_notification.text.haml new file mode 100644 index 00000000000..a165c73891b --- /dev/null +++ b/app/views/notify/send_admin_notification.text.haml @@ -0,0 +1,6 @@ += h @body + +\----- + +Don't want to receive updates from GitLab administrators? +== Unsubscribe here: #{@unsubscribe_url}
\ No newline at end of file diff --git a/app/views/notify/send_unsubscribed_notification.html.haml b/app/views/notify/send_unsubscribed_notification.html.haml new file mode 100644 index 00000000000..9f68feeaa31 --- /dev/null +++ b/app/views/notify/send_unsubscribed_notification.html.haml @@ -0,0 +1,2 @@ +%p + You have been unsubscribed from receiving GitLab administrator notifications. diff --git a/app/views/notify/send_unsubscribed_notification.text.haml b/app/views/notify/send_unsubscribed_notification.text.haml new file mode 100644 index 00000000000..5edc1ddcdae --- /dev/null +++ b/app/views/notify/send_unsubscribed_notification.text.haml @@ -0,0 +1 @@ +You have been unsubscribed from receiving GitLab administrator notifications. diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml index cd7b1b0fe03..730deb92305 100644 --- a/app/views/profiles/accounts/show.html.haml +++ b/app/views/profiles/accounts/show.html.haml @@ -29,33 +29,32 @@ %span You don`t have one yet. Click generate to fix it. = f.submit 'Generate', class: "btn btn-default btn-build-token" - - unless current_user.ldap_user? - .panel.panel-default - .panel-heading - Two-factor Authentication - .panel-body - - if current_user.two_factor_enabled? - .pull-right - = link_to 'Disable Two-factor Authentication', profile_two_factor_auth_path, method: :delete, class: 'btn btn-close btn-sm', - data: { confirm: 'Are you sure?' } - %p.text-success - %strong - Two-factor Authentication is enabled - %p - If you lose your recovery codes you can - %strong - = succeed ',' do - = link_to 'generate new ones', codes_profile_two_factor_auth_path, method: :post, data: { confirm: 'Are you sure?' } - invalidating all previous codes. + .panel.panel-default + .panel-heading + Two-factor Authentication + .panel-body + - if current_user.two_factor_enabled? + .pull-right + = link_to 'Disable Two-factor Authentication', profile_two_factor_auth_path, method: :delete, class: 'btn btn-close btn-sm', + data: { confirm: 'Are you sure?' } + %p.text-success + %strong + Two-factor Authentication is enabled + %p + If you lose your recovery codes you can + %strong + = succeed ',' do + = link_to 'generate new ones', codes_profile_two_factor_auth_path, method: :post, data: { confirm: 'Are you sure?' } + invalidating all previous codes. - - else - %p - Increase your account's security by enabling two-factor authentication (2FA). - %p - Each time you log in you’ll be required to provide your username and - password as usual, plus a randomly-generated code from your phone. - %div - = link_to 'Enable Two-factor Authentication', new_profile_two_factor_auth_path, class: 'btn btn-success' + - else + %p + Increase your account's security by enabling two-factor authentication (2FA). + %p + Each time you log in you’ll be required to provide your username and + password as usual, plus a randomly-generated code from your phone. + %div + = link_to 'Enable Two-factor Authentication', new_profile_two_factor_auth_path, class: 'btn btn-success' - if button_based_providers.any? .panel.panel-default diff --git a/app/views/profiles/keys/_key.html.haml b/app/views/profiles/keys/_key.html.haml index 9bbccbc45ea..e95dd038321 100644 --- a/app/views/profiles/keys/_key.html.haml +++ b/app/views/profiles/keys/_key.html.haml @@ -8,4 +8,5 @@ %span.cgray added #{time_ago_with_tooltip(key.created_at)} %td - = link_to 'Remove', path_to_key(key, is_admin), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-sm btn-remove delete-key pull-right" + - unless key.is_a? LDAPKey + = link_to 'Remove', path_to_key(key, is_admin), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-sm btn-remove delete-key pull-right" diff --git a/app/views/profiles/keys/_key_details.html.haml b/app/views/profiles/keys/_key_details.html.haml index 0ca8bd95157..0d0b235c6a3 100644 --- a/app/views/profiles/keys/_key_details.html.haml +++ b/app/views/profiles/keys/_key_details.html.haml @@ -20,4 +20,5 @@ = @key.key .col-md-12 .pull-right - = link_to 'Remove', path_to_key(@key, is_admin), data: {confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove delete-key" + - unless @key.is_a? LDAPKey + = link_to 'Remove', path_to_key(@key, is_admin), data: {confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove delete-key" diff --git a/app/views/projects/_issues_settings.html.haml b/app/views/projects/_issues_settings.html.haml new file mode 100644 index 00000000000..083e9ae845a --- /dev/null +++ b/app/views/projects/_issues_settings.html.haml @@ -0,0 +1,11 @@ +%fieldset.issues-feature + %legend + Issues: + + .form-group + = f.label :issues_template, class: 'control-label' do + Description template + .col-sm-10 + = f.text_area :issues_template, class: "form-control", rows: 3 + + diff --git a/app/views/projects/_merge_request_settings.html.haml b/app/views/projects/_merge_request_settings.html.haml new file mode 100644 index 00000000000..3190233f499 --- /dev/null +++ b/app/views/projects/_merge_request_settings.html.haml @@ -0,0 +1,80 @@ +%fieldset.merge-request-feature + %legend + Merge requests: + + .form-group + .col-sm-offset-2.col-sm-10 + .checkbox + = f.label :merge_requests_ff_only_enabled do + = f.check_box :merge_requests_ff_only_enabled + %strong Only fast-forward merging + %br + %span.descr The accept merge request button will only show when a merge without a merge commit is possible. + + .form-group.rebase-feature + .col-sm-offset-2.col-sm-10 + .checkbox + = f.label :merge_requests_rebase_enabled do + = f.check_box :merge_requests_rebase_enabled + %strong Rebase button + %br + %span.descr Allows rebasing of merge requests before fast-forward merge. + + .form-group + = f.label :merge_requests_template, class: 'control-label' do + Description template + .col-sm-10 + = f.text_area :merge_requests_template, class: "form-control", rows: 3 + + .form-group + = f.label :approvals_before_merge, class: 'control-label' do + Approvals required + .col-sm-10 + = f.number_field :approvals_before_merge, class: "form-control", min: 0 + .help-block + Number of users to approve a merge request before it can be accepted. 0 - approving is disabled + + .form-group.reset-approvals-on-push + .col-sm-offset-2.col-sm-10 + .checkbox + = f.label :reset_approvals_on_push do + = f.check_box :reset_approvals_on_push + %span.descr Reset approvals on push + .help-block Approvals are reset when new data is pushed to the merge request + + .form-group + = f.label :approver_ids, class: 'control-label' do + Approvers + .col-sm-10 + = users_select_tag("project[approver_ids]", multiple: true, class: 'input-large', scope: :all, email_user: true) + .help-block + Add an approver suggestion for each merge request + + .panel.panel-default.prepend-top-10 + .panel-heading + Approvers + %small + (#{@project.approvers.count(:all)}) + %ul.well-list + - @project.approvers.each do |approver| + %li + = link_to approver.user.name, approver.user + .pull-right + = link_to namespace_project_approver_path(@project.namespace, @project, approver), data: { confirm: "Are you sure you want to remove approver #{approver.user.name}"}, method: :delete, class: "btn-xs btn btn-remove", title: 'Remove approver' do + = icon("sign-out") + Remove + - if @project.approvers.empty? + %li There are no approvers + + +:coffeescript + new UsersSelect() + + mergeRequestsRebaseVisibilityCheck = -> + is_rebase_enabled = $("input#project_merge_requests_ff_only_enabled").prop("checked") + $(".rebase-feature").toggle(is_rebase_enabled) + + mergeRequestsRebaseVisibilityCheck() + + $("input#project_merge_requests_ff_only_enabled").change -> + mergeRequestsRebaseVisibilityCheck() diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 3ebc175648e..012cc3c95ff 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -86,6 +86,10 @@ %br %span.descr Share code pastes with others out of git repository + = render 'issues_settings', f: f + + = render 'merge_request_settings', f: f + %fieldset.features %legend Project avatar: diff --git a/app/views/projects/git_hooks/index.html.haml b/app/views/projects/git_hooks/index.html.haml new file mode 100644 index 00000000000..03446506fcc --- /dev/null +++ b/app/views/projects/git_hooks/index.html.haml @@ -0,0 +1,14 @@ +- page_title "Git Hooks" +%h3.page-title + Git hooks +%p.light + Rules that define what git pushes are accepted for this project + +%hr.clearfix + += form_for [@project.namespace.becomes(Namespace), @project, @git_hook], html: { class: 'form-horizontal' } do |f| + -if @git_hook.errors.any? + .alert.alert-danger + - @git_hook.errors.full_messages.each do |msg| + %p= msg + = render "shared/git_hooks_form", f: f diff --git a/app/views/projects/group_links/index.html.haml b/app/views/projects/group_links/index.html.haml new file mode 100644 index 00000000000..13f5fc141fa --- /dev/null +++ b/app/views/projects/group_links/index.html.haml @@ -0,0 +1,41 @@ +- page_title "Groups" +%h3.page_title Share project with other groups +%p.light + Projects can be stored in only one group at once. However you can share a project with other groups here. +%hr +- if @group_links.present? + .enabled-groups.panel.panel-default + .panel-heading + Already shared with + %ul.well-list + - @group_links.each do |group_link| + - group = group_link.group + %li + .pull-right + = link_to namespace_project_group_link_path(@project.namespace, @project, group_link), method: :delete, class: 'btn btn-sm' do + %i.icon-remove + disable sharing + = link_to group do + %strong + %i.icon-folder-open + = group.name + %br + .light up to #{group_link.human_access} + + +.available-groups + %h4 + Can be shared with + %div + = form_tag namespace_project_group_links_path(@project.namespace, @project), method: :post, class: 'form-horizontal' do + .form-group + = label_tag :link_group_id, 'Group', class: 'control-label' + .col-sm-10 + = groups_select_tag(:link_group_id, skip_group: @project.group.try(:path)) + .form-group + = label_tag :link_group_access, 'Max access level', class: 'control-label' + .col-sm-10 + = select_tag :link_group_access, options_for_select(ProjectGroupLink.access_options, ProjectGroupLink.default_access), class: "form-control" + .form-actions + = submit_tag "Share", class: "btn btn-create" + diff --git a/app/views/projects/merge_requests/ff_merge.js.haml b/app/views/projects/merge_requests/ff_merge.js.haml new file mode 100644 index 00000000000..661930b54d4 --- /dev/null +++ b/app/views/projects/merge_requests/ff_merge.js.haml @@ -0,0 +1,6 @@ +- if @status + :plain + location.reload() +- else + :plain + $('.mr-widget-body').html("#{escape_javascript(render('projects/merge_requests/widget/open/reload'))}"); diff --git a/app/views/projects/merge_requests/rebase.js.haml b/app/views/projects/merge_requests/rebase.js.haml new file mode 100644 index 00000000000..3db4662f91e --- /dev/null +++ b/app/views/projects/merge_requests/rebase.js.haml @@ -0,0 +1,3 @@ +:plain + $('.mr-widget-body').html("#{escape_javascript(render('projects/merge_requests/widget/open/ff_accept'))}") + merge_request_widget.rebaseInProgress(); diff --git a/app/views/projects/merge_requests/widget/_open.html.haml b/app/views/projects/merge_requests/widget/_open.html.haml index 0aad9bb3e88..4e7a233b06b 100644 --- a/app/views/projects/merge_requests/widget/_open.html.haml +++ b/app/views/projects/merge_requests/widget/_open.html.haml @@ -13,8 +13,12 @@ = render 'projects/merge_requests/widget/open/conflicts' - elsif @merge_request.work_in_progress? = render 'projects/merge_requests/widget/open/wip' + - elsif @merge_request.requires_approve? && !@merge_request.approved? + = render 'projects/merge_requests/widget/open/approve' - elsif !@merge_request.can_be_merged_by?(current_user) = render 'projects/merge_requests/widget/open/not_allowed' + - elsif @project.merge_requests_ff_only_enabled + = render 'projects/merge_requests/widget/open/ff_accept' - elsif @merge_request.can_be_merged? = render 'projects/merge_requests/widget/open/accept' @@ -25,3 +29,9 @@ Accepting this merge request will close #{"issue".pluralize(@closes_issues.size)} = succeed '.' do != gfm(issues_sentence(@closes_issues)) + + - if @merge_request.approvals.any? + .mr-widget-footer.approved-by-users + Approved by + - @merge_request.approved_by_users.each do |user| + = link_to_member(@project, user, name: false, size: 24) diff --git a/app/views/projects/merge_requests/widget/open/_approve.html.haml b/app/views/projects/merge_requests/widget/open/_approve.html.haml new file mode 100644 index 00000000000..20dfa25351e --- /dev/null +++ b/app/views/projects/merge_requests/widget/open/_approve.html.haml @@ -0,0 +1,8 @@ +%div
+ %h4
+ = render_require_section(@merge_request)
+
+ - if @merge_request.can_approve?(current_user)
+ .append-bottom-10
+ = form_for [:approve, @project.namespace.becomes(Namespace), @project, @merge_request], method: :post do |f|
+ = f.submit "Approve Merge Request", class: "btn btn-primary approve-btn"
diff --git a/app/views/projects/merge_requests/widget/open/_ff_accept.html.haml b/app/views/projects/merge_requests/widget/open/_ff_accept.html.haml new file mode 100644 index 00000000000..241c6d5627e --- /dev/null +++ b/app/views/projects/merge_requests/widget/open/_ff_accept.html.haml @@ -0,0 +1,39 @@ +- if @merge_request.ff_merge_possible? + = form_for [:ff_merge, @project.namespace.becomes(Namespace), @project, @merge_request], remote: true, method: :post, html: { class: 'accept-mr-form js-requires-input' } do |f| + = hidden_field_tag :authenticity_token, form_authenticity_token + .accept-merge-holder.clearfix.js-toggle-container + .accept-action + = f.button class: "btn btn-create accept-mr" do + Accept Merge Request + + .accept-control + Fast-forward merge without creating merge commit +- else + = form_for [:rebase, @project.namespace.becomes(Namespace), @project, @merge_request], + remote: true, method: :post, html: { class: 'accept-mr-form js-requires-input' } do |f| + = hidden_field_tag :authenticity_token, form_authenticity_token + .accept-merge-holder.clearfix.js-toggle-container + - if @merge_request.target_project.merge_requests_rebase_enabled && can_rebase?(@merge_request.source_project, @merge_request.source_branch) + - if @merge_request.rebase_in_progress? + %h4 Rebase in progress... It can take a while. Reload at will. + - else + .accept-action + = f.button class: "btn btn-reopen rebase-mr" do + Rebase onto #{@merge_request.target_branch} + .accept-control + Fast-forward merge is not possible. Branch must be rebased first + +:coffeescript + $('.accept-mr-form').on 'ajax:before', -> + btn = $('.accept-mr') + btn.disable() + btn.html("<i class='fa fa-spinner fa-spin'></i> Merge in progress") + + $('.rebase-mr-form').on 'ajax:before', -> + btn = $('.rebase-mr') + btn.disable() + btn.html("<i class='fa fa-spinner fa-spin'></i> Rebase in progress. It could take some time") + + - if #{@merge_request.rebase_in_progress?} + $ -> + merge_request_widget.rebaseInProgress() diff --git a/app/views/projects/project_members/_shared_group_members.html.haml b/app/views/projects/project_members/_shared_group_members.html.haml new file mode 100644 index 00000000000..792de5a3d27 --- /dev/null +++ b/app/views/projects/project_members/_shared_group_members.html.haml @@ -0,0 +1,21 @@ +- @project_group_links.each do |group_links| + - shared_group = group_links.group + - shared_group_users_count = group_links.group.group_members.count + .panel.panel-default + .panel-heading + Shared with + %strong #{shared_group.name} + group, members with + %strong #{group_links.human_access} + role (#{shared_group_users_count}) + - if current_user.can?(:admin_group, shared_group) + .panel-head-actions + = link_to group_group_members_path(shared_group), class: 'btn btn-sm' do + %i.fa.fa-pencil-square-o + Edit group members + %ul.well-list + - shared_group.group_members.order('access_level DESC').limit(20).each do |member| + = render 'groups/group_members/group_member', member: member, show_controls: false, show_roles: false + - if shared_group_users_count > 20 + %li + and #{shared_group_users_count - 20} more. For full list visit #{link_to 'group members page', group_group_members_path(shared_group)} diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index 9fc4be583cc..c17f049d30b 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -8,7 +8,7 @@ = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input', spellcheck: false } = button_tag 'Search', class: 'btn' - - if can?(current_user, :admin_project_member, @project) + - if can?(current_user, :admin_project_member, @project) && !membership_locked? %span.pull-right = button_tag class: 'btn btn-new btn-grouped js-toggle-button', type: 'button' do Add members @@ -24,10 +24,16 @@ Read more about project permissions %strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink" +- if membership_locked? + %p.text-warning + Adding new users is disabled at group level + = render "team", members: @project_members - if @group = render "group_members", members: @group_members +- if @project_group_links.any? && @project.allowed_to_share_with_group? + = render "shared_group_members" :javascript $('form.member-search-form').on('submit', function (event) { diff --git a/app/views/shared/_git_hooks_form.html.haml b/app/views/shared/_git_hooks_form.html.haml new file mode 100644 index 00000000000..7d68160204c --- /dev/null +++ b/app/views/shared/_git_hooks_form.html.haml @@ -0,0 +1,60 @@ +.form-group + .col-sm-offset-2.col-sm-10 + .checkbox + = f.label :deny_delete_tag do + = f.check_box :deny_delete_tag + %strong + Do not allow users to remove git tags with + %code git push + .help-block Tags can still be deleted through the web UI. + +.form-group + .col-sm-offset-2.col-sm-10 + .checkbox + = f.label :member_check do + = f.check_box :member_check + %strong + Check whether author is a GitLab user + .help-block Restrict commits by author (email) to existing GitLab users + +.form-group + = f.label :commit_message_regex, "Commit message", class: 'control-label' + .col-sm-10 + = f.text_field :commit_message_regex, class: "form-control", placeholder: 'Example: Fixes \d+\..*' + .help-block + All commit messages must match this + = link_to 'Ruby regular expression', 'http://www.ruby-doc.org/core-2.1.1/Regexp.html' + to be pushed. + If this field is empty it allows any commit message. + For example you can require that an issue number is always mentioned in the commit message. + +.form-group + = f.label :author_email_regex, "Commit author's email", class: 'control-label' + .col-sm-10 + = f.text_field :author_email_regex, class: "form-control", placeholder: 'Example: Fixes @my-company.com$' + .help-block + All commit author's email must match this + = link_to 'Ruby regular expression', 'http://www.ruby-doc.org/core-2.1.1/Regexp.html' + to be pushed. + If this field is empty it allows any email. + +.form-group + = f.label :file_name_regex, "Prohibited file names", class: 'control-label' + .col-sm-10 + = f.text_field :file_name_regex, class: "form-control", placeholder: 'Example: (jar|exe)$' + .help-block + All commited filenames must not match this + = link_to 'Ruby regular expression', 'http://www.ruby-doc.org/core-2.1.1/Regexp.html' + to be pushed. + If this field is empty it allows any filenames. + +.form-group + = f.label :max_file_size, "Maximum file size (MB)", class: 'control-label' + .col-sm-10 + = f.number_field :max_file_size, class: "form-control", min: 0 + .help-block + Pushes that contain added or updated files that exceed this file size are rejected. + Set to 0 to allow files of any size. + +.form-actions + = f.submit "Save Git hooks", class: "btn btn-create"
\ No newline at end of file diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index 0fc74d7d2b1..b199a6acb68 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -75,6 +75,45 @@ = link_to 'Create new label', new_namespace_project_label_path(issuable.project.namespace, issuable.project), target: :blank - if issuable.is_a?(MergeRequest) + - if @merge_request.requires_approve? + .form-group + = f.label :approver_ids, class: 'control-label' do + Approvers + .col-sm-10 + = users_select_tag("merge_request[approver_ids]", multiple: true, class: 'input-large', scope: :all, email_user: true) + .help-block + Merge Request should be approved by these users. + You can override the project settings by setting your own list of approvers. + + .panel.panel-default.prepend-top-10 + .panel-heading + Approvers + %ul.well-list.approver-list + - if @merge_request.new_record? + - @merge_request.target_project.approvers.each do |approver| + %li.project-approvers{id: dom_id(approver.user)} + = link_to approver.user.name, approver.user + .pull-right + = link_to "#", data: { confirm: "Are you sure you want to remove approver #{approver.user.name}"}, class: "btn-xs btn btn-remove", title: 'Remove approver' do + = icon("sign-out") + Remove + - if @merge_request.target_project.approvers.empty? + %li.no-approvers There are no approvers + - else + - @merge_request.approvers.each do |approver| + %li{id: dom_id(approver.user)} + = link_to approver.user.name, approver.user + .pull-right + = link_to namespace_project_merge_request_approver_path(@project.namespace, @project, @merge_request, approver), data: { confirm: "Are you sure you want to remove approver #{approver.user.name}"}, method: :delete, class: "btn-xs btn btn-remove", title: 'Remove approver' do + = icon("sign-out") + Remove + - if @merge_request.approvers.empty? + %li.no-approvers There are no approvers + .help-block.suggested-approvers + - if @suggested_approvers.any? + Suggested approvers: + = raw @suggested_approvers.map{|approver| link_to sanitize(approver.name), "#", id: dom_id(approver) }.join(", ") + %hr - if @merge_request.new_record? .form-group @@ -108,3 +147,37 @@ - else - cancel_project = issuable.project = link_to 'Cancel', [cancel_project.namespace.becomes(Namespace), cancel_project, issuable], class: 'btn btn-cancel' + +%li.project-approvers.hide.approver-template{id: "user_{user_id}"} + = link_to "{approver_name}", "#" + .pull-right + = link_to "#", data: { confirm: "Are you sure you want to remove approver {approver_name}"}, class: "btn-xs btn btn-remove", title: 'Remove approver' do + = icon("sign-out") + Remove +- if issuable.is_a?(MergeRequest) + :coffeescript + $(".approver-list").on "click", ".project-approvers .btn-remove", -> + $(this).closest("li").remove() + return false + $("form.merge-request-form").submit -> + if $("input#merge_request_approver_ids").length + approver_ids = $.map $("li.project-approvers").not(".approver-template"), (li, i) -> + li.id.replace("user_", "") + approvers_input = $(this).find("input#merge_request_approver_ids") + approver_ids = approver_ids.concat(approvers_input.val().split(",")) + approvers_input.val(_.compact(approver_ids).join(",")) + $(".suggested-approvers a").click -> + user_id = this.id.replace("user_", "") + user_name = this.text + return false if $(".approver-list #user_" + user_id).length + + approver_item_html = $(".project-approvers.approver-template").clone(). + removeClass("hide approver-template")[0]. + outerHTML. + replace(/\{approver_name\}/g, user_name). + replace(/\{user_id\}/g, user_id) + $(".no-approvers").remove() + $(".approver-list").append(approver_item_html) + return false + + diff --git a/app/views/unsubscribes/show.html.haml b/app/views/unsubscribes/show.html.haml new file mode 100644 index 00000000000..b30ba13504f --- /dev/null +++ b/app/views/unsubscribes/show.html.haml @@ -0,0 +1,11 @@ +- page_title "Unsubscribe", "Admin Notifications" +%h3.page-title Unsubscribe from Admin notifications + +%hr += form_tag unsubscribe_path(Base64.urlsafe_encode64(@email)) do + %p + Yes, I want to unsubscribe + %strong= @email + from any further admin emails. + .form-actions + = submit_tag 'Unsubscribe', class: 'btn btn-create' diff --git a/app/workers/admin_emails_worker.rb b/app/workers/admin_emails_worker.rb new file mode 100644 index 00000000000..650c5e859a2 --- /dev/null +++ b/app/workers/admin_emails_worker.rb @@ -0,0 +1,22 @@ +class AdminEmailsWorker + include Sidekiq::Worker + + def perform(recipient_id, subject, body) + recipient_list(recipient_id).pluck(:id).each do |user_id| + Notify.delay.send_admin_notification(user_id, subject, body) + end + end + + private + + def recipient_list(recipient_id) + case recipient_id + when 'all' + User.subscribed_for_admin_email + when /group-(\d+)\z/ + Group.find($1).users.subscribed_for_admin_email + when /project-(\d+)\z/ + Project.find($1).team.users.subscribed_for_admin_email + end + end +end diff --git a/app/workers/historical_data_worker.rb b/app/workers/historical_data_worker.rb new file mode 100644 index 00000000000..341d83d44a0 --- /dev/null +++ b/app/workers/historical_data_worker.rb @@ -0,0 +1,10 @@ +class HistoricalDataWorker + include Sidekiq::Worker + include Sidetiq::Schedulable + + recurrence { daily.hour_of_day(12) } + + def perform + HistoricalData.track! + end +end diff --git a/app/workers/ldap_sync_worker.rb b/app/workers/ldap_sync_worker.rb new file mode 100644 index 00000000000..13c6b414781 --- /dev/null +++ b/app/workers/ldap_sync_worker.rb @@ -0,0 +1,19 @@ +class LdapSyncWorker + include Sidekiq::Worker + include Sidetiq::Schedulable + + if Gitlab.config.ldap.enabled + HOUR = Gitlab.config.ldap.schedule_sync_hour + MINUTE = Gitlab.config.ldap.schedule_sync_minute + + recurrence { daily.hour_of_day(HOUR).minute_of_hour(MINUTE) } + end + + def perform + 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}" + Gitlab::LDAP::Access.allowed?(ldap_user) + end + end +end diff --git a/app/workers/rebase_worker.rb b/app/workers/rebase_worker.rb new file mode 100644 index 00000000000..afea9d3793a --- /dev/null +++ b/app/workers/rebase_worker.rb @@ -0,0 +1,13 @@ +class RebaseWorker + include Sidekiq::Worker + + sidekiq_options queue: :default + + def perform(merge_request_id, current_user_id) + current_user = User.find(current_user_id) + merge_request = MergeRequest.find(merge_request_id) + + MergeRequests::RebaseService.new(merge_request.target_project, current_user). + execute(merge_request) + end +end diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 8fdb2603ce8..871eb8fc512 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -163,6 +163,14 @@ production: &base # bundle exec rake gitlab:ldap:check RAILS_ENV=production ldap: enabled: false + + # GitLab EE only. + # In addition to refreshing users when they log in, + # enabling this setting will refresh LDAP user membership once a day. + # Default time of the day when this will happen is at 1:30am server time. + schedule_sync_hour: 1 # Hour of the day. Value from 0-23. + schedule_sync_minute: 30 # Minute of the hour. Value from 0-59. + servers: ########################################################################## # @@ -225,6 +233,33 @@ production: &base # user_filter: '' + # This setting controls the amount of time between LDAP permission checks for each user. + # After this time has expired for a given user, their next interaction with GitLab (a click in the web UI, a git pull etc.) will be slower because the LDAP permission check is being performed. + # How much slower depends on your LDAP setup, but it is not uncommon for this check to add seconds of waiting time. + # The default value is to have a 'slow click' once every 3600 seconds, i.e. once per hour. + # + # Warning: if you set this value too low, every click in GitLab will be a 'slow click' for all of your LDAP users. + # sync_time: 3600 + + # Base where we can search for groups + # + # Ex. ou=Groups,dc=gitlab,dc=example + # + group_base: '' + + # LDAP group of users who should be admins in GitLab + # + # Ex. GLAdmins + # + admin_group: '' + + # Name of attribute which holds a ssh public key of the user object. + # If false or nil, SSH key syncronisation will be disabled. + # + # Ex. sshpublickey + # + sync_ssh_keys: false + # LDAP attributes that GitLab will use to create an account for the LDAP user. # The specified attribute can either be the attribute name as a string (e.g. 'mail'), # or an array of attribute names to try in order (e.g. ['mail', 'email']). @@ -253,6 +288,27 @@ production: &base # host: # .... + ## Kerberos settings + kerberos: + # Allow the HTTP Negotiate authentication method for Git clients + enabled: false + + # Kerberos 5 keytab file. The keytab file must be readable by the GitLab user, + # and should be different from other keytabs in the system. + # (default: use default keytab from Krb5 config) + # keytab: /etc/http.keytab + + # The Kerberos service name to be used by GitLab. + # (default: accept any service name in keytab file) + # service_principal_name: HTTP/gitlab.example.com@EXAMPLE.COM + + # Dedicated port: Git before 2.4 does not fall back to Basic authentication if Negotiate fails. + # To support both Basic and Negotiate methods with older versions of Git, configure + # nginx to proxy GitLab on an extra port (e.g. 8443) and uncomment the following lines + # to dedicate this port to Kerberos authentication. (default: false) + # use_dedicated_port: true + # port: 8443 + # https: true ## OmniAuth settings omniauth: @@ -284,6 +340,7 @@ production: &base # - { name: 'github', # app_id: 'YOUR_APP_ID', # app_secret: 'YOUR_APP_SECRET', + # url: "https://github.com/", # args: { scope: 'user:email' } } # - { name: 'bitbucket', # app_id: 'YOUR_APP_ID', diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 6b7990c0ab0..5dc26e21f0b 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -88,10 +88,12 @@ class Settings < Settingslogic end end - # Default settings Settings['ldap'] ||= Settingslogic.new({}) Settings.ldap['enabled'] = false if Settings.ldap['enabled'].nil? +Settings.ldap['sync_time'] = 3600 if Settings.ldap['sync_time'].nil? +Settings.ldap['schedule_sync_hour'] = 1 if Settings.ldap['schedule_sync_hour'].nil? +Settings.ldap['schedule_sync_minute'] = 30 if Settings.ldap['schedule_sync_minute'].nil? # backwards compatibility, we only have one host if Settings.ldap['enabled'] || Rails.env.test? @@ -105,13 +107,16 @@ if Settings.ldap['enabled'] || Rails.env.test? end Settings.ldap['servers'].each do |key, server| + server = Settingslogic.new(server) server['label'] ||= 'LDAP' server['block_auto_created_users'] = false if server['block_auto_created_users'].nil? server['allow_username_or_email_login'] = false if server['allow_username_or_email_login'].nil? server['active_directory'] = true if server['active_directory'].nil? server['attributes'] = {} if server['attributes'].nil? server['provider_name'] ||= "ldap#{key}".downcase + server['sync_time'] = 3600 if server['sync_time'].nil? server['provider_class'] = OmniAuth::Utils.camelize(server['provider_name']) + Settings.ldap['servers'][key] = server end end @@ -125,6 +130,29 @@ Settings.omniauth['auto_link_ldap_user'] = false if Settings.omniauth['auto_link Settings.omniauth['providers'] ||= [] +# Fill out omniauth-gitlab settings. It is needed for easy set up GHE or GH by just specifying url. + +github_default_url = "https://github.com" +github_settings = Settings.omniauth['providers'].find { |provider| provider["name"] == "github"} + +if github_settings + # For compatibility with old config files (before 7.8) + # where people dont have url in github settings + if github_settings['url'].blank? + github_settings['url'] = github_default_url + end + + if github_settings["url"].include?(github_default_url) + github_settings["args"]["client_options"] = OmniAuth::Strategies::GitHub.default_options[:client_options] + else + github_settings["args"]["client_options"] = { + "site" => File.join(github_settings["url"], "api/v3"), + "authorize_url" => File.join(github_settings["url"], "login/oauth/authorize"), + "token_url" => File.join(github_settings["url"], "login/oauth/access_token") + } + end +end + Settings['shared'] ||= Settingslogic.new({}) Settings.shared['path'] = File.expand_path(Settings.shared['path'] || "shared", Rails.root) @@ -162,7 +190,7 @@ Settings.gitlab['signin_enabled'] ||= true if Settings.gitlab['signin_enabled']. Settings.gitlab['twitter_sharing_enabled'] ||= true if Settings.gitlab['twitter_sharing_enabled'].nil? Settings.gitlab['restricted_visibility_levels'] = Settings.send(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], []) Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username_changing_enabled'].nil? -Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing)) +(?:(?:issues? +)?#\d+(?:(?:, *| +and +)?))+)' if Settings.gitlab['issue_closing_pattern'].nil? +Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing)) +(?:(?:issues? +)?#\d+(?:(?:, *| +and +)?)|([A-Z]*-\d*))+)' if Settings.gitlab['issue_closing_pattern'].nil? Settings.gitlab['default_projects_features'] ||= {} Settings.gitlab['webhook_timeout'] ||= 10 Settings.gitlab['max_attachment_size'] ||= 10 @@ -263,6 +291,17 @@ Settings.satellites['path'] = File.expand_path(Settings.satellites['path'] || "t # +# Kerberos +# +Settings['kerberos'] ||= Settingslogic.new({}) +Settings.kerberos['enabled'] = false if Settings.kerberos['enabled'].nil? +Settings.kerberos['keytab'] = nil if Settings.kerberos['keytab'].blank? #nil means use default keytab +Settings.kerberos['service_principal_name'] = nil if Settings.kerberos['service_principal_name'].blank? #nil means any SPN in keytab +Settings.kerberos['use_dedicated_port'] = false if Settings.kerberos['use_dedicated_port'].nil? +Settings.kerberos['https'] = Settings.gitlab.https if Settings.kerberos['https'].nil? +Settings.kerberos['port'] ||= Settings.kerberos.https ? 8443 : 8088 + +# # Extra customization # Settings['extra'] ||= Settingslogic.new({}) diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 29506970af2..33e81cd5fcd 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -210,7 +210,7 @@ Devise.setup do |config| # end if Gitlab::LDAP::Config.enabled? - Gitlab.config.ldap.servers.values.each do |server| + Gitlab::LDAP::Config.servers.each do |server| if server['allow_username_or_email_login'] email_stripping_proc = ->(name) {name.gsub(/@.*\z/,'')} else diff --git a/config/initializers/license.rb b/config/initializers/license.rb new file mode 100644 index 00000000000..4f4199ae56b --- /dev/null +++ b/config/initializers/license.rb @@ -0,0 +1,15 @@ +begin + public_key_file = File.read(Rails.root.join(".license_encryption_key.pub")) + public_key = OpenSSL::PKey::RSA.new(public_key_file) + Gitlab::License.encryption_key = public_key +rescue + warn "WARNING: No valid license encryption key provided." +end + +# Needed to run migration +if ActiveRecord::Base.connected? && ActiveRecord::Base.connection.table_exists?('licenses') + message = LicenseHelper.license_message(signed_in: true, is_admin: true) + if message.present? + warn "WARNING: #{message}" + end +end diff --git a/config/initializers/omniauth.rb b/config/initializers/omniauth.rb index 70ed10e8275..6d39772b5c2 100644 --- a/config/initializers/omniauth.rb +++ b/config/initializers/omniauth.rb @@ -1,13 +1,16 @@ if Gitlab::LDAP::Config.enabled? module OmniAuth::Strategies - server = Gitlab.config.ldap.servers.values.first - klass = server['provider_class'] - const_set(klass, Class.new(LDAP)) unless klass == 'LDAP' + Gitlab::LDAP::Config.servers.each do |server| + # do not redeclare LDAP + next if server['provider_name'] == 'ldap' + const_set(server['provider_class'], Class.new(LDAP)) + end end OmniauthCallbacksController.class_eval do - server = Gitlab.config.ldap.servers.values.first - alias_method server['provider_name'], :ldap + Gitlab::LDAP::Config.servers.each do |server| + alias_method server['provider_name'], :ldap + end end end diff --git a/config/routes.rb b/config/routes.rb index bd85f4e3c69..5211596ad9d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -183,6 +183,11 @@ Gitlab::Application.routes.draw do to: "uploads#show", constraints: { model: /note|user|group|project/, mounted_as: /avatar|attachment/, filename: /[^\/]+/ } + # Appearance + get ":model/:mounted_as/:id/:filename", + to: "uploads#show", + constraints: { model: /appearance/, mounted_as: /logo|dark_logo|light_logo/, filename: /.+/ } + # Project markdown uploads get ":namespace_id/:project_id/:secret/:filename", to: "projects/uploads#show", @@ -239,6 +244,7 @@ Gitlab::Application.routes.draw do end end + resources :git_hooks, only: [:index, :update] resources :abuse_reports, only: [:index, :destroy] resources :applications @@ -257,6 +263,7 @@ Gitlab::Application.routes.draw do resources :broadcast_messages, only: [:index, :create, :destroy] resource :logs, only: [:show] resource :background_jobs, controller: 'background_jobs', only: [:show] + resource :email, only: [:show, :create] resources :namespaces, path: '/projects', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: [] do root to: 'projects#index', as: :projects @@ -273,10 +280,22 @@ Gitlab::Application.routes.draw do end end + resource :appearances, path: 'appearance' do + member do + get :preview + delete :logo + delete :header_logos + end + end + resource :application_settings, only: [:show, :update] do resources :services end + resource :license, only: [:show, :new, :create, :destroy] do + get :download, on: :member + end + resources :labels root to: 'dashboard#index' @@ -361,7 +380,18 @@ Gitlab::Application.routes.draw do get :projects end + collection do + get :autocomplete + end + scope module: :groups do + resource :ldap, only: [] do + member do + put :reset_access + end + end + + resources :ldap_group_links, only: [:index, :create, :destroy] resources :group_members, only: [:index, :create, :update, :destroy] do post :resend_invite, on: :member delete :leave, on: :collection @@ -370,8 +400,18 @@ Gitlab::Application.routes.draw do resource :avatar, only: [:destroy] resources :milestones, only: [:index, :show, :update] end + + get "/audit_events" => "audit_events#group_log" + + resources :hooks, only: [:index, :create, :destroy], constraints: { id: /\d+/ }, module: :groups do + member do + get :test + end + end end + get 'unsubscribes/:email', to: 'unsubscribes#show', as: :unsubscribe + post 'unsubscribes/:email', to: 'unsubscribes#create' resources :projects, constraints: { id: /[^\/]+/ }, only: [:index, :new, :create] devise_for :users, controllers: { omniauth_callbacks: :omniauth_callbacks, registrations: :registrations , passwords: :passwords, sessions: :sessions, confirmations: :confirmations } @@ -574,6 +614,9 @@ Gitlab::Application.routes.draw do get :merge_check get :ci_status post :toggle_subscription + post :approve + post :rebase + post :ff_merge end collection do @@ -581,6 +624,7 @@ Gitlab::Application.routes.draw do get :branch_to get :update_branches end + resources :approvers, only: :destroy end resources :branches, only: [:index, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } @@ -589,6 +633,7 @@ Gitlab::Application.routes.draw do end resources :protected_branches, only: [:index, :create, :update, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } + resources :git_hooks, constraints: { id: /\d+/ } resource :variables, only: [:show, :update] resources :triggers, only: [:index, :create, :destroy] resource :ci_settings, only: [:edit, :update, :destroy] @@ -660,6 +705,8 @@ Gitlab::Application.routes.draw do end end + resources :group_links, only: [:index, :create, :destroy], constraints: { id: /\d+/ } + resources :notes, constraints: { id: /\d+/ } do member do delete :delete_attachment @@ -678,7 +725,12 @@ Gitlab::Application.routes.draw do get :pause end end + + resources :approvers, only: :destroy end + + get "/audit_events" => "audit_events#project_log" + end end diff --git a/db/migrate/20130711063759_create_project_group_links.rb b/db/migrate/20130711063759_create_project_group_links.rb new file mode 100644 index 00000000000..395083f2a03 --- /dev/null +++ b/db/migrate/20130711063759_create_project_group_links.rb @@ -0,0 +1,10 @@ +class CreateProjectGroupLinks < ActiveRecord::Migration + def change + create_table :project_group_links do |t| + t.integer :project_id, null: false + t.integer :group_id, null: false + + t.timestamps + end + end +end diff --git a/db/migrate/20130802124933_add_ldap_settings_to_group.rb b/db/migrate/20130802124933_add_ldap_settings_to_group.rb new file mode 100644 index 00000000000..2fed8cb7e19 --- /dev/null +++ b/db/migrate/20130802124933_add_ldap_settings_to_group.rb @@ -0,0 +1,5 @@ +class AddLdapSettingsToGroup < ActiveRecord::Migration + def change + add_column :namespaces, :ldap_cn, :string, null: true + end +end diff --git a/db/migrate/20130809090140_add_ldap_access_to_group.rb b/db/migrate/20130809090140_add_ldap_access_to_group.rb new file mode 100644 index 00000000000..d709494eeeb --- /dev/null +++ b/db/migrate/20130809090140_add_ldap_access_to_group.rb @@ -0,0 +1,5 @@ +class AddLdapAccessToGroup < ActiveRecord::Migration + def change + add_column :namespaces, :ldap_access, :integer, null: true + end +end diff --git a/db/migrate/20130820102832_add_access_to_project_group_link.rb b/db/migrate/20130820102832_add_access_to_project_group_link.rb new file mode 100644 index 00000000000..00e3947a6bb --- /dev/null +++ b/db/migrate/20130820102832_add_access_to_project_group_link.rb @@ -0,0 +1,5 @@ +class AddAccessToProjectGroupLink < ActiveRecord::Migration + def change + add_column :project_group_links, :group_access, :integer, null: false, default: ProjectGroupLink.default_access + end +end diff --git a/db/migrate/20140319135450_create_git_hooks.rb b/db/migrate/20140319135450_create_git_hooks.rb new file mode 100644 index 00000000000..ca6d13effd0 --- /dev/null +++ b/db/migrate/20140319135450_create_git_hooks.rb @@ -0,0 +1,13 @@ +class CreateGitHooks < ActiveRecord::Migration + def change + create_table :git_hooks do |t| + t.string :force_push_regex + t.string :delete_branch_regex + t.string :commit_message_regex + t.boolean :deny_delete_tag + t.integer :project_id + + t.timestamps + end + end +end diff --git a/db/migrate/20140414093351_create_appearances.rb b/db/migrate/20140414093351_create_appearances.rb new file mode 100644 index 00000000000..60c81e6d355 --- /dev/null +++ b/db/migrate/20140414093351_create_appearances.rb @@ -0,0 +1,12 @@ +class CreateAppearances < ActiveRecord::Migration + def change + create_table :appearances do |t| + t.string :title + t.text :description + t.string :logo + t.integer :updated_by + + t.timestamps + end + end +end diff --git a/db/migrate/20140508105809_add_mr_template_to_project.rb b/db/migrate/20140508105809_add_mr_template_to_project.rb new file mode 100644 index 00000000000..c16e488a61d --- /dev/null +++ b/db/migrate/20140508105809_add_mr_template_to_project.rb @@ -0,0 +1,5 @@ +class AddMrTemplateToProject < ActiveRecord::Migration + def change + add_column :projects, :merge_requests_template, :text + end +end diff --git a/db/migrate/20140513095908_add_username_password_api_version_to_services.rb b/db/migrate/20140513095908_add_username_password_api_version_to_services.rb new file mode 100644 index 00000000000..407c9305253 --- /dev/null +++ b/db/migrate/20140513095908_add_username_password_api_version_to_services.rb @@ -0,0 +1,7 @@ +class AddUsernamePasswordApiVersionToServices < ActiveRecord::Migration + def change + add_column :services, :username, :string + add_column :services, :password, :string + add_column :services, :api_version, :string + end +end diff --git a/db/migrate/20140811083829_add_unsubscribed_at_field_to_users.rb b/db/migrate/20140811083829_add_unsubscribed_at_field_to_users.rb new file mode 100644 index 00000000000..c3b74c2b7b8 --- /dev/null +++ b/db/migrate/20140811083829_add_unsubscribed_at_field_to_users.rb @@ -0,0 +1,5 @@ +class AddUnsubscribedAtFieldToUsers < ActiveRecord::Migration + def change + add_column :users, :admin_email_unsubscribed_at, :datetime + end +end diff --git a/db/migrate/20140811155127_add_jira_issue_transition_id_to_services.rb b/db/migrate/20140811155127_add_jira_issue_transition_id_to_services.rb new file mode 100644 index 00000000000..c687804fbe7 --- /dev/null +++ b/db/migrate/20140811155127_add_jira_issue_transition_id_to_services.rb @@ -0,0 +1,11 @@ +class AddJiraIssueTransitionIdToServices < ActiveRecord::Migration + def up + add_column :services, :jira_issue_transition_id, :string, default: '2' + Service.reset_column_information + Service.where(jira_issue_transition_id: nil).update_all jira_issue_transition_id: '2' + end + + def down + remove_column :services, :jira_issue_transition_id + end +end diff --git a/db/migrate/20140813090117_add_ldap_groups_table.rb b/db/migrate/20140813090117_add_ldap_groups_table.rb new file mode 100644 index 00000000000..2cd7d239088 --- /dev/null +++ b/db/migrate/20140813090117_add_ldap_groups_table.rb @@ -0,0 +1,15 @@ +class AddLdapGroupsTable < ActiveRecord::Migration + def up + create_table :ldap_groups do |t| + t.string :cn, null: false + t.integer :group_access, null: false + t.references :group, null: false + + t.timestamps + end + end + + def down + drop_table :ldap_groups + end +end diff --git a/db/migrate/20140813133925_rename_ldap_group_to_ldap_group_link.rb b/db/migrate/20140813133925_rename_ldap_group_to_ldap_group_link.rb new file mode 100644 index 00000000000..d3e940a96df --- /dev/null +++ b/db/migrate/20140813133925_rename_ldap_group_to_ldap_group_link.rb @@ -0,0 +1,16 @@ +class RenameLdapGroupToLdapGroupLink < ActiveRecord::Migration + def up + rename_table :ldap_groups, :ldap_group_links + + # NOTE: we use the old_ methods because the new methods are overloaded + # for backwards compatibility + time = Time.now.strftime('%Y-%m-%d %H:%M:%S') + execute "INSERT INTO ldap_group_links ( group_access, cn, group_id, created_at, updated_at ) + SELECT ldap_access, ldap_cn, id, DATE('#{time}'), DATE('#{time}') FROM namespaces + WHERE ldap_cn IS NOT NULL;" + end + + def down + rename_table :ldap_group_links, :ldap_groups + end +end diff --git a/db/migrate/20140907223153_remove_columns_for_services.rb b/db/migrate/20140907223153_remove_columns_for_services.rb new file mode 100644 index 00000000000..859d79ca71b --- /dev/null +++ b/db/migrate/20140907223153_remove_columns_for_services.rb @@ -0,0 +1,8 @@ +class RemoveColumnsForServices < ActiveRecord::Migration + def change + remove_column :services, :username, :string + remove_column :services, :password, :string + remove_column :services, :jira_issue_transition_id, :string + remove_column :services, :api_version, :string + end +end diff --git a/db/migrate/20141010132608_add_provider_to_ldap_group_links.rb b/db/migrate/20141010132608_add_provider_to_ldap_group_links.rb new file mode 100644 index 00000000000..2858d97cb63 --- /dev/null +++ b/db/migrate/20141010132608_add_provider_to_ldap_group_links.rb @@ -0,0 +1,5 @@ +class AddProviderToLdapGroupLinks < ActiveRecord::Migration + def change + add_column :ldap_group_links, :provider, :string + end +end diff --git a/db/migrate/20141027173526_add_author_email_regex_to_git_hook.rb b/db/migrate/20141027173526_add_author_email_regex_to_git_hook.rb new file mode 100644 index 00000000000..bf58a13e81f --- /dev/null +++ b/db/migrate/20141027173526_add_author_email_regex_to_git_hook.rb @@ -0,0 +1,5 @@ +class AddAuthorEmailRegexToGitHook < ActiveRecord::Migration + def change + add_column :git_hooks, :author_email_regex, :string + end +end diff --git a/db/migrate/20141030133853_add_member_check_to_git_hooks.rb b/db/migrate/20141030133853_add_member_check_to_git_hooks.rb new file mode 100644 index 00000000000..15ce6eec89e --- /dev/null +++ b/db/migrate/20141030133853_add_member_check_to_git_hooks.rb @@ -0,0 +1,5 @@ +class AddMemberCheckToGitHooks < ActiveRecord::Migration + def change + add_column :git_hooks, :member_check, :boolean, default: false, null: false + end +end diff --git a/db/migrate/20141103160516_add_file_name_regex_to_git_hooks.rb b/db/migrate/20141103160516_add_file_name_regex_to_git_hooks.rb new file mode 100644 index 00000000000..ce0e1dedd16 --- /dev/null +++ b/db/migrate/20141103160516_add_file_name_regex_to_git_hooks.rb @@ -0,0 +1,5 @@ +class AddFileNameRegexToGitHooks < ActiveRecord::Migration + def change + add_column :git_hooks, :file_name_regex, :string + end +end diff --git a/db/migrate/20141126120926_add_merge_request_rebase_enabled_to_projects.rb b/db/migrate/20141126120926_add_merge_request_rebase_enabled_to_projects.rb new file mode 100644 index 00000000000..e78add15e60 --- /dev/null +++ b/db/migrate/20141126120926_add_merge_request_rebase_enabled_to_projects.rb @@ -0,0 +1,5 @@ +class AddMergeRequestRebaseEnabledToProjects < ActiveRecord::Migration + def change + add_column :projects, :merge_requests_rebase_enabled, :boolean, default: false + end +end diff --git a/db/migrate/20141212124604_add_group_membership_lock.rb b/db/migrate/20141212124604_add_group_membership_lock.rb new file mode 100644 index 00000000000..5dfd0835cb6 --- /dev/null +++ b/db/migrate/20141212124604_add_group_membership_lock.rb @@ -0,0 +1,5 @@ +class AddGroupMembershipLock < ActiveRecord::Migration + def change + add_column :namespaces, :membership_lock, :boolean, default: false + end +end diff --git a/db/migrate/20141213212220_add_header_logos_to_appearances.rb b/db/migrate/20141213212220_add_header_logos_to_appearances.rb new file mode 100644 index 00000000000..9f3b7877271 --- /dev/null +++ b/db/migrate/20141213212220_add_header_logos_to_appearances.rb @@ -0,0 +1,6 @@ +class AddHeaderLogosToAppearances < ActiveRecord::Migration + def change + add_column :appearances, :dark_logo, :string + add_column :appearances, :light_logo, :string + end +end diff --git a/db/migrate/20141230100055_remove_old_fields_from_namespace.rb b/db/migrate/20141230100055_remove_old_fields_from_namespace.rb new file mode 100644 index 00000000000..a71547f845f --- /dev/null +++ b/db/migrate/20141230100055_remove_old_fields_from_namespace.rb @@ -0,0 +1,11 @@ +class RemoveOldFieldsFromNamespace < ActiveRecord::Migration + def up + remove_column :namespaces, :ldap_cn + remove_column :namespaces, :ldap_access + end + + def down + add_column :namespaces, :ldap_cn, :string, null: true + add_column :namespaces, :ldap_access, :integer, null: true + end +end diff --git a/db/migrate/20150125163158_add_rebase_setting_to_projects.rb b/db/migrate/20150125163158_add_rebase_setting_to_projects.rb new file mode 100644 index 00000000000..61b2a2798b8 --- /dev/null +++ b/db/migrate/20150125163158_add_rebase_setting_to_projects.rb @@ -0,0 +1,5 @@ +class AddRebaseSettingToProjects < ActiveRecord::Migration + def change + add_column :projects, :merge_requests_rebase_default, :boolean, default: true + end +end diff --git a/db/migrate/20150225214822_help_text_to_application_settings.rb b/db/migrate/20150225214822_help_text_to_application_settings.rb new file mode 100644 index 00000000000..8594758a48c --- /dev/null +++ b/db/migrate/20150225214822_help_text_to_application_settings.rb @@ -0,0 +1,5 @@ +class HelpTextToApplicationSettings < ActiveRecord::Migration + def change + add_column :application_settings, :help_text, :text + end +end diff --git a/db/migrate/20150312000132_add_group_id_to_web_hooks.rb b/db/migrate/20150312000132_add_group_id_to_web_hooks.rb new file mode 100644 index 00000000000..074a71c4f21 --- /dev/null +++ b/db/migrate/20150312000132_add_group_id_to_web_hooks.rb @@ -0,0 +1,5 @@ +class AddGroupIdToWebHooks < ActiveRecord::Migration + def change + add_column :web_hooks, :group_id, :integer, after: :project_id + end +end
\ No newline at end of file diff --git a/db/migrate/20150324223425_add_is_sample_to_git_hooks.rb b/db/migrate/20150324223425_add_is_sample_to_git_hooks.rb new file mode 100644 index 00000000000..3541060109b --- /dev/null +++ b/db/migrate/20150324223425_add_is_sample_to_git_hooks.rb @@ -0,0 +1,5 @@ +class AddIsSampleToGitHooks < ActiveRecord::Migration + def change + add_column :git_hooks, :is_sample, :boolean, default: false + end +end diff --git a/db/migrate/20150501095306_create_licenses.rb b/db/migrate/20150501095306_create_licenses.rb new file mode 100644 index 00000000000..730c544c828 --- /dev/null +++ b/db/migrate/20150501095306_create_licenses.rb @@ -0,0 +1,9 @@ +class CreateLicenses < ActiveRecord::Migration + def change + create_table :licenses do |t| + t.text :data, null: false + + t.timestamps + end + end +end diff --git a/db/migrate/20150507194350_create_historical_data.rb b/db/migrate/20150507194350_create_historical_data.rb new file mode 100644 index 00000000000..06624eb3ef9 --- /dev/null +++ b/db/migrate/20150507194350_create_historical_data.rb @@ -0,0 +1,10 @@ +class CreateHistoricalData < ActiveRecord::Migration + def change + create_table :historical_data do |t| + t.date :date, null: false + t.integer :active_user_count + + t.timestamps + end + end +end diff --git a/db/migrate/20150605131047_add_max_file_size_to_git_hooks.rb b/db/migrate/20150605131047_add_max_file_size_to_git_hooks.rb new file mode 100644 index 00000000000..fe087ba6165 --- /dev/null +++ b/db/migrate/20150605131047_add_max_file_size_to_git_hooks.rb @@ -0,0 +1,5 @@ +class AddMaxFileSizeToGitHooks < ActiveRecord::Migration + def change + add_column :git_hooks, :max_file_size, :integer, default: 0 + end +end diff --git a/db/migrate/20150609113337_create_approves.rb b/db/migrate/20150609113337_create_approves.rb new file mode 100644 index 00000000000..7a64bd63dfb --- /dev/null +++ b/db/migrate/20150609113337_create_approves.rb @@ -0,0 +1,10 @@ +class CreateApproves < ActiveRecord::Migration + def change + create_table :approvals do |t| + t.integer :merge_request_id, null: false + t.integer :user_id, null: false + + t.timestamps + end + end +end diff --git a/db/migrate/20150609125332_add_project_merge_approves.rb b/db/migrate/20150609125332_add_project_merge_approves.rb new file mode 100644 index 00000000000..0028d6de8f9 --- /dev/null +++ b/db/migrate/20150609125332_add_project_merge_approves.rb @@ -0,0 +1,5 @@ +class AddProjectMergeApproves < ActiveRecord::Migration + def change + add_column :projects, :approvals_before_merge, :integer, null: false, default: 0 + end +end diff --git a/db/migrate/20150707222220_add_approvers_table.rb b/db/migrate/20150707222220_add_approvers_table.rb new file mode 100644 index 00000000000..4af7fe1c5c4 --- /dev/null +++ b/db/migrate/20150707222220_add_approvers_table.rb @@ -0,0 +1,14 @@ +class AddApproversTable < ActiveRecord::Migration + def change + create_table :approvers do |t| + t.integer :target_id, null: false + t.string :target_type + t.integer :user_id, null: false + + t.timestamps + + t.index [:target_id, :target_type] + t.index :user_id + end + end +end diff --git a/db/migrate/20150709134649_add_reset_approvers_to_project.rb b/db/migrate/20150709134649_add_reset_approvers_to_project.rb new file mode 100644 index 00000000000..0a9d6219cb8 --- /dev/null +++ b/db/migrate/20150709134649_add_reset_approvers_to_project.rb @@ -0,0 +1,5 @@ +class AddResetApproversToProject < ActiveRecord::Migration + def change + add_column :projects, :reset_approvers_on_push, :boolean, default: true + end +end diff --git a/db/migrate/20150717155058_rename_reset_approvers.rb b/db/migrate/20150717155058_rename_reset_approvers.rb new file mode 100644 index 00000000000..cf7e74b659f --- /dev/null +++ b/db/migrate/20150717155058_rename_reset_approvers.rb @@ -0,0 +1,5 @@ +class RenameResetApprovers < ActiveRecord::Migration + def change + rename_column :projects, :reset_approvers_on_push, :reset_approvals_on_push + end +end diff --git a/db/migrate/20150731200022_remove_invalid_approvers.rb b/db/migrate/20150731200022_remove_invalid_approvers.rb new file mode 100644 index 00000000000..e9d547d14f2 --- /dev/null +++ b/db/migrate/20150731200022_remove_invalid_approvers.rb @@ -0,0 +1,8 @@ +class RemoveInvalidApprovers < ActiveRecord::Migration + def up + execute("DELETE FROM approvers WHERE user_id = 0") + end + + def down + end +end diff --git a/db/migrate/20150827121444_add_fast_forward_option_to_project.rb b/db/migrate/20150827121444_add_fast_forward_option_to_project.rb new file mode 100644 index 00000000000..7263e624fe5 --- /dev/null +++ b/db/migrate/20150827121444_add_fast_forward_option_to_project.rb @@ -0,0 +1,5 @@ +class AddFastForwardOptionToProject < ActiveRecord::Migration + def change + add_column :projects, :merge_requests_ff_only_enabled, :boolean, default: false + end +end diff --git a/db/migrate/20150827144737_migrate_rebase_feature.rb b/db/migrate/20150827144737_migrate_rebase_feature.rb new file mode 100644 index 00000000000..562c28bc1da --- /dev/null +++ b/db/migrate/20150827144737_migrate_rebase_feature.rb @@ -0,0 +1,11 @@ +class MigrateRebaseFeature < ActiveRecord::Migration + def up + execute %q{UPDATE projects SET merge_requests_ff_only_enabled = TRUE WHERE merge_requests_rebase_enabled IS TRUE} + + remove_column :projects, :merge_requests_rebase_default + end + + def down + add_column :projects, :merge_requests_rebase_default, :boolean, default: true + end +end diff --git a/db/migrate/20150929160851_add_issues_template_to_project.rb b/db/migrate/20150929160851_add_issues_template_to_project.rb new file mode 100644 index 00000000000..1caf6531c2e --- /dev/null +++ b/db/migrate/20150929160851_add_issues_template_to_project.rb @@ -0,0 +1,5 @@ +class AddIssuesTemplateToProject < ActiveRecord::Migration + def change + add_column :projects, :issues_template, :text + end +end diff --git a/db/migrate/20150930110012_add_group_share_lock.rb b/db/migrate/20150930110012_add_group_share_lock.rb new file mode 100644 index 00000000000..78d1a4538f2 --- /dev/null +++ b/db/migrate/20150930110012_add_group_share_lock.rb @@ -0,0 +1,5 @@ +class AddGroupShareLock < ActiveRecord::Migration + def change + add_column :namespaces, :share_with_group_lock, :boolean, default: false + end +end diff --git a/db/migrate/20151007110107_update_group_links.rb b/db/migrate/20151007110107_update_group_links.rb new file mode 100644 index 00000000000..8d2eaaa1fb9 --- /dev/null +++ b/db/migrate/20151007110107_update_group_links.rb @@ -0,0 +1,6 @@ +class UpdateGroupLinks < ActiveRecord::Migration + def change + provider = quote_string(Gitlab::LDAP::Config.providers.first) + execute("UPDATE ldap_group_links SET provider = '#{provider}' WHERE provider IS NULL") + end +end diff --git a/db/migrate/20151012173029_set_jira_service_api_url.rb b/db/migrate/20151012173029_set_jira_service_api_url.rb new file mode 100644 index 00000000000..2af99e0db0b --- /dev/null +++ b/db/migrate/20151012173029_set_jira_service_api_url.rb @@ -0,0 +1,50 @@ +class SetJiraServiceApiUrl < ActiveRecord::Migration + # This migration can be performed online without errors, but some Jira API calls may be missed + # when doing so because api_url is not yet available. + + def build_api_url_from_project_url(project_url, api_version) + # this is the exact logic previously used to build the Jira API URL from project_url + server = URI(project_url) + default_ports = [80, 443].include?(server.port) + server_url = "#{server.scheme}://#{server.host}" + server_url.concat(":#{server.port}") unless default_ports + "#{server_url}/rest/api/#{api_version}" + end + + def get_api_version_from_api_url(api_url) + match = /\/rest\/api\/(?<api_version>\w+)$/.match(api_url) + match && match['api_version'] + end + + def change + reversible do |dir| + select_all("SELECT id, properties FROM services WHERE services.type IN ('JiraService')").each do |jira_service| + id = jira_service["id"] + properties = JSON.parse(jira_service["properties"]) + properties_was = properties.clone + + dir.up do + # remove api_version and set api_url + if properties['api_version'].present? && properties['project_url'].present? + begin + properties['api_url'] ||= build_api_url_from_project_url(properties['project_url'], properties['api_version']) + rescue + # looks like project_url was not a valid URL. Do nothing. + end + end + properties.delete('api_version') if properties.include?('api_version') + end + + dir.down do + # remove api_url and set api_version (default to '2') + properties['api_version'] ||= get_api_version_from_api_url(properties['api_url']) || '2' + properties.delete('api_url') if properties.include?('api_url') + end + + if properties != properties_was + execute("UPDATE services SET properties = '#{quote_string(properties.to_json)}' WHERE id = #{id}") + end + end + end + end +end diff --git a/db/schema.rb b/db/schema.rb index a8e8dfe6bbf..fa53ded5ad0 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -24,6 +24,17 @@ ActiveRecord::Schema.define(version: 20151114113410) do t.datetime "updated_at" end + create_table "appearances", force: true do |t| + t.string "title" + t.text "description" + t.string "logo" + t.integer "updated_by" + t.datetime "created_at" + t.datetime "updated_at" + t.string "dark_logo" + t.string "light_logo" + end + create_table "application_settings", force: true do |t| t.integer "default_projects_limit" t.boolean "signup_enabled" @@ -35,6 +46,7 @@ ActiveRecord::Schema.define(version: 20151114113410) do t.string "home_page_url" t.integer "default_branch_protection", default: 2 t.boolean "twitter_sharing_enabled", default: true + t.text "help_text" t.text "restricted_visibility_levels" t.boolean "version_check_enabled", default: true t.integer "max_attachment_size", default: 10, null: false @@ -51,6 +63,24 @@ ActiveRecord::Schema.define(version: 20151114113410) do t.integer "max_artifacts_size", default: 100, null: false end + create_table "approvals", force: true do |t| + t.integer "merge_request_id", null: false + t.integer "user_id", null: false + t.datetime "created_at" + t.datetime "updated_at" + end + + create_table "approvers", force: true do |t| + t.integer "target_id", null: false + t.string "target_type" + t.integer "user_id", null: false + t.datetime "created_at" + t.datetime "updated_at" + end + + add_index "approvers", ["target_id", "target_type"], name: "index_approvers_on_target_id_and_target_type", using: :btree + add_index "approvers", ["user_id"], name: "index_approvers_on_user_id", using: :btree + create_table "audit_events", force: true do |t| t.integer "author_id", null: false t.string "type", null: false @@ -350,6 +380,28 @@ ActiveRecord::Schema.define(version: 20151114113410) do add_index "forked_project_links", ["forked_to_project_id"], name: "index_forked_project_links_on_forked_to_project_id", unique: true, using: :btree + create_table "git_hooks", force: true do |t| + t.string "force_push_regex" + t.string "delete_branch_regex" + t.string "commit_message_regex" + t.boolean "deny_delete_tag" + t.integer "project_id" + t.datetime "created_at" + t.datetime "updated_at" + t.string "author_email_regex" + t.boolean "member_check", default: false, null: false + t.string "file_name_regex" + t.boolean "is_sample", default: false + t.integer "max_file_size", default: 0 + end + + create_table "historical_data", force: true do |t| + t.date "date", null: false + t.integer "active_user_count" + t.datetime "created_at" + t.datetime "updated_at" + end + create_table "identities", force: true do |t| t.string "extern_uid" t.string "provider" @@ -422,8 +474,17 @@ ActiveRecord::Schema.define(version: 20151114113410) do add_index "labels", ["project_id"], name: "index_labels_on_project_id", using: :btree + create_table "ldap_group_links", force: true do |t| + t.string "cn", null: false + t.integer "group_access", null: false + t.integer "group_id", null: false + t.datetime "created_at" + t.datetime "updated_at" + t.string "provider" + end + create_table "lfs_objects", force: true do |t| - t.string "oid", null: false, unique: true + t.string "oid", null: false t.integer "size", null: false t.datetime "created_at" t.datetime "updated_at" @@ -442,6 +503,12 @@ ActiveRecord::Schema.define(version: 20151114113410) do add_index "lfs_objects_projects", ["project_id"], name: "index_lfs_objects_projects_on_project_id", using: :btree + create_table "licenses", force: true do |t| + t.text "data", null: false + t.datetime "created_at" + t.datetime "updated_at" + end + create_table "members", force: true do |t| t.integer "access_level", null: false t.integer "source_id", null: false @@ -524,15 +591,17 @@ ActiveRecord::Schema.define(version: 20151114113410) do add_index "milestones", ["project_id"], name: "index_milestones_on_project_id", using: :btree create_table "namespaces", force: true do |t| - t.string "name", null: false - t.string "path", null: false + t.string "name", null: false + t.string "path", null: false t.integer "owner_id" t.datetime "created_at" t.datetime "updated_at" t.string "type" - t.string "description", default: "", null: false + t.string "description", default: "", null: false t.string "avatar" - t.boolean "public", default: false + t.boolean "membership_lock", default: false + t.boolean "share_with_group_lock", default: false + t.boolean "public", default: false end add_index "namespaces", ["created_at", "id"], name: "index_namespaces_on_created_at_and_id", using: :btree @@ -611,6 +680,14 @@ ActiveRecord::Schema.define(version: 20151114113410) do add_index "oauth_applications", ["owner_id", "owner_type"], name: "index_oauth_applications_on_owner_id_and_owner_type", using: :btree add_index "oauth_applications", ["uid"], name: "index_oauth_applications_on_uid", unique: true, using: :btree + create_table "project_group_links", force: true do |t| + t.integer "project_id", null: false + t.integer "group_id", null: false + t.datetime "created_at" + t.datetime "updated_at" + t.integer "group_access", default: 30, null: false + end + create_table "project_import_data", force: true do |t| t.integer "project_id" t.text "data" @@ -623,25 +700,31 @@ ActiveRecord::Schema.define(version: 20151114113410) do t.datetime "created_at" t.datetime "updated_at" t.integer "creator_id" - t.boolean "issues_enabled", default: true, null: false - t.boolean "wall_enabled", default: true, null: false - t.boolean "merge_requests_enabled", default: true, null: false - t.boolean "wiki_enabled", default: true, null: false + t.boolean "issues_enabled", default: true, null: false + t.boolean "wall_enabled", default: true, null: false + t.boolean "merge_requests_enabled", default: true, null: false + t.boolean "wiki_enabled", default: true, null: false t.integer "namespace_id" - t.string "issues_tracker", default: "gitlab", null: false + t.string "issues_tracker", default: "gitlab", null: false t.string "issues_tracker_id" - t.boolean "snippets_enabled", default: true, null: false + t.boolean "snippets_enabled", default: true, null: false t.datetime "last_activity_at" t.string "import_url" - t.integer "visibility_level", default: 0, null: false - t.boolean "archived", default: false, null: false + t.integer "visibility_level", default: 0, null: false + t.boolean "archived", default: false, null: false t.string "avatar" t.string "import_status" - t.float "repository_size", default: 0.0 - t.integer "star_count", default: 0, null: false + t.float "repository_size", default: 0.0 + t.text "merge_requests_template" + t.integer "star_count", default: 0, null: false + t.boolean "merge_requests_rebase_enabled", default: false t.string "import_type" t.string "import_source" - t.integer "commit_count", default: 0 + t.integer "approvals_before_merge", default: 0, null: false + t.boolean "reset_approvals_on_push", default: true + t.integer "commit_count", default: 0 + t.boolean "merge_requests_ff_only_enabled", default: false + t.text "issues_template" end add_index "projects", ["created_at", "id"], name: "index_projects_on_created_at_and_id", using: :btree @@ -756,12 +839,12 @@ ActiveRecord::Schema.define(version: 20151114113410) do add_index "tags", ["name"], name: "index_tags_on_name", unique: true, using: :btree create_table "users", force: true do |t| - t.string "email", default: "", null: false - t.string "encrypted_password", default: "", null: false + t.string "email", default: "", null: false + t.string "encrypted_password", default: "", null: false t.string "reset_password_token" t.datetime "reset_password_sent_at" t.datetime "remember_created_at" - t.integer "sign_in_count", default: 0 + t.integer "sign_in_count", default: 0 t.datetime "current_sign_in_at" t.datetime "last_sign_in_at" t.string "current_sign_in_ip" @@ -769,22 +852,22 @@ ActiveRecord::Schema.define(version: 20151114113410) do t.datetime "created_at" t.datetime "updated_at" t.string "name" - t.boolean "admin", default: false, null: false - t.integer "projects_limit", default: 10 - t.string "skype", default: "", null: false - t.string "linkedin", default: "", null: false - t.string "twitter", default: "", null: false + t.boolean "admin", default: false, null: false + t.integer "projects_limit", default: 10 + t.string "skype", default: "", null: false + t.string "linkedin", default: "", null: false + t.string "twitter", default: "", null: false t.string "authentication_token" - t.integer "theme_id", default: 1, null: false + t.integer "theme_id", default: 1, null: false t.string "bio" - t.integer "failed_attempts", default: 0 + t.integer "failed_attempts", default: 0 t.datetime "locked_at" t.string "username" - t.boolean "can_create_group", default: true, null: false - t.boolean "can_create_team", default: true, null: false + t.boolean "can_create_group", default: true, null: false + t.boolean "can_create_team", default: true, null: false t.string "state" - t.integer "color_scheme_id", default: 1, null: false - t.integer "notification_level", default: 1, null: false + t.integer "color_scheme_id", default: 1, null: false + t.integer "notification_level", default: 1, null: false t.datetime "password_expires_at" t.integer "created_by_id" t.datetime "last_credential_check_at" @@ -793,22 +876,23 @@ ActiveRecord::Schema.define(version: 20151114113410) do t.datetime "confirmed_at" t.datetime "confirmation_sent_at" t.string "unconfirmed_email" - t.boolean "hide_no_ssh_key", default: false - t.string "website_url", default: "", null: false + t.boolean "hide_no_ssh_key", default: false + t.string "website_url", default: "", null: false + t.datetime "admin_email_unsubscribed_at" t.string "notification_email" - t.boolean "hide_no_password", default: false - t.boolean "password_automatically_set", default: false + t.boolean "hide_no_password", default: false + t.boolean "password_automatically_set", default: false t.string "location" t.string "encrypted_otp_secret" t.string "encrypted_otp_secret_iv" t.string "encrypted_otp_secret_salt" - t.boolean "otp_required_for_login", default: false, null: false + t.boolean "otp_required_for_login", default: false, null: false t.text "otp_backup_codes" - t.string "public_email", default: "", null: false - t.integer "dashboard", default: 0 - t.integer "project_view", default: 0 + t.string "public_email", default: "", null: false + t.integer "dashboard", default: 0 + t.integer "project_view", default: 0 t.integer "consumed_timestep" - t.integer "layout", default: 0 + t.integer "layout", default: 0 end add_index "users", ["admin"], name: "index_users_on_admin", using: :btree @@ -843,6 +927,7 @@ ActiveRecord::Schema.define(version: 20151114113410) do t.boolean "issues_events", default: false, null: false t.boolean "merge_requests_events", default: false, null: false t.boolean "tag_push_events", default: false + t.integer "group_id" t.boolean "note_events", default: false, null: false t.boolean "enable_ssl_verification", default: true end diff --git a/doc/README.md b/doc/README.md index 0f6866475f7..f70c385448e 100644 --- a/doc/README.md +++ b/doc/README.md @@ -36,7 +36,12 @@ ## Administrator documentation +- [Audit Events](administration/audit_events.md) Check how user access changed in projects and groups. +- [Changing the appearance of the login page](customization/branded_login_page.md) Make the login page branded for your GitLab instance. - [Custom git hooks](hooks/custom_hooks.md) Custom git hooks (on the filesystem) for when web hooks aren't enough. +- [Email](tools/email.md) Email GitLab users from GitLab +- [Git Hooks](git_hooks/git_hooks.md) Advanced push rules for your project. +- [Help message](customization/help_message.md) Set information about administrators of your GitLab instance. - [Install](install/README.md) Requirements, directory structures and installation from source. - [Integration](integration/README.md) How to integrate with systems such as JIRA, Redmine, LDAP and Twitter. - [Issue closing](customization/issue_closing.md) Customize how to close an issue from commit messages. diff --git a/doc/administration/audit_events.md b/doc/administration/audit_events.md new file mode 100644 index 00000000000..6fc10c1bed9 --- /dev/null +++ b/doc/administration/audit_events.md @@ -0,0 +1,30 @@ +# Audit Events + +GitLab Enterprise Edition offers a way to view the changes made within the GitLab server as a help to system administrators. + +GitLab system administrators can also take advantage of the logs located on the filesystem, see [the logs system documentation](logs/logs.md) for more details. + + +# Security Events + +| Security Event | Description | +|--------------------------------|--------------------------------------------------------------------------------------------------| +| User added to group or project | Notes the author of the change, target user | +| User permission changed | Notes the author of the change, original permission and new permission, target user | + + +# Audit Events in Project + +To view the Audit Events user needs to have enough permissions to view the project Settings page. + +Navigate to Project->Settings->Audit Events to view the Audit Events: + + + +# Audit Events in Group + +To view the Audit Events user needs to have enough permissions to view the group Settings page. + +Navigate to Group->Settings->Audit Events to view the Audit Events: + + diff --git a/doc/administration/audit_events_group.png b/doc/administration/audit_events_group.png Binary files differnew file mode 100644 index 00000000000..29bff41e5fb --- /dev/null +++ b/doc/administration/audit_events_group.png diff --git a/doc/administration/audit_events_project.png b/doc/administration/audit_events_project.png Binary files differnew file mode 100644 index 00000000000..bd4502361b3 --- /dev/null +++ b/doc/administration/audit_events_project.png diff --git a/doc/api/groups.md b/doc/api/groups.md index 0b9f6406d8d..984c46fce20 100644 --- a/doc/api/groups.md +++ b/doc/api/groups.md @@ -1,192 +1,236 @@ -# Groups
-
-## List project groups
-
-Get a list of groups. (As user: my groups, as admin: all groups)
-
-```
-GET /groups
-```
-
-```json
-[
- {
- "id": 1,
- "name": "Foobar Group",
- "path": "foo-bar",
- "description": "An interesting group"
- }
-]
-```
-
-You can search for groups by name or path, see below.
-
-## Details of a group
-
-Get all details of a group.
-
-```
-GET /groups/:id
-```
-
-Parameters:
-
-- `id` (required) - The ID or path of a group
-
-## New group
-
-Creates a new project group. Available only for users who can create groups.
-
-```
-POST /groups
-```
-
-Parameters:
-
-- `name` (required) - The name of the group
-- `path` (required) - The path of the group
-- `description` (optional) - The group's description
-
-## Transfer project to group
-
-Transfer a project to the Group namespace. Available only for admin
-
-```
-POST /groups/:id/projects/:project_id
-```
-
-Parameters:
-
-- `id` (required) - The ID or path of a group
-- `project_id` (required) - The ID of a project
-
-## Remove group
-
-Removes group with all projects inside.
-
-```
-DELETE /groups/:id
-```
-
-Parameters:
-
-- `id` (required) - The ID or path of a user group
-
-## Search for group
-
-Get all groups that match your string in their name or path.
-
-```
-GET /groups?search=foobar
-```
-
-```json
-[
- {
- "id": 1,
- "name": "Foobar Group",
- "path": "foo-bar",
- "description": "An interesting group"
- }
-]
-```
-
-## Group members
-
-**Group access levels**
-
-The group access levels are defined in the `Gitlab::Access` module. Currently, these levels are recognized:
-
-```
-GUEST = 10
-REPORTER = 20
-DEVELOPER = 30
-MASTER = 40
-OWNER = 50
-```
-
-### List group members
-
-Get a list of group members viewable by the authenticated user.
-
-```
-GET /groups/:id/members
-```
-
-```json
-[
- {
- "id": 1,
- "username": "raymond_smith",
- "email": "ray@smith.org",
- "name": "Raymond Smith",
- "state": "active",
- "created_at": "2012-10-22T14:13:35Z",
- "access_level": 30
- },
- {
- "id": 2,
- "username": "john_doe",
- "email": "joh@doe.org",
- "name": "John Doe",
- "state": "active",
- "created_at": "2012-10-22T14:13:35Z",
- "access_level": 30
- }
-]
-```
-
-### Add group member
-
-Adds a user to the list of group members.
-
-```
-POST /groups/:id/members
-```
-
-Parameters:
-
-- `id` (required) - The ID or path of a group
-- `user_id` (required) - The ID of a user to add
-- `access_level` (required) - Project access level
-
-### Edit group team member
-
-Updates a group team member to a specified access level.
-
-```
-PUT /groups/:id/members/:user_id
-```
-
-Parameters:
-
-- `id` (required) - The ID of a group
-- `user_id` (required) - The ID of a group member
-- `access_level` (required) - Project access level
-
-### Remove user team member
-
-Removes user from user team.
-
-```
-DELETE /groups/:id/members/:user_id
-```
-
-Parameters:
-
-- `id` (required) - The ID or path of a user group
-- `user_id` (required) - The ID of a group member
-
-## Namespaces in groups
-
-By default, groups only get 20 namespaces at a time because the API results are paginated.
-
-To get more (up to 100), pass the following as an argument to the API call:
-```
-/groups?per_page=100
-```
-
-And to switch pages add:
-```
-/groups?per_page=100&page=2
-```
\ No newline at end of file +# Groups + +## List project groups + +Get a list of groups. (As user: my groups, as admin: all groups) + +``` +GET /groups +``` + +```json +[ + { + "id": 1, + "name": "Foobar Group", + "path": "foo-bar", + "description": "An interesting group" + } +] +``` + +You can search for groups by name or path, see below. + +## Details of a group + +Get all details of a group. + +``` +GET /groups/:id +``` + +Parameters: + +- `id` (required) - The ID or path of a group + +## New group + +Creates a new project group. Available only for users who can create groups. + +``` +POST /groups +``` + +Parameters: + +- `name` (required) - The name of the group +- `path` (required) - The path of the group +- `description` (optional) - The group's description +- `membership_lock` (optional, boolean) - Prevent adding new members to project membership within this group +- `share_with_group_lock` (optional, boolean) - Prevent sharing a project with another group within this group + +## Update group + +Updates a project group. Available only for users who can manage this group. + +``` +PUT /groups/:id +``` + +Parameters: + +- `name` (required) - The name of the group +- `path` (required) - The path of the group +- `description` (optional) - The group's description +- `membership_lock` (optional, boolean) - Prevent adding new members to project membership within this group +- `share_with_group_lock` (optional, boolean) - Prevent sharing a project with another group within this group + +## Transfer project to group + +Transfer a project to the Group namespace. Available only for admin + +``` +POST /groups/:id/projects/:project_id +``` + +Parameters: + +- `id` (required) - The ID or path of a group +- `project_id` (required) - The ID of a project + +## Remove group + +Removes group with all projects inside. + +``` +DELETE /groups/:id +``` + +Parameters: + +- `id` (required) - The ID or path of a user group + +## Search for group + +Get all groups that match your string in their name or path. + +``` +GET /groups?search=foobar +``` + +```json +[ + { + "id": 1, + "name": "Foobar Group", + "path": "foo-bar", + "description": "An interesting group" + } +] +``` + +## Group members + +**Group access levels** + +The group access levels are defined in the `Gitlab::Access` module. Currently, these levels are recognized: + +``` +GUEST = 10 +REPORTER = 20 +DEVELOPER = 30 +MASTER = 40 +OWNER = 50 +``` + +### List group members + +Get a list of group members viewable by the authenticated user. + +``` +GET /groups/:id/members +``` + +```json +[ + { + "id": 1, + "username": "raymond_smith", + "email": "ray@smith.org", + "name": "Raymond Smith", + "state": "active", + "created_at": "2012-10-22T14:13:35Z", + "access_level": 30 + }, + { + "id": 2, + "username": "john_doe", + "email": "joh@doe.org", + "name": "John Doe", + "state": "active", + "created_at": "2012-10-22T14:13:35Z", + "access_level": 30 + } +] +``` + +### Add group member + +Adds a user to the list of group members. + +``` +POST /groups/:id/members +``` + +Parameters: + +- `id` (required) - The ID or path of a group +- `user_id` (required) - The ID of a user to add +- `access_level` (required) - Project access level + +### Edit group team member + +Updates a group team member to a specified access level. + +``` +PUT /groups/:id/members/:user_id +``` + +Parameters: + +- `id` (required) - The ID of a group +- `user_id` (required) - The ID of a group member +- `access_level` (required) - Project access level + +### Remove user team member + +Removes user from user team. + +``` +DELETE /groups/:id/members/:user_id +``` + +Parameters: + +- `id` (required) - The ID or path of a user group +- `user_id` (required) - The ID of a group member + +### Add LDAP group link + +Adds LDAP group link + +``` +POST /groups/:id/ldap_group_links +``` + +Parameters: + +- `id` (required) - The ID of a group +- `cn` (required) - The CN of a LDAP group +- `group_access` (required) - Minimum access level for members of the LDAP group +- `provider` (required) - LDAP provider for the LDAP group (when using several providers) + +### Delete LDAP group link + +Deletes a LDAP group link + +``` +DELETE /groups/:id/ldap_group_links/:cn +``` + +Parameters: + +- `id` (required) - The ID of a group +- `cn` (required) - The CN of a LDAP group + +Deletes a LDAP group link for a specific LDAP provider + +``` +DELETE /groups/:id/ldap_group_links/:provider/:cn +``` + +Parameters: + +- `id` (required) - The ID of a group +- `cn` (required) - The CN of a LDAP group +- `provider` (required) - Name of a LDAP provider diff --git a/doc/api/oauth2.md b/doc/api/oauth2.md index d416a826f79..606bbebd607 100644 --- a/doc/api/oauth2.md +++ b/doc/api/oauth2.md @@ -35,7 +35,7 @@ Where REDIRECT_URI is the URL in your app where users will be sent after authori To request the access token, you should use the returned code and exchange it for an access token. To do that you can use any HTTP client. In this case, I used rest-client: ``` -parameters = 'client_id=APP_ID&client_secret=APP_SECRET&code=RETURNED_CODE&grant_type=AUTHORIZATION_CODE&redirect_uri=REDIRECT_URI' +parameters = 'client_id=APP_ID&client_secret=APP_SECRET&code=RETURNED_CODE&grant_type=authorization_code&redirect_uri=REDIRECT_URI' RestClient.post 'http://localhost:3000/oauth/token', parameters # The response will be @@ -99,4 +99,4 @@ For testing you can use the oauth2 ruby gem: client = OAuth2::Client.new('the_client_id', 'the_client_secret', :site => "http://example.com") access_token = client.password.get_token('user@example.com', 'sekret') puts access_token.token -``` +```
\ No newline at end of file diff --git a/doc/api/projects.md b/doc/api/projects.md index 755cc6525c2..8c62ead51fd 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -483,6 +483,20 @@ Revoking team membership for a user who is not currently a team member is consid Please note that the returned JSON currently differs slightly. Thus you should not rely on the returned JSON structure. +### Share project with group + +Allow to share project with group. + +``` +POST /projects/:id/share +``` + +Parameters: + +- `id` (required) - The ID of a project +- `group_id` (required) - The ID of a group +- `group_access` (required) - Level of permissions for sharing + ## Hooks Also called Project Hooks and Webhooks. @@ -729,3 +743,72 @@ Parameters: - `page` (optional) - the page to retrieve - `order_by` (optional) - Return requests ordered by `id`, `name`, `created_at` or `last_activity_at` fields - `sort` (optional) - Return requests sorted in `asc` or `desc` order + +## Git Hooks (EE only) + +### Show project git hooks + +Get a project git hook. + +``` +GET /projects/:id/git_hooks +``` + +Parameters: + +- `id` (required) - The ID of a project + + +```json +{ + "id": 1, + "project_id": 3, + "commit_message_regex": "Fixes \d +\", + "deny_delete_tag": false, + "created_at": "2012-10-12T17:04:47Z" +} +``` + +### Add project git hook + +Adds a git hook to a specified project. + +``` +POST /projects/:id/git_hooks +``` + +Parameters: + +- `id` (required) - The ID of a project +- `deny_delete_tag` - Do not allow users to remove git tags with git push +- `commit_message_regex` - Commit message regex + +### Edit project git hook + +Edits a git hook for a specified project. + +``` +PUT /projects/:id/git_hooks +``` + +Parameters: + +- `id` (required) - The ID of a project +- `deny_delete_tag` - Do not allow users to remove git tags with git push +- `commit_message_regex` - Commit message regex + +### Delete project git hook + +Removes a git hook from a project. This is an idempotent method and can be called multiple times. +Either the git hook is available or not. + +``` +DELETE /projects/:id/git_hooks +``` + +Parameters: + +- `id` (required) - The ID of a project + +Note the JSON response differs if the hook is available or not. If the project hook +is available before it is returned in the JSON response or an empty response is returned. diff --git a/doc/api/users.md b/doc/api/users.md index 7ba2db248ff..cc24696d2cf 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -2,6 +2,8 @@ ## List users +Active users = Total accounts - Blocked users + Get a list of users. This function takes pagination parameters `page` and `per_page` to restrict the list of users. diff --git a/doc/customization/branded_login_page.md b/doc/customization/branded_login_page.md new file mode 100644 index 00000000000..0821ab2c7cd --- /dev/null +++ b/doc/customization/branded_login_page.md @@ -0,0 +1,19 @@ +# Changing the appearance of the login page + +GitLab Enterprise Edition offers a way to put your company recognizible identity on the login page of your GitLab server and make it a branded login page. + +By default, Enterprise Edition page shows GitLab logo and description + + + +## Changing the appearance of the login page + +Navigate to the  and go to the Appearance page. + +Fill in the required details like Title, Description and upload the company logo. + + + +After saving the page, your GitLab login page will have the details you filled in: + +
\ No newline at end of file diff --git a/doc/customization/branded_login_page/admin_area.png b/doc/customization/branded_login_page/admin_area.png Binary files differnew file mode 100644 index 00000000000..28e64623dee --- /dev/null +++ b/doc/customization/branded_login_page/admin_area.png diff --git a/doc/customization/branded_login_page/appearance.png b/doc/customization/branded_login_page/appearance.png Binary files differnew file mode 100644 index 00000000000..18c021c2221 --- /dev/null +++ b/doc/customization/branded_login_page/appearance.png diff --git a/doc/customization/branded_login_page/company_login_page.png b/doc/customization/branded_login_page/company_login_page.png Binary files differnew file mode 100644 index 00000000000..e64bf66ecce --- /dev/null +++ b/doc/customization/branded_login_page/company_login_page.png diff --git a/doc/customization/branded_login_page/default_login_page.png b/doc/customization/branded_login_page/default_login_page.png Binary files differnew file mode 100644 index 00000000000..0a757bca8a6 --- /dev/null +++ b/doc/customization/branded_login_page/default_login_page.png diff --git a/doc/customization/help_message.md b/doc/customization/help_message.md new file mode 100644 index 00000000000..831b871fecb --- /dev/null +++ b/doc/customization/help_message.md @@ -0,0 +1,13 @@ +# GitLab server administrator information + +In larger organizations it is useful to have information about who has the responsibility of maintaining the company GitLab server. + +Navigate to the admin area and go to the Settings page. + +Under `Help text` fill in the required information about the person(s) administering GitLab. + + + +After saving the page this information will be shown on the GitLab login page and on the GitLab help page. + + diff --git a/doc/customization/help_message/help_text.png b/doc/customization/help_message/help_text.png Binary files differnew file mode 100644 index 00000000000..47a4f717589 --- /dev/null +++ b/doc/customization/help_message/help_text.png diff --git a/doc/customization/help_message/help_text_on_help_page.png b/doc/customization/help_message/help_text_on_help_page.png Binary files differnew file mode 100644 index 00000000000..6b5c6183d27 --- /dev/null +++ b/doc/customization/help_message/help_text_on_help_page.png diff --git a/doc/development/architecture.md b/doc/development/architecture.md index c00d290371e..39fa89aac20 100644 --- a/doc/development/architecture.md +++ b/doc/development/architecture.md @@ -4,7 +4,7 @@ There are two editions of GitLab: [Enterprise Edition](https://about.gitlab.com/gitlab-ee/) (EE) and [Community Edition](https://about.gitlab.com/gitlab-ce/) (CE). GitLab CE is delivered via git from the [gitlabhq repository](https://gitlab.com/gitlab-org/gitlab-ce/tree/master). New versions of GitLab are released in stable branches and the master branch is for bleeding edge development. -EE releases are available not long after CE releases. To obtain the GitLab EE there is a [repository at gitlab.com](https://gitlab.com/subscribers/gitlab-ee). For more information about the release process see the section 'New versions and upgrading' in the readme. +EE releases are available not long after CE releases. To obtain GitLab EE there is a [repository at gitlab.com](https://gitlab.com/gitlab-org/gitlab-ee). For more information about the release process see the section 'New versions and upgrading' in the readme. Both EE and CE require an add-on component called gitlab-shell. It is obtained from the [gitlab-shell repository](https://gitlab.com/gitlab-org/gitlab-shell/tree/master). New versions are usually tags but staying on the master branch will give you the latest stable version. New releases are generally around the same time as GitLab CE releases with exception for informal security updates deemed critical. diff --git a/doc/development/ee_features.md b/doc/development/ee_features.md new file mode 100644 index 00000000000..b6d8ebe3ded --- /dev/null +++ b/doc/development/ee_features.md @@ -0,0 +1,15 @@ +# Guidelines for implementing Enterprise Edition feature + +## Write the code and the tests + +Implement the wanted feature. +Implemented feature needs to have the full test coverage. +For now, exception is the code that needs to query a LDAP server. + +## Write the documentation + +Any feature needs to be well documented. Add the documentation to `/doc` directory, describe the main use of the newly implemented feature and, if applicable, add screenshots. + +## Submit the MR to `about.gitlab.com` + +Submit the MR to [about.gitlab.com site repository](https://gitlab.com/gitlab-com/www-gitlab-com) to add the new feature to the [EE feature comparison page](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/source/gitlab-ee/index.html) diff --git a/doc/git_hooks/git_hooks.md b/doc/git_hooks/git_hooks.md new file mode 100644 index 00000000000..4b0577c1317 --- /dev/null +++ b/doc/git_hooks/git_hooks.md @@ -0,0 +1,27 @@ +# Git Hooks + +Sometimes you need additional control over pushes to your repository. +GitLab already offers protected branches. +But there are cases when you need some specific rules like preventing git tag removal or enforcing a special format for commit messages. +GitLab Enterprise Edition offers a user-friendly interface for such cases. + +Git hooks are defined per project so you can have different rules applied to different projects depends on your needs. +Git hooks settings can be found at Project settings -> Git Hooks page. + + +## How to use + +Let's assume you have the following requirements for your workflow: + +* every commit should reference a reference JIRA issue. For example: `Refactored css. Fixes JIRA-123. ` +* users should not be able to remove git tags with `git push` + +All you need to do is write simple regular expression that requires mention of JIRA issue in a commit message. +It can be something like this `/JIRA\-\d+/`. +Just paste regular expression into commit message textfield(without start and ending slash) and save changes. +See the screenshot below: + + + +Now when a user tries to push a commit like `Bugfix` - their push will be declined. +And pushing commit with message like `Bugfix according to JIRA-123` will be accepted.
\ No newline at end of file diff --git a/doc/git_hooks/git_hooks.png b/doc/git_hooks/git_hooks.png Binary files differnew file mode 100644 index 00000000000..c8bc55e53c8 --- /dev/null +++ b/doc/git_hooks/git_hooks.png diff --git a/doc/install/README.md b/doc/install/README.md index 239f5f301ec..79ee751955c 100644 --- a/doc/install/README.md +++ b/doc/install/README.md @@ -4,3 +4,4 @@ - [Requirements](requirements.md) - [Structure](structure.md) - [Database MySQL](database_mysql.md) +- [LDAP](ldap.md) diff --git a/doc/install/installation.md b/doc/install/installation.md index 8028e51dbcd..9ef38dd06c0 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -211,9 +211,9 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da ### Clone the Source # Clone GitLab repository - sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-1-stable gitlab + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-1-stable-ee gitlab -**Note:** You can change `8-1-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! +**Note:** You can change `8-1-stable-ee` to `master` if you want the *bleeding edge* version, but never install master on a production server! ### Configure It diff --git a/doc/install/ldap.md b/doc/install/ldap.md new file mode 100644 index 00000000000..8236d61ba14 --- /dev/null +++ b/doc/install/ldap.md @@ -0,0 +1,60 @@ +# Link LDAP Groups +You can link LDAP groups with GitLab groups. +It gives you ability to automatically add/remove users from GitLab groups based on LDAP groups membership. + +How it works: +1. We retrieve user ldap groups +2. We find corresponding GitLab groups +3. We add user to GitLab groups +4. We remove user from GitLab groups if user has no membership in LDAP groups + +In order to use LDAP groups feature: + +1. Edit gitlab.yml config LDAP sections. +2. Visit group settings -> LDAP tab +3. Edit LDAP cn and access level for gitlab group +4. Setup LDAP group members + + +Example of LDAP section from gitlab.yml + +``` + # + # 2. Auth settings + # ========================== + + ## LDAP settings + ldap: + enabled: true + host: 'localhost' + base: 'ou=People,dc=gitlab,dc=local' + group_base: 'ou=Groups,dc=gitlab,dc=local' + port: 389 + uid: 'uid' +``` + + +# Test whether LDAP group functionality is configured correctly + +You need a non-LDAP admin user (such as the default admin@local.host), an LDAP user (e.g. Mary) and an LDAP group to which Mary belongs (e.g. Developers). + +1. As the admin, create a new group 'Developers' in GitLab and associate it with the Developers LDAP group at gitlab.example.com/admin/groups/developers/edit . +2. Log in as Mary. +3. Verify that Mary is now a member of the Developers group in GitLab. + +If you get an error message when logging in as Mary, double-check your `group_base` setting in `config/gitlab.yml`. + + +# Debug LDAP user filter with ldapsearch + +This example uses [ldapsearch](http://www.openldap.org/software/man.cgi?query=ldapsearch&apropos=0&sektion=0&manpath=OpenLDAP+2.0-Release&format=html) and assumes you are using ActiveDirectory. + +The following query returns the login names of the users that will be allowed to log in to GitLab if you configure your own `user_filter`. + +```bash +ldapsearch -H ldaps://$host:$port -D "$bind_dn" -y bind_dn_password.txt -b "$base" "(&(ObjectClass=User)($user_filter))" sAMAccountName +``` + +- `$var` refers to a variable from the `ldap` section of your `config/gitlab.yml` https://gitlab.com/gitlab-org/gitlab-ee/blob/master/config/gitlab.yml.example#L100; +- Replace `ldaps://` with `ldap://` if you are using the `plain` authentication method; +- We are assuming the password for the `bind_dn` user is in `bind_dn_password.txt`. diff --git a/doc/integration/README.md b/doc/integration/README.md index eff39a626ae..650134479d0 100644 --- a/doc/integration/README.md +++ b/doc/integration/README.md @@ -4,11 +4,14 @@ GitLab integrates with multiple third-party services to allow external issue tra See the documentation below for details on how to configure these services. -- [External issue tracker](external-issue-tracker.md) Redmine, JIRA, etc. +- [Jira](jira.md) Integrate with the JIRA issue tracker +- [External issue tracker](external-issue-tracker.md) Redmine, bugzilla, etc. - [LDAP](ldap.md) Set up sign in via LDAP +- [Jenkins](jenkins.md) Integrate with the Jenkins CI - [OmniAuth](omniauth.md) Sign in via Twitter, GitHub, GitLab, and Google via OAuth. - [SAML](saml.md) Configure GitLab as a SAML 2.0 Service Provider - [Slack](slack.md) Integrate with the Slack chat service +- [Kerberos](kerberos.md) Integrate with Kerberos - [OAuth2 provider](oauth_provider.md) OAuth2 application creation - [Gmail actions buttons](gmail_action_buttons_for_gitlab.md) Adds GitLab actions to messages diff --git a/doc/integration/github.md b/doc/integration/github.md index b64501c2aaa..50eb22ca58c 100644 --- a/doc/integration/github.md +++ b/doc/integration/github.md @@ -60,11 +60,25 @@ GitHub will generate an application ID and secret key for you to use. For installation from source: + For GitHub.com: + + ``` + - { name: 'github', app_id: 'YOUR_APP_ID', + app_secret: 'YOUR_APP_SECRET', + args: { scope: 'user:email' } } + ``` + + + For GitHub Enterprise: + ``` - { name: 'github', app_id: 'YOUR_APP_ID', app_secret: 'YOUR_APP_SECRET', + url: "https://github.example.com/", args: { scope: 'user:email' } } ``` + + __Replace `https://github.example.com/` with your GitHub URL__ 1. Change 'YOUR_APP_ID' to the client ID from the GitHub application page from step 7. diff --git a/doc/integration/jenkins.md b/doc/integration/jenkins.md new file mode 100644 index 00000000000..79eaba56557 --- /dev/null +++ b/doc/integration/jenkins.md @@ -0,0 +1,83 @@ +# Jenkins CI integration + +GitLab can be configured to interact with Jenkins + +Integration includes: + +* Trigger Jenkins build after push to repo +* Show build status on Merge Request page + +Requirements: + +* Jenkins GitLab Hook plugin +* git clone access for Jenkins from GitLab repo (via ssh key) + +## Jenkins + +1. Install GitLab Hook plugin +2. Setup jenkins project + + + + +## GitLab + + +### Read access to repository + +Jenkins need read access to GitLab repository. We already specified private key to use in Jenkins. Now we need to add public key to GitLab project + + + + +### Jenkins service + +Now navigate to GitLab services page and activate Jenkins + + + +Done! Now when you push to GitLab - it will create a build for Jenkins. +And also you will be able to see merge request build status with a link to the Jenkins build. + +### Multi-project Configuration + +The GitLab Hook plugin in Jenkins supports the automatic creation of a project +for each feature branch. After configuration GitLab will trigger feature branch +builds and a corresponding project will be created in Jenkins. + +Configure the GitLab Hook plugin in Jenkins. Go to 'Manage Jenkins' and then +'Configure System'. Find the 'GitLab Web Hook' section and configure as shown below. + + + +In the Jenkins service in GitLab, check the 'Multi-project setup enabled?'. + + + +### Mark unstable build as passing + +When using some plugins in Jenkins, an unstable build status will result when +tests are not passing. In these cases the unstable status in Jenkins should +register as a failure in GitLab on the merge request page. In other cases you +may not want an unstable status to display as a build failure in GitLab. Control +this behavior using the 'Should unstable builds be treated as passing?' setting +in the Jenkins service in GitLab. + +When checked, unstable builds will display as green or passing in GitLab. By +default unstable builds display in GitLab as red or failed. + + + +## Development + +An explanation of how this works in case anyone want to improve it or develop this service for another CI tool. +In GitLab there is no database table that lists the commits, these are always read from the repository. +Therefore it is not possible to mark the build status of a commit in GitLab. +Actually we believe this information should be stored in a single place, the CI tool itself. +To show this information in a merge request you make a project service in GitLab. +This project service does a (JSON) query to a url of the CI tool with the SHA1 of the commit. +The project service builds this url and payload based on project service settings and knowlegde of the CI tool. +The response is parsed to give a response in GitLab (success/failed/pending). +All this happens with AJAX requests on the merge request page. +The Jenkins project service code is only available in GitLab EE. +The GitLab CI project service code is available in the GitLab CE codebase. diff --git a/doc/integration/jenkins_gitlab_deploy.png b/doc/integration/jenkins_gitlab_deploy.png Binary files differnew file mode 100644 index 00000000000..8a60a6c9966 --- /dev/null +++ b/doc/integration/jenkins_gitlab_deploy.png diff --git a/doc/integration/jenkins_gitlab_service.png b/doc/integration/jenkins_gitlab_service.png Binary files differnew file mode 100644 index 00000000000..1dfe3b324b3 --- /dev/null +++ b/doc/integration/jenkins_gitlab_service.png diff --git a/doc/integration/jenkins_multiproject_configuration.png b/doc/integration/jenkins_multiproject_configuration.png Binary files differnew file mode 100644 index 00000000000..92f33d3ed84 --- /dev/null +++ b/doc/integration/jenkins_multiproject_configuration.png diff --git a/doc/integration/jenkins_multiproject_enabled.png b/doc/integration/jenkins_multiproject_enabled.png Binary files differnew file mode 100644 index 00000000000..daed0ad44d3 --- /dev/null +++ b/doc/integration/jenkins_multiproject_enabled.png diff --git a/doc/integration/jenkins_project.png b/doc/integration/jenkins_project.png Binary files differnew file mode 100644 index 00000000000..41d756676e5 --- /dev/null +++ b/doc/integration/jenkins_project.png diff --git a/doc/integration/jenkins_unstable_passing.png b/doc/integration/jenkins_unstable_passing.png Binary files differnew file mode 100644 index 00000000000..3c35989c107 --- /dev/null +++ b/doc/integration/jenkins_unstable_passing.png diff --git a/doc/integration/jira.md b/doc/integration/jira.md new file mode 100644 index 00000000000..c4a4bc5dcaf --- /dev/null +++ b/doc/integration/jira.md @@ -0,0 +1,113 @@ +# GitLab Jira integration + +GitLab can be configured to interact with Jira. +Configuration happens via username and password. +Connecting to a Jira server via CAS is not possible. + +Each project can be configured to connect to a different Jira instance, configuration is explained [here](#configuration). +If you have one Jira instance you can pre-fill the settings page with a default template. To configure the template [see external issue tracker document](external-issue-tracker.md#service-template)). + +Once the project is connected to Jira, you can reference and close the issues in Jira directly from GitLab. This functionality is only available in GitLab Enterprise Edition as described in this document. + + +## Table of Contents + +* [Referencing Jira Issues from GitLab](#referencing-jira-issues) +* [Closing Jira Issues from GitLab](#closing-jira-issues) +* [Configuration](#configuration) + +### Referencing Jira Issues + +When GitLab project has Jira issue tracker configured and enabled, mentioning Jira issue in GitLab will automatically add a comment in Jira issue with the link back to GitLab. This means that in comments in merge requests and commits referencing an issue, eg. `PROJECT-7`, will add a comment in Jira issue in the format: + + +``` + USER mentioned this issue in LINK_TO_THE_MENTION +``` + +* `USER` A user that mentioned the issue. This is the link to the user profile in GitLab. +* `LINK_TO_THE_MENTION` Link to the origin of mention with a name of the entity where Jira issue was mentioned. +Can be commit or merge request. + + + + + +### Closing Jira Issues + +Jira issues can be closed directly from GitLab by using trigger words, eg. `Resolves PROJECT-1`, `Closes PROJECT-1` or `Fixes PROJECT-1`, in commits and merge requests. +When a commit which contains the trigger word in the commit message is pushed, GitLab will add a comment in the mentioned Jira issue. + +For example, for project named PROJECT in Jira, we implemented a new feature and created a merge request in GitLab. + +This feature was requested in Jira issue PROJECT-7. Merge request in GitLab contains the improvement and in merge request description we say that this merge request `Closes PROJECT-7` issue. + +Once this merge request is merged, Jira issue will be automatically closed with a link to the commit that resolved the issue. + + + + + + + +## Configuration + +### Configuring JIRA + +We need to create a user in JIRA which will have access to all projects that need to integrate with GitLab. +Login to your JIRA instance as admin and under Administration go to User Management and create a new user. +As an example, we'll create a user named `gitlab` and add it to `jira-developers` group. + +**It is important that the user `gitlab` has write-access to projects in JIRA** + +### Configuring GitLab + +### GitLab 7.8 EE and up with JIRA v6.x + +To enable JIRA integration in a project, navigate to the project Settings page and go to Services. Here you will find JIRA. + +Fill in the required details on the page: + + + +* `description` A name for the issue tracker (to differentiate between instances, for instance). +* `project url` The URL to the JIRA project which is being linked to this GitLab project. +* `issues url` The URL to the JIRA project issues overview for the project that is linked to this GitLab project. +* `new issue url` This is the URL to create a new issue in JIRA for the project linked to this GitLab project. +* `api url` The base URL of the JIRA API. It may be omitted, in which case GitLab will automatically use API version `2` based on the `project url`, i.e. `https://jira.example.com/rest/api/2`. +* `username` The username of the user created in [configuring JIRA step](#configuring-jira). +* `password` The password of the user created in [configuring JIRA step](#configuring-jira). +* `Jira issue transition` This is the id of a transition that moves issues to a closed state. You can find this number under [JIRA workflow administration, see screenshot](jira_workflow_screenshot.png). By default, this id is `2`. (In the example image, this is `2` as well) + +After saving the configuration, your GitLab project will be able to interact with the linked JIRA project. + + +### GitLab 6.x-7.7 with JIRA v6.x + +**Note: GitLab 7.8 and up contain various integration improvements. We strongly recommend upgrading.** + + +In `gitlab.yml` enable [JIRA issue tracker section by uncommenting the lines](https://gitlab.com/subscribers/gitlab-ee/blob/6-8-stable-ee/config/gitlab.yml.example#L111-115). +This will make sure that all issues within GitLab are pointing to the JIRA issue tracker. + +We can also enable JIRA service that will allow us to interact with JIRA issues. + +For example, we can close issues in JIRA by a commit in GitLab. + +Go to project settings page and fill in the project name for the JIRA project: + + + +Next, go to the services page and find JIRA. + + + +1. Tick the active check box to enable the service. +1. Supply the url to JIRA server, for example http://jira.sample +1. Supply the username of a user we created under `Configuring JIRA` section, for example `gitlab` +1. Supply the password of the user +1. Optional: supply the JIRA api version, default is version +1. Optional: supply the JIRA issue transition ID (issue transition to closed). This is dependant on JIRA settings, default is 2 +1. Save + +Now we should be able to interact with JIRA issues. diff --git a/doc/integration/jira_issue_reference.png b/doc/integration/jira_issue_reference.png Binary files differnew file mode 100644 index 00000000000..15739a22dc7 --- /dev/null +++ b/doc/integration/jira_issue_reference.png diff --git a/doc/integration/jira_project_name.png b/doc/integration/jira_project_name.png Binary files differnew file mode 100644 index 00000000000..5986fdb63fb --- /dev/null +++ b/doc/integration/jira_project_name.png diff --git a/doc/integration/jira_service.png b/doc/integration/jira_service.png Binary files differnew file mode 100644 index 00000000000..1f6628c4371 --- /dev/null +++ b/doc/integration/jira_service.png diff --git a/doc/integration/jira_service_close_issue.png b/doc/integration/jira_service_close_issue.png Binary files differnew file mode 100644 index 00000000000..67dfc6144c4 --- /dev/null +++ b/doc/integration/jira_service_close_issue.png diff --git a/doc/integration/jira_service_page.png b/doc/integration/jira_service_page.png Binary files differnew file mode 100644 index 00000000000..69ec44e826f --- /dev/null +++ b/doc/integration/jira_service_page.png diff --git a/doc/integration/jira_workflow_screenshot.png b/doc/integration/jira_workflow_screenshot.png Binary files differnew file mode 100644 index 00000000000..8635a32eb68 --- /dev/null +++ b/doc/integration/jira_workflow_screenshot.png diff --git a/doc/integration/kerberos.md b/doc/integration/kerberos.md new file mode 100644 index 00000000000..1f8aacf3813 --- /dev/null +++ b/doc/integration/kerberos.md @@ -0,0 +1,121 @@ +# Kerberos integration + +GitLab can be configured to allow your users to sign with their Kerberos credentials. +Kerberos integration can be enabled as a regular omniauth provider, edit [gitlab.rb (omnibus-gitlab)`](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#omniauth-google-twitter-github-login) or [gitlab.yml (source installations)](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/gitlab.yml.example) on your GitLab server and restart GitLab. You only need to specify the provider name. For example: + +``` +{ name: 'kerberos'} +``` + +NB: for source installations, make sure the `kerberos` gem group [has been installed](../install/installation.md#install-gems). + +You still need to configure your system for Kerberos usage, such as specifying realms. GitLab will make use of the system's Kerberos settings. + +Existing GitLab users can go to profile > account and attach a Kerberos account. if you want to allow users without a GitLab account to login you should enable the option `omniauth_allow_single_sign_on` in config file (default: false). Then, the first time a user signs in with Kerberos credentials, GitLab will create a new GitLab user associated with the email, which is built from the kerberos username and realm. +User accounts will be created automatically when authentication was successful. + +### HTTP git access + +A linked Kerberos account enables you to `git pull` and `git push` using your Kerberos account, as well as your standard GitLab credentials. + +### HTTP git access with Kerberos token (passwordless authentication) + +GitLab users with a linked Kerberos account can also `git pull` and `git push` using Kerberos tokens, i.e. without having to send their password with each operation. + +For GitLab to offer Kerberos token-based authentication, perform the following prerequisites: + +1. Create a Kerberos Service Principal for the HTTP service on your GitLab server. If your GitLab server is gitlab.example.com and your Kerberos realm EXAMPLE.COM, create a Service Principal `HTTP/gitlab.example.com@EXAMPLE.COM` in your Kerberos database. + +1. Create a keytab for the above Service Principal, e.g. `/etc/http.keytab`. + +The keytab is a sensitive file and must be readable by the GitLab user. Set ownership and protect the file appropriately: + +``` +$ sudo chown git /etc/http.keytab +$ sudo chmod 0700 /etc/http.keytab +``` + +#### Installations from source + +Edit the kerberos section of [gitlab.yml](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/gitlab.yml.example) to enable Kerberos ticket-based authentication. In most cases, you only need to enable Kerberos and specify the location of the keytab: + +```yaml + kerberos: + # Allow the HTTP Negotiate authentication method for Git clients + enabled: true + + # Kerberos 5 keytab file. The keytab file must be readable by the GitLab user, + # and should be different from other keytabs in the system. + # (default: use default keytab from Krb5 config) + keytab: /etc/http.keytab +``` + +Restart GitLab to apply the changes. GitLab will now offer the `negotiate` authentication method for HTTP git access, enabling git clients that support this authentication protocol to authenticate with Kerberos tokens. + +##### Omnibus package installations + +In `/etc/gitlab/gitlab.rb`: + +```ruby + +gitlab_rails['kerberos_enabled'] = true +gitlab_rails['kerberos_keytab'] = "/etc/http.keytab" +``` + +and run `sudo gitlab-ctl reconfigure` for changes to take effect. + +#### Support for Git before 2.4 + +Until version 2.4, the `git` command uses only the `negotiate` authentication method if the HTTP server offers it, even if this method fails (such as when the client does not have a Kerberos token). +It is thus not possible to fall back to username/password (also known as `basic`) authentication if Kerberos authentication fails. + +For GitLab users to be able to use either `basic` or `negotiate` authentication with older git versions, it is possible to offer Kerberos ticket-based authentication on a different port (e.g. 8443) while the standard port will keep offering only `basic` authentication. + +* For source installations with HTTPS: + +1. Edit the nginx configuration file for GitLab (e.g. `/etc/nginx/sites-available/gitlab-ssl`) and configure nginx to listen to port 8443 in addition to the standard HTTPS port + + ```yaml + server { + listen 0.0.0.0:443 ssl; + listen [::]:443 ipv6only=on ssl default_server; + listen 0.0.0.0:8443 ssl; + listen [::]:8443 ipv6only=on ssl; + ``` + +1. Update the kerberos section of [gitlab.yml](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/gitlab.yml.example) + + ```yaml + kerberos: + # Dedicated port: Git before 2.4 does not fall back to Basic authentication if Negotiate fails. + # To support both Basic and Negotiate methods with older versions of Git, configure + # nginx to proxy GitLab on an extra port (e.g. 8443) and uncomment the following lines + # to dedicate this port to Kerberos authentication. (default: false) + use_dedicated_port: true + port: 8443 + https: true + ``` + +1. Restart nginx and gitlab + +* For Omnibus package installations, in `/etc/gitlab/gitlab.rb`: + +```ruby +gitlab_rails['kerberos_use_dedicated_port'] = true +gitlab_rails['kerberos_port'] = 8443 +gitlab_rails['kerberos_https'] = true +``` +and run `sudo gitlab-ctl reconfigure` for changes to take effect. + + +Git remote URLs will have to be updated to `https://gitlab.example.com:8443/mygroup/myproject.git` in order to use Kerberos ticket-based authentication. + +#### Support for Active Directory Kerberos environments + +When using Kerberos ticket-based authentication in an Active Directory domain, it may be necessary to increase the maximum header size allowed by nginx, as extensions to the Kerberos protocol may result in HTTP authentication headers larger than the default size of 8kB. Configure `large_client_header_buffers` to a larger value in [the nginx configuration](http://nginx.org/en/docs/http/ngx_http_core_module.html#large_client_header_buffers). + +### Helpful links to setup development kerberos environment. + +https://help.ubuntu.com/community/Kerberos + +http://blog.manula.org/2012/04/setting-up-kerberos-server-with-debian.html diff --git a/doc/integration/ldap.md b/doc/integration/ldap.md index 9b7d8fa3969..adf692c8aae 100644 --- a/doc/integration/ldap.md +++ b/doc/integration/ldap.md @@ -1,6 +1,7 @@ # GitLab LDAP integration GitLab can be configured to allow your users to sign with their LDAP credentials to integrate with e.g. Active Directory. +To enable LDAP integration, edit [gitlab.rb (omnibus-gitlab)`](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#setting-up-ldap-sign-in) or [gitlab.yml (source installations)](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/gitlab.yml.example) on your GitLab server and restart GitLab. The first time a user signs in with LDAP credentials, GitLab will create a new GitLab user associated with the LDAP Distinguished Name (DN) of the LDAP user. @@ -42,6 +43,9 @@ main: # 'main' is the GitLab 'provider ID' of this LDAP server bind_dn: '_the_full_dn_of_the_user_you_will_bind_with' password: '_the_password_of_the_bind_user' + # This setting allows an LDAP group to become GitLab administrators + admin_group: '' + # This setting specifies if LDAP server is Active Directory LDAP server. # For non AD servers it skips the AD specific queries. # If your LDAP server is not AD, set this to false. @@ -142,6 +146,106 @@ GitLab recognizes the following LDAP attributes as email addresses: `mail`, `ema If multiple LDAP email attributes are present, e.g. `mail: foo@bar.com` and `email: foo@example.com`, then the first attribute found wins -- in this case `foo@bar.com`. +## LDAP group synchronization (GitLab Enterprise Edition) + +LDAP group synchronization in GitLab Enterprise Edition allows you to synchronize the members of a GitLab group with one or more LDAP groups. + +### Setting up LDAP group synchronization + +Before enabling group synchronization, you need to make sure that the `group_base` field is set in your LDAP settings on +your `gitlab.rb` or `gitlab.yml` file. This setting will tell GitLab where to look for groups within your LDAP server. + +``` +group_base: 'OU=groups,DC=example,DC=com' +``` + +Suppose we want to synchronize the GitLab group 'example group' with the LDAP group 'Engineering'. + +1. As an owner, go to the group settings page for 'example group'. + + + +As an admin you can also go to the group edit page in the admin area. + + + +2. Enter 'Engineering' as the LDAP Common Name (CN) in the 'LDAP Group cn' field. + +3. Enter a default group access level in the 'LDAP Access' field; let's say Developer. + + + +4. Click 'Add synchronization' to add the new LDAP group link. + +Now every time a member of the 'Engineering' LDAP group signs in, they automatically become a Developer-level member of the 'example group' GitLab group. Users who are already signed in will see the change in membership after up to one hour. + +### Synchronizing with more than one LDAP group (GitLab EE 7.3 and newer) + +If you want to add the members of LDAP group to your GitLab group you can add an additional LDAP group link. +If you have two LDAP group links, e.g. 'cn=Engineering' at level 'Developer' and 'cn=QA' at level 'Reporter', and user Jane belongs to both the 'Engineering' and 'QA' LDAP groups, she will get the _highest_ access level of the two, namely 'Developer'. + + + +### Locking yourself out of your own group + +As an LDAP-enabled GitLab user, if you create a group and then set it to synchronize with an LDAP group you do not belong to, you will be removed from the group as soon as the synchronization takes effect for you, unless you are the last owner of the group. + +If you accidentally lock yourself out of your own GitLab group, ask another owner of the group or a GitLab administrator to change the LDAP synchronization settings for your group. + +### Non-LDAP GitLab users + +Your GitLab instance may have users on it for whom LDAP is not enabled. +If this is the case, these users will not be affected by LDAP group synchronization settings: they will be neither added nor removed automatically. + +### ActiveDirectory nested group support + +If you are using ActiveDirectory, it is possible to create nested LDAP groups: the 'Engineering' LDAP group may contain another LDAP group 'Software', with 'Software' containing LDAP users Alice and Bob. +GitLab will recognize Alice and Bob as members of the 'Engineering' group. + +## Define GitLab admin status via LDAP + +It is possible to configure GitLab Enterprise Edition (7.1 and newer) so that GitLab admin rights are bestowed on the members of a given LDAP group. +GitLab administrator users who do not have LDAP enabled are not affected by the LDAP admin group feature. + +### Enabling the admin group feature + +Below we assume that you have an LDAP group with the common name (CN) 'GitLab administrators' containing the users that should be GitLab administrators. +We recommend that you keep a non-LDAP GitLab administrator user around on your GitLab instance in case you accidentally remove the admin status from your own LDAP-enabled GitLab user. + +For omnibus-gitlab, add the following to the LDAP part of `/etc/gitlab/gitlab.rb` under one (or more) of the servers in +the `servers:` section and run `gitlab-ctl reconfigure`. + +```yaml + admin_group: 'GitLab administrators' +``` + +For installations from source, add the following setting in the 'ldap' section of gitlab.yml, and run `service gitlab reload` afterwards. + +```yaml + admin_group: 'Gitlab administrators' +``` + +## Synchronising user SSH keys with LDAP + +It is possible to configure GitLab Enterprise Edition (7.1 and newer) so that users have their SSH public keys synchronised with an attribute in their LDAP object. +Existing SSH public keys that are manually manged in GitLab are not affected by this feature. + +### Enabling the key synchronisation feature + +Below we assume that you have LDAP users with an attribute 'sshpublickey' containing the users ssh public key. + +For omnibus-gitlab, add the following to `/etc/gitlab/gitlab.rb` and run `gitlab-ctl reconfigure`. + +```ruby +gitlab_rails['ldap_sync_ssh_keys'] = 'sshpublickey' +``` + +For installations from source, add the following setting in the 'ldap' section of gitlab.yml, and run `service gitlab reload` afterwards. + +```yaml + sync_ssh_keys: 'sshpublickey' +``` + ## Using an LDAP filter to limit access to your GitLab server If you want to limit all GitLab access to a subset of the LDAP users on your LDAP server you can set up an LDAP user filter. @@ -174,6 +278,129 @@ Tip: if you want to limit access to the nested members of an Active Directory gr Please note that GitLab does not support the custom filter syntax used by omniauth-ldap. +## Integrate GitLab with more than one LDAP server (Enterprise Edition) + +Starting with GitLab Enterprise Edition 7.4 it is possible to give users from more than one LDAP server access to the same GitLab server. + +Please use the following steps to enable support for multiple LDAP servers. + +### 1. Check your GitLab version + +Go to gitlab.example.com/help and verify you are running GitLab Enterprise Edition 7.4.0 or newer. + +### 2. Make sure your GitLab server uses the new LDAP syntax + +``` +# For omnibus packages +sudo gitlab-rails runner 'puts (Gitlab.config.ldap["host"] ? :old_syntax : :new_syntax)' + +# For installations from source +cd /home/git/gitlab +bundle exec rails runner -e production 'puts (Gitlab.config.ldap["host"] ? :old_syntax : :new_syntax)' +``` + +If you are not using the new syntax yet, please edit `/etc/gitlab/gitlab.rb` or +`gitlab.yml` (for installations from source) and make your LDAP configuration +setting look as above. With the new syntax, LDAP server blocks are named. Your +existing LDAP server should be named 'main'. + +### 3. Add new LDAP servers + +Now you can add new LDAP servers via `/etc/gitlab/gitlab.rb` (omnibus packages) or `gitlab.yml` (installations from source). +Remember to run `sudo gitlab-ctl reconfigure` or `sudo service gitlab reload` for the new servers to become available. + +Tip: you can assign labels to the different servers to give them human-friendly names. + +``` +ldap: + servers: + main: + label: 'LDAP HQ' +``` + +## Automatic Daily LDAP Sync + +GitLab Enterprise Edition will now automatically sync all LDAP members on a daily basis. You can configure the time that it happens. + +LDAP group synchronization in GitLab Enterprise Edition works by GitLab periodically updating the group memberships of _active_ GitLab users. +If a GitLab user becomes _inactive_ however, their group memberships in GitLab can start to lag behind the LDAP server group memberships. +Starting with GitLab 7.5 Enterprise Edition, GitLab will also update the LDAP group memberships of inactive users, by doing a daily LDAP check for _all_ GitLab users. + +> Example: +John Doe leaves the company and is removed from the LDAP server. +At this point he can no longer log in to GitLab 7.4 EE. +But because he is no longer active on the GitLab EE server (he cannot log in!), his LDAP group memberships in GitLab no longer get updated, and he stays listed as a group member on the GitLab server. + +> Now with GitLab 7.5 Enterprise Edition, within 24 hours of John being removed from the LDAP server, his user will also stop being listed as member of any GitLab groups. + + +### Blocked users and Daily sync + +If you use Active directory and you block user in AD - user will be automatically blocked in GitLab during next LDAP Sync. + + +## LDAP Synchronization + +LDAP membership is checked for a GitLab user: + +- when they sign in to the GitLab instance +- on a daily basis +- on any request that they do, once the LDAP cache has expired (default 1 hour, configurable, cache is per user) + +If you want a shorter or longer LDAP sync time, you can easily set this with the `sync_time` attribute in your config. + +For Omnibus package installations, simply add `"sync_time"` in `/etc/gitlab/gitlab.rb` to your LDAP config. +A typical LDAP configuration for GitLab installed with an Omnibus package might look like this: + +``` +gitlab_rails['ldap_servers'] = YAML.load <<-EOS +main: + label: 'LDAP' + host: '_your_ldap_server' + port: 636 + uid: 'sAMAccountName' + method: 'ssl' # "tls" or "ssl" or "plain" + bind_dn: '_the_full_dn_of_the_user_you_will_bind_with' + password: '_the_password_of_the_bind_user' + active_directory: true + allow_username_or_email_login: false + base: '' + user_filter: '' + sync_time: 1800 + ## EE only + group_base: '' + admin_group: '' + sync_ssh_keys: false +EOS +``` + +Here, `sync_time` is set to `1800` seconds, meaning the LDAP cache will expire every 30 minutes. + +For manual GitLab installations, simply uncomment the `sync_time` entry in your `gitlab.yml` and set it to the value you desire. + +Please note that changing the LDAP sync time can influence the performance of your GitLab instance. + +## What sort of queries can my LDAP server expect from GitLab EE? + +Active GitLab users trigger 'permission updates' by signing in or +interacting with GitLab, and in addtion all GitLab users (active or not) get a +permission update during the daily sweep. The number of permission updates per +day depends on how many of your GitLab users are active and on how many +LDAP-enabled GitLab users exist in your GitLab SQL database. + +During a 'permission update' for a user, GitLab does 1-2 queries for the +specific user, and 1 queries for each LDAP group known to GitLab. GitLab +fetches all available attributes of LDAP user and group objects on most +queries. If you use Active Directory, GitLab performs additional +'extensibleMatch' queries to check for nested group membership and whether the +user is blocked, one of each per user and group. + +Note that usually not all user and group objects in an organization's LDAP tree +will be known to GitLab. GitLab only queries LDAP user objects corresponding to +users who use or have used GitLab. Similarly, GitLab only queries LDAP group +objects that have been (manually) linked to a GitLab group by a GitLab user or +administrator. + ## Limitations GitLab's LDAP client is based on [omniauth-ldap](https://gitlab.com/gitlab-org/omniauth-ldap) @@ -192,4 +419,4 @@ Not supported by GitLab's configuration options. When setting `method: ssl`, the underlying authentication method used by `omniauth-ldap` is `simple_tls`. This method establishes TLS encryption with the LDAP server before any LDAP-protocol data is exchanged but no validation of -the LDAP server's SSL certificate is performed.
\ No newline at end of file +the LDAP server's SSL certificate is performed. diff --git a/doc/integration/ldap/select_group_cn.png b/doc/integration/ldap/select_group_cn.png Binary files differnew file mode 100644 index 00000000000..60328114aad --- /dev/null +++ b/doc/integration/ldap/select_group_cn.png diff --git a/doc/integration/ldap/select_group_cn_admin.png b/doc/integration/ldap/select_group_cn_admin.png Binary files differnew file mode 100644 index 00000000000..2c2cbf89fd0 --- /dev/null +++ b/doc/integration/ldap/select_group_cn_admin.png diff --git a/doc/integration/ldap/select_group_cn_engineering.png b/doc/integration/ldap/select_group_cn_engineering.png Binary files differnew file mode 100644 index 00000000000..7834a15a477 --- /dev/null +++ b/doc/integration/ldap/select_group_cn_engineering.png diff --git a/doc/integration/ldap/two_linked_ldap_groups.png b/doc/integration/ldap/two_linked_ldap_groups.png Binary files differnew file mode 100644 index 00000000000..36e21cd3373 --- /dev/null +++ b/doc/integration/ldap/two_linked_ldap_groups.png diff --git a/doc/integration/merge_request_close_jira.png b/doc/integration/merge_request_close_jira.png Binary files differnew file mode 100644 index 00000000000..1e78daf105f --- /dev/null +++ b/doc/integration/merge_request_close_jira.png diff --git a/doc/release/manual_testing.md b/doc/release/manual_testing.md new file mode 100644 index 00000000000..f207c2939b1 --- /dev/null +++ b/doc/release/manual_testing.md @@ -0,0 +1,56 @@ +# GitLab QA + +## Login +- Regular account login +- LDAP login +Use the [support document](https://docs.google.com/document/d/1cAHvbdFE6zR5WY-zhn3HsDcACssJE8Cav6WeYq3oCkM/edit#heading=h.2x3u50ukp87w) for the ldap settings. + +## Forks +- fork group project +- push changes to fork +- submit merge request to origin project +- accept merge request + +## Git +- add, remove ssh key +- git clone, git push over ssh +- git clone, git push over http (with both regular and ldap accounts) + +## Project +- create project +- create project using import repo +- transfer project +- rename repo path +- add/remove project member +- remove project +- create git branch with UI +- create git tag with UI + +## Web editor +- create, edit, remove file in web UI + +## Group +- create group +- create project in group +- add/remove group member +- remove group + +## Markdown +- Visit / clone [relative links repository](https://dev.gitlab.org/samples/relative-links/tree/master) and see if the links are linking to the correct documents in the repository +- Check if images are rendered in the md +- Click on a [directory link](https://dev.gitlab.org/samples/relative-links/tree/master/documents) and see if it correctly takes to the tree view +- Click on a [file link](https://dev.gitlab.org/samples/relative-links/blob/master/documents/0.md) and see if it correctly takes to the blob view +- Check if the links in the README when viewed as a [blob](https://dev.gitlab.org/samples/relative-links/blob/master/README.md) are correct +- Select the "markdown" branch and check if all links point to the files within the markdown branch + +## Syntax highlighting +- Visit/clone [language highlight repository](https://dev.gitlab.org/samples/languages-highlight) +- Check for obvious errors in highlighting + +## Upgrader +- Upgrade from the previous release +- Run the upgrader script in this release (it should not break) + +## Rake tasks +- Check if rake gitlab:check is updated and works +- Check if rake gitlab:env:info is updated and works diff --git a/doc/tools/email.md b/doc/tools/email.md new file mode 100644 index 00000000000..36390169446 --- /dev/null +++ b/doc/tools/email.md @@ -0,0 +1,22 @@ +# Email from GitLab + +As a GitLab administrator you can email GitLab users from within GitLab. + +In the administrator interface, go to `Users`. Here you will find the button to email users: + + + +Here you can simply compose an email. + + + +Which will be sent to all users or users of a chosen group or project. + + + +## Note +User can choose to unsubscribe from receiving emails from GitLab by following the unsubscribe link from the email. +Unsubscribing is unauthenticated in order to keep the simplicity of this feature. + +On unsubscribe, user will receive an email notifying that unsubscribe happened. +Endpoint that provides unsubscribe option is protected by request being rate-limited. diff --git a/doc/tools/email1.png b/doc/tools/email1.png Binary files differnew file mode 100644 index 00000000000..c43c595b455 --- /dev/null +++ b/doc/tools/email1.png diff --git a/doc/tools/email2.png b/doc/tools/email2.png Binary files differnew file mode 100644 index 00000000000..714d8d33b64 --- /dev/null +++ b/doc/tools/email2.png diff --git a/doc/tools/email3.png b/doc/tools/email3.png Binary files differnew file mode 100644 index 00000000000..6f4de8393f9 --- /dev/null +++ b/doc/tools/email3.png diff --git a/doc/update/6.0-ce-to-ee.md b/doc/update/6.0-ce-to-ee.md new file mode 100644 index 00000000000..a1551efb3d3 --- /dev/null +++ b/doc/update/6.0-ce-to-ee.md @@ -0,0 +1,87 @@ +# From Community Edition 6.0 to Enterprise Edition 6.0 + +This guide assumes you have a correctly configured and tested installation of GitLab Community Edition 6.0. +If you run into any trouble or if you have any questions please contact us at support@gitlab.com. + +### 0. Backup + +Make a backup just in case things go south: +(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version) + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 1. Stop server + + sudo service gitlab stop + +### 2. Get the EE code + +```bash +cd /home/git/gitlab +sudo -u git -H git remote add ee https://gitlab.com/subscribers/gitlab-ee.git +sudo -u git -H git fetch --all +sudo -u git -H git checkout 6-0-stable-ee +``` + +### 3. Update config files + +* Make `/home/git/gitlab/config/gitlab.yml` same as /home/git/gitlab/config/gitlab.yml.example but with your settings. +Note: Under LDAP settings fill in the `group_base` setting. +* Make `/home/git/gitlab/config/unicorn.rb` same as /home/git/gitlab/config/unicorn.rb.example but with your settings. + +### 4. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL +sudo -u git -H bundle install --without development test postgres --deployment + +#PostgreSQL +sudo -u git -H bundle install --without development test mysql --deployment + +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production +sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production +``` + +### 5. Update Init script + +```bash +sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab +sudo chmod +x /etc/init.d/gitlab +``` + +### 6. Start application + + sudo service gitlab start + sudo service nginx restart + +### 7. Check application status + +Check if GitLab and its environment are configured correctly: + + sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production + +To make sure you didn't miss anything run a more thorough check with: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations upgrade complete! + +## Things went wrong? Revert to previous version (Community Edition 6.0) + +### 1. Revert the code to the previous version +```bash +cd /home/git/gitlab +sudo -u git -H git checkout 6-0-stable +``` + +### 2. Restore from the backup: + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` diff --git a/doc/update/6.0-to-6.2.md b/doc/update/6.0-to-6.2.md new file mode 100644 index 00000000000..03d7e96effe --- /dev/null +++ b/doc/update/6.0-to-6.2.md @@ -0,0 +1,131 @@ +# From 6.0 to 6.2 + +## Notice +Security vulnerabilities CVE-2013-4490 and CVE-2013-4489 have been patched in the latest version of GitLab 6.2. + +# In 6.1 we remove a lot of deprecated code. +# You should update to 6.0 before installing 6.1 or higher so all the necessary conversions are run. + +### Deprecations + +#### Global issue numbers + +As of 6.1 issue numbers are project specific. This means all issues are renumbered and get a new number in their url. If you use an old issue number url and the issue number does not exist yet you are redirected to the new one. This conversion does not trigger if the old number already exists for this project, this is unlikely but will happen with old issues and large projects. + +### 0. Backup + +It's useful to make a backup just in case things go south: +(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version) + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 1. Stop server + + sudo service gitlab stop + +### 2. Get latest code + +```bash +cd /home/git/gitlab +sudo -u git -H git fetch +sudo -u git -H git checkout 6-2-stable # Latest version of 6-2-stable addresses CVE-2013-4489 +``` + + +### 3. Install additional packages + +```bash +# Add support for lograte for better log file handling +sudo apt-get install logrotate +``` + +### 4. Update gitlab-shell + +```bash +cd /home/git/gitlab-shell +sudo -u git -H git fetch +sudo -u git -H git checkout v1.7.9 # Addresses multiple critical security vulnerabilities +``` + +### 5. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL +sudo -u git -H bundle install --without development test postgres --deployment + +#PostgreSQL +sudo -u git -H bundle install --without development test mysql --deployment + + +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production +sudo -u git -H bundle exec rake migrate_iids RAILS_ENV=production +sudo -u git -H bundle exec rake assets:clean RAILS_ENV=production +sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production +sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production +``` + +### 6. Update config files + +TIP: to see what changed in gitlab.yml.example in this release use next command: + +``` +git diff 6-0-stable:config/gitlab.yml.example 6-2-stable:config/gitlab.yml.example +``` + +* Make `/home/git/gitlab/config/gitlab.yml` same as https://github.com/gitlabhq/gitlabhq/blob/6-2-stable/config/gitlab.yml.example but with your settings. +* Make `/home/git/gitlab/config/unicorn.rb` same as https://github.com/gitlabhq/gitlabhq/blob/6-2-stable/config/unicorn.rb.example but with your settings. +* Copy rack attack middleware config + +```bash +sudo -u git -H cp config/initializers/rack_attack.rb.example config/initializers/rack_attack.rb +``` +* Uncomment `config.middleware.use Rack::Attack` in `/home/git/gitlab/config/application.rb` +* Set up logrotate + +```bash +sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab +``` + +### 7. Update Init script + +```bash +sudo rm /etc/init.d/gitlab +sudo curl --output /etc/init.d/gitlab https://raw.github.com/gitlabhq/gitlabhq/6-2-stable/lib/support/init.d/gitlab +sudo chmod +x /etc/init.d/gitlab +``` + +### 8. Start application + + sudo service gitlab start + sudo service nginx restart + +### 9. Check application status + +Check if GitLab and its environment are configured correctly: + + cd /home/git/gitlab + sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production + +To make sure you didn't miss anything run a more thorough check with: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations upgrade complete! + +## Things went south? Revert to previous version (6.0) + +### 1. Revert the code to the previous version +Follow the [`upgrade guide from 5.4 to 6.0`](5.4-to-6.0.md), except for the database migration +(The backup is already migrated to the previous version) + +### 2. Restore from the backup: + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` diff --git a/doc/update/6.1-ce-to-ee.md b/doc/update/6.1-ce-to-ee.md new file mode 100644 index 00000000000..e75b67d239f --- /dev/null +++ b/doc/update/6.1-ce-to-ee.md @@ -0,0 +1,87 @@ +# From Community Edition 6.1 to Enterprise Edition 6.1 + +This guide assumes you have a correctly configured and tested installation of GitLab Community Edition 6.1. +If you run into any trouble or if you have any questions please contact us at support@gitlab.com. + +### 0. Backup + +Make a backup just in case things go south: +(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version) + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 1. Stop server + + sudo service gitlab stop + +### 2. Get the EE code + +```bash +cd /home/git/gitlab +sudo -u git -H git remote add ee https://gitlab.com/subscribers/gitlab-ee.git +sudo -u git -H git fetch --all +sudo -u git -H git checkout 6-1-stable-ee +``` + +### 3. Update config files + +* Make `/home/git/gitlab/config/gitlab.yml` same as /home/git/gitlab/config/gitlab.yml.example but with your settings. +Note: Under LDAP settings fill in the `group_base` setting. +* Make `/home/git/gitlab/config/unicorn.rb` same as /home/git/gitlab/config/unicorn.rb.example but with your settings. + +### 4. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL +sudo -u git -H bundle install --without development test postgres --deployment + +#PostgreSQL +sudo -u git -H bundle install --without development test mysql --deployment + +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production +sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production +``` + +### 5. Update Init script + +```bash +sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab +sudo chmod +x /etc/init.d/gitlab +``` + +### 6. Start application + + sudo service gitlab start + sudo service nginx restart + +### 7. Check application status + +Check if GitLab and its environment are configured correctly: + + sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production + +To make sure you didn't miss anything run a more thorough check with: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations upgrade complete! + +## Things went wrong? Revert to previous version (Community Edition 6.1) + +### 1. Revert the code to the previous version +```bash +cd /home/git/gitlab +sudo -u git -H git checkout 6-1-stable +``` + +### 2. Restore from the backup: + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` diff --git a/doc/update/6.2-ce-to-ee.md b/doc/update/6.2-ce-to-ee.md new file mode 100644 index 00000000000..34aef31410e --- /dev/null +++ b/doc/update/6.2-ce-to-ee.md @@ -0,0 +1,87 @@ +# From Community Edition 6.2 to Enterprise Edition 6.2 + +This guide assumes you have a correctly configured and tested installation of GitLab Community Edition 6.2. +If you run into any trouble or if you have any questions please contact us at support@gitlab.com. + +### 0. Backup + +Make a backup just in case things go south: +(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version) + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 1. Stop server + + sudo service gitlab stop + +### 2. Get the EE code + +```bash +cd /home/git/gitlab +sudo -u git -H git remote add ee https://gitlab.com/subscribers/gitlab-ee.git +sudo -u git -H git fetch --all +sudo -u git -H git checkout 6-2-stable-ee +``` + +### 3. Update config files + +* Make `/home/git/gitlab/config/gitlab.yml` same as /home/git/gitlab/config/gitlab.yml.example but with your settings. +Note: Under LDAP settings fill in the `group_base` setting. +* Make `/home/git/gitlab/config/unicorn.rb` same as /home/git/gitlab/config/unicorn.rb.example but with your settings. + +### 4. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL +sudo -u git -H bundle install --without development test postgres --deployment + +#PostgreSQL +sudo -u git -H bundle install --without development test mysql --deployment + +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production +sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production +``` + +### 5. Update Init script + +```bash +sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab +sudo chmod +x /etc/init.d/gitlab +``` + +### 6. Start application + + sudo service gitlab start + sudo service nginx restart + +### 7. Check application status + +Check if GitLab and its environment are configured correctly: + + sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production + +To make sure you didn't miss anything run a more thorough check with: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations upgrade complete! + +## Things went wrong? Revert to previous version (Community Edition 6.2) + +### 1. Revert the code to the previous version +```bash +cd /home/git/gitlab +sudo -u git -H git checkout 6-2-stable +``` + +### 2. Restore from the backup: + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` diff --git a/doc/update/6.3-ce-to-ee.md b/doc/update/6.3-ce-to-ee.md new file mode 100644 index 00000000000..538113043a8 --- /dev/null +++ b/doc/update/6.3-ce-to-ee.md @@ -0,0 +1,87 @@ +# From Community Edition 6.3 to Enterprise Edition 6.3 + +This guide assumes you have a correctly configured and tested installation of GitLab Community Edition 6.3. +If you run into any trouble or if you have any questions please contact us at support@gitlab.com. + +### 0. Backup + +Make a backup just in case things go south: +(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version) + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 1. Stop server + + sudo service gitlab stop + +### 2. Get the EE code + +```bash +cd /home/git/gitlab +sudo -u git -H git remote add ee https://gitlab.com/subscribers/gitlab-ee.git +sudo -u git -H git fetch --all +sudo -u git -H git checkout 6-3-stable-ee +``` + +### 3. Update config files + +* Make `/home/git/gitlab/config/gitlab.yml` same as /home/git/gitlab/config/gitlab.yml.example but with your settings. +Note: Under LDAP settings fill in the `group_base` setting. +* Make `/home/git/gitlab/config/unicorn.rb` same as /home/git/gitlab/config/unicorn.rb.example but with your settings. + +### 4. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL +sudo -u git -H bundle install --without development test postgres --deployment + +#PostgreSQL +sudo -u git -H bundle install --without development test mysql --deployment + +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production +sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production +``` + +### 5. Update Init script + +```bash +sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab +sudo chmod +x /etc/init.d/gitlab +``` + +### 6. Start application + + sudo service gitlab start + sudo service nginx restart + +### 7. Check application status + +Check if GitLab and its environment are configured correctly: + + sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production + +To make sure you didn't miss anything run a more thorough check with: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations upgrade complete! + +## Things went wrong? Revert to previous version (Community Edition 6.3) + +### 1. Revert the code to the previous version +```bash +cd /home/git/gitlab +sudo -u git -H git checkout 6-3-stable +``` + +### 2. Restore from the backup: + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` diff --git a/doc/update/6.4-ce-to-ee.md b/doc/update/6.4-ce-to-ee.md new file mode 100644 index 00000000000..4b8fe964a7c --- /dev/null +++ b/doc/update/6.4-ce-to-ee.md @@ -0,0 +1,87 @@ +# From Community Edition 6.4 to Enterprise Edition 6.4 + +This guide assumes you have a correctly configured and tested installation of GitLab Community Edition 6.4. +If you run into any trouble or if you have any questions please contact us at support@gitlab.com. + +### 0. Backup + +Make a backup just in case things go south: +(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version) + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 1. Stop server + + sudo service gitlab stop + +### 2. Get the EE code + +```bash +cd /home/git/gitlab +sudo -u git -H git remote add ee https://gitlab.com/subscribers/gitlab-ee.git +sudo -u git -H git fetch --all +sudo -u git -H git checkout 6-4-stable-ee +``` + +### 3. Update config files + +* Make `/home/git/gitlab/config/gitlab.yml` same as /home/git/gitlab/config/gitlab.yml.example but with your settings. +Note: Under LDAP settings fill in the `group_base` setting. +* Make `/home/git/gitlab/config/unicorn.rb` same as /home/git/gitlab/config/unicorn.rb.example but with your settings. + +### 4. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL +sudo -u git -H bundle install --without development test postgres --deployment + +#PostgreSQL +sudo -u git -H bundle install --without development test mysql --deployment + +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production +sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production +``` + +### 5. Update Init script + +```bash +sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab +sudo chmod +x /etc/init.d/gitlab +``` + +### 6. Start application + + sudo service gitlab start + sudo service nginx restart + +### 7. Check application status + +Check if GitLab and its environment are configured correctly: + + sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production + +To make sure you didn't miss anything run a more thorough check with: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations upgrade complete! + +## Things went wrong? Revert to previous version (Community Edition 6.4) + +### 1. Revert the code to the previous version +```bash +cd /home/git/gitlab +sudo -u git -H git checkout 6-4-stable +``` + +### 2. Restore from the backup: + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` diff --git a/doc/update/6.5-ce-to-ee.md b/doc/update/6.5-ce-to-ee.md new file mode 100644 index 00000000000..23dad885e42 --- /dev/null +++ b/doc/update/6.5-ce-to-ee.md @@ -0,0 +1,87 @@ +# From Community Edition 6.5 to Enterprise Edition 6.5 + +This guide assumes you have a correctly configured and tested installation of GitLab Community Edition 6.5. +If you run into any trouble or if you have any questions please contact us at support@gitlab.com. + +### 0. Backup + +Make a backup just in case things go south: +(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version) + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 1. Stop server + + sudo service gitlab stop + +### 2. Get the EE code + +```bash +cd /home/git/gitlab +sudo -u git -H git remote add ee https://gitlab.com/subscribers/gitlab-ee.git +sudo -u git -H git fetch --all +sudo -u git -H git checkout 6-5-stable-ee +``` + +### 3. Update config files + +* Make `/home/git/gitlab/config/gitlab.yml` same as /home/git/gitlab/config/gitlab.yml.example but with your settings. +Note: Under LDAP settings fill in the `group_base` setting. +* Make `/home/git/gitlab/config/unicorn.rb` same as /home/git/gitlab/config/unicorn.rb.example but with your settings. + +### 4. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL +sudo -u git -H bundle install --without development test postgres --deployment + +#PostgreSQL +sudo -u git -H bundle install --without development test mysql --deployment + +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production +sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production +``` + +### 5. Update Init script + +```bash +sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab +sudo chmod +x /etc/init.d/gitlab +``` + +### 6. Start application + + sudo service gitlab start + sudo service nginx restart + +### 7. Check application status + +Check if GitLab and its environment are configured correctly: + + sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production + +To make sure you didn't miss anything run a more thorough check with: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations upgrade complete! + +## Things went wrong? Revert to previous version (Community Edition 6.5) + +### 1. Revert the code to the previous version +```bash +cd /home/git/gitlab +sudo -u git -H git checkout 6-5-stable +``` + +### 2. Restore from the backup: + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` diff --git a/doc/update/6.6-ce-to-ee.md b/doc/update/6.6-ce-to-ee.md new file mode 100644 index 00000000000..826975bb973 --- /dev/null +++ b/doc/update/6.6-ce-to-ee.md @@ -0,0 +1,87 @@ +# From Community Edition 6.6 to Enterprise Edition 6.6 + +This guide assumes you have a correctly configured and tested installation of GitLab Community Edition 6.6. +If you run into any trouble or if you have any questions please contact us at support@gitlab.com. + +### 0. Backup + +Make a backup just in case things go south: +(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version) + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 1. Stop server + + sudo service gitlab stop + +### 2. Get the EE code + +```bash +cd /home/git/gitlab +sudo -u git -H git remote add ee https://gitlab.com/subscribers/gitlab-ee.git +sudo -u git -H git fetch --all +sudo -u git -H git checkout 6-6-stable-ee +``` + +### 3. Update config files + +* Make `/home/git/gitlab/config/gitlab.yml` same as /home/git/gitlab/config/gitlab.yml.example but with your settings. +Note: Under LDAP settings fill in the `group_base` setting. +* Make `/home/git/gitlab/config/unicorn.rb` same as /home/git/gitlab/config/unicorn.rb.example but with your settings. + +### 4. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL +sudo -u git -H bundle install --without development test postgres --deployment + +#PostgreSQL +sudo -u git -H bundle install --without development test mysql --deployment + +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production +sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production +``` + +### 5. Update Init script + +```bash +sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab +sudo chmod +x /etc/init.d/gitlab +``` + +### 6. Start application + + sudo service gitlab start + sudo service nginx restart + +### 7. Check application status + +Check if GitLab and its environment are configured correctly: + + sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production + +To make sure you didn't miss anything run a more thorough check with: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations upgrade complete! + +## Things went wrong? Revert to previous version (Community Edition 6.6) + +### 1. Revert the code to the previous version +```bash +cd /home/git/gitlab +sudo -u git -H git checkout 6-6-stable +``` + +### 2. Restore from the backup: + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` diff --git a/doc/update/6.7-ce-to-ee.md b/doc/update/6.7-ce-to-ee.md new file mode 100644 index 00000000000..d5618f2c1fe --- /dev/null +++ b/doc/update/6.7-ce-to-ee.md @@ -0,0 +1,87 @@ +# From Community Edition 6.7 to Enterprise Edition 6.7 + +This guide assumes you have a correctly configured and tested installation of GitLab Community Edition 6.7. +If you run into any trouble or if you have any questions please contact us at support@gitlab.com. + +### 0. Backup + +Make a backup just in case things go south: +(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version) + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 1. Stop server + + sudo service gitlab stop + +### 2. Get the EE code + +```bash +cd /home/git/gitlab +sudo -u git -H git remote add ee https://gitlab.com/subscribers/gitlab-ee.git +sudo -u git -H git fetch --all +sudo -u git -H git checkout 6-7-stable-ee +``` + +### 3. Update config files + +* Make `/home/git/gitlab/config/gitlab.yml` same as /home/git/gitlab/config/gitlab.yml.example but with your settings. +Note: Under LDAP settings fill in the `group_base` setting. +* Make `/home/git/gitlab/config/unicorn.rb` same as /home/git/gitlab/config/unicorn.rb.example but with your settings. + +### 4. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL +sudo -u git -H bundle install --without development test postgres --deployment + +#PostgreSQL +sudo -u git -H bundle install --without development test mysql --deployment + +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production +sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production +``` + +### 5. Update Init script + +```bash +sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab +sudo chmod +x /etc/init.d/gitlab +``` + +### 6. Start application + + sudo service gitlab start + sudo service nginx restart + +### 7. Check application status + +Check if GitLab and its environment are configured correctly: + + sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production + +To make sure you didn't miss anything run a more thorough check with: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations upgrade complete! + +## Things went wrong? Revert to previous version (Community Edition 6.7) + +### 1. Revert the code to the previous version +```bash +cd /home/git/gitlab +sudo -u git -H git checkout 6-7-stable +``` + +### 2. Restore from the backup: + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` diff --git a/doc/update/6.8-ce-to-ee.md b/doc/update/6.8-ce-to-ee.md new file mode 100644 index 00000000000..1c99691448e --- /dev/null +++ b/doc/update/6.8-ce-to-ee.md @@ -0,0 +1,87 @@ +# From Community Edition 6.8 to Enterprise Edition 6.8 + +This guide assumes you have a correctly configured and tested installation of GitLab Community Edition 6.8. +If you run into any trouble or if you have any questions please contact us at support@gitlab.com. + +### 0. Backup + +Make a backup just in case things go south: +(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version) + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 1. Stop server + + sudo service gitlab stop + +### 2. Get the EE code + +```bash +cd /home/git/gitlab +sudo -u git -H git remote add ee https://gitlab.com/subscribers/gitlab-ee.git +sudo -u git -H git fetch --all +sudo -u git -H git checkout 6-8-stable-ee +``` + +### 3. Update config files + +* Make `/home/git/gitlab/config/gitlab.yml` same as /home/git/gitlab/config/gitlab.yml.example but with your settings. +Note: Under LDAP settings fill in the `group_base` setting. +* Make `/home/git/gitlab/config/unicorn.rb` same as /home/git/gitlab/config/unicorn.rb.example but with your settings. + +### 4. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL +sudo -u git -H bundle install --without development test postgres --deployment + +#PostgreSQL +sudo -u git -H bundle install --without development test mysql --deployment + +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production +sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production +``` + +### 5. Update Init script + +```bash +sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab +sudo chmod +x /etc/init.d/gitlab +``` + +### 6. Start application + + sudo service gitlab start + sudo service nginx restart + +### 7. Check application status + +Check if GitLab and its environment are configured correctly: + + sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production + +To make sure you didn't miss anything run a more thorough check with: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations upgrade complete! + +## Things went wrong? Revert to previous version (Community Edition 6.8) + +### 1. Revert the code to the previous version +```bash +cd /home/git/gitlab +sudo -u git -H git checkout 6-8-stable +``` + +### 2. Restore from the backup: + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` diff --git a/doc/update/6.9-ce-to-ee.md b/doc/update/6.9-ce-to-ee.md new file mode 100644 index 00000000000..5de611f3234 --- /dev/null +++ b/doc/update/6.9-ce-to-ee.md @@ -0,0 +1,87 @@ +# From Community Edition 6.9 to Enterprise Edition 6.9 + +This guide assumes you have a correctly configured and tested installation of GitLab Community Edition 6.9. +If you run into any trouble or if you have any questions please contact us at support@gitlab.com. + +### 0. Backup + +Make a backup just in case things go south: +(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version) + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 1. Stop server + + sudo service gitlab stop + +### 2. Get the EE code + +```bash +cd /home/git/gitlab +sudo -u git -H git remote add ee https://gitlab.com/subscribers/gitlab-ee.git +sudo -u git -H git fetch --all +sudo -u git -H git checkout 6-9-stable-ee +``` + +### 3. Update config files + +* Make `/home/git/gitlab/config/gitlab.yml` same as /home/git/gitlab/config/gitlab.yml.example but with your settings. +Note: Under LDAP settings fill in the `group_base` setting. +* Make `/home/git/gitlab/config/unicorn.rb` same as /home/git/gitlab/config/unicorn.rb.example but with your settings. + +### 4. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL +sudo -u git -H bundle install --without development test postgres --deployment + +#PostgreSQL +sudo -u git -H bundle install --without development test mysql --deployment + +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production +sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production +``` + +### 5. Update Init script + +```bash +sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab +sudo chmod +x /etc/init.d/gitlab +``` + +### 6. Start application + + sudo service gitlab start + sudo service nginx restart + +### 7. Check application status + +Check if GitLab and its environment are configured correctly: + + sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production + +To make sure you didn't miss anything run a more thorough check with: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations upgrade complete! + +## Things went wrong? Revert to previous version (Community Edition 6.9) + +### 1. Revert the code to the previous version +```bash +cd /home/git/gitlab +sudo -u git -H git checkout 6-9-stable +``` + +### 2. Restore from the backup: + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` diff --git a/doc/update/6.x-or-7.x-to-7.14.md b/doc/update/6.x-or-7.x-to-7.14.md index b34fb12da6f..ca086210d55 100644 --- a/doc/update/6.x-or-7.x-to-7.14.md +++ b/doc/update/6.x-or-7.x-to-7.14.md @@ -133,6 +133,8 @@ sudo -u git -H git checkout v2.6.5 ## 7. Install libs, migrations, etc. ```bash +sudo apt-get install libkrb5-dev + cd /home/git/gitlab # MySQL installations (note: the line below states '--without ... postgres') @@ -162,11 +164,11 @@ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab TIP: to see what changed in `gitlab.yml.example` in this release use next command: ``` -git diff 6-0-stable:config/gitlab.yml.example 7-14-stable:config/gitlab.yml.example +git diff 6-0-stable:config/gitlab.yml.example 7-14-stable-ee:config/gitlab.yml.example ``` -* Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-14-stable/config/gitlab.yml.example but with your settings. -* Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-14-stable/config/unicorn.rb.example but with your settings. +* Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ee/blob/7-14-stable-ee/config/gitlab.yml.example but with your settings. +* Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ee/blob/7-14-stable-ee/config/unicorn.rb.example but with your settings. * Make `/home/git/gitlab-shell/config.yml` the same as https://gitlab.com/gitlab-org/gitlab-shell/blob/v2.6.5/config.yml.example but with your settings. * Copy rack attack middleware config @@ -182,8 +184,8 @@ sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab ### Change Nginx settings -* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-14-stable/lib/support/nginx/gitlab but with your settings. -* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-14-stable/lib/support/nginx/gitlab-ssl but with your settings. +* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as https://gitlab.com/gitlab-org/gitlab-ee/blob/7-14-stable-ee/lib/support/nginx/gitlab but with your settings. +* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ee/blob/7-14-stable-ee/lib/support/nginx/gitlab-ssl but with your settings. * A new `location /uploads/` section has been added that needs to have the same content as the existing `location @gitlab` section. ### Check the version of /usr/local/bin/git diff --git a/doc/update/7.0-ce-to-ee.md b/doc/update/7.0-ce-to-ee.md new file mode 100644 index 00000000000..2c54d165771 --- /dev/null +++ b/doc/update/7.0-ce-to-ee.md @@ -0,0 +1,87 @@ +# From Community Edition 7.0 to Enterprise Edition 7.0 + +This guide assumes you have a correctly configured and tested installation of GitLab Community Edition 7.0. +If you run into any trouble or if you have any questions please contact us at support@gitlab.com. + +### 0. Backup + +Make a backup just in case things go south: +(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version) + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 1. Stop server + + sudo service gitlab stop + +### 2. Get the EE code + +```bash +cd /home/git/gitlab +sudo -u git -H git remote add ee https://gitlab.com/subscribers/gitlab-ee.git +sudo -u git -H git fetch --all +sudo -u git -H git checkout 7-0-stable-ee +``` + +### 3. Update config files + +* Make `/home/git/gitlab/config/gitlab.yml` same as /home/git/gitlab/config/gitlab.yml.example but with your settings. +Note: Under LDAP settings fill in the `group_base` setting. +* Make `/home/git/gitlab/config/unicorn.rb` same as /home/git/gitlab/config/unicorn.rb.example but with your settings. + +### 4. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL +sudo -u git -H bundle install --without development test postgres --deployment + +#PostgreSQL +sudo -u git -H bundle install --without development test mysql --deployment + +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production +sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production +``` + +### 5. Update Init script + +```bash +sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab +sudo chmod +x /etc/init.d/gitlab +``` + +### 6. Start application + + sudo service gitlab start + sudo service nginx restart + +### 7. Check application status + +Check if GitLab and its environment are configured correctly: + + sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production + +To make sure you didn't miss anything run a more thorough check with: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations upgrade complete! + +## Things went wrong? Revert to previous version (Community Edition 7.0) + +### 1. Revert the code to the previous version +```bash +cd /home/git/gitlab +sudo -u git -H git checkout 7-0-stable +``` + +### 2. Restore from the backup: + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` diff --git a/doc/update/7.1-ce-to-ee.md b/doc/update/7.1-ce-to-ee.md new file mode 100644 index 00000000000..c00783ef7dc --- /dev/null +++ b/doc/update/7.1-ce-to-ee.md @@ -0,0 +1,87 @@ +# From Community Edition 7.1 to Enterprise Edition 7.1 + +This guide assumes you have a correctly configured and tested installation of GitLab Community Edition 7.1. +If you run into any trouble or if you have any questions please contact us at support@gitlab.com. + +### 0. Backup + +Make a backup just in case things go south: +(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version) + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 1. Stop server + + sudo service gitlab stop + +### 2. Get the EE code + +```bash +cd /home/git/gitlab +sudo -u git -H git remote add ee https://gitlab.com/subscribers/gitlab-ee.git +sudo -u git -H git fetch --all +sudo -u git -H git checkout 7-1-stable-ee +``` + +### 3. Update config files + +* Make `/home/git/gitlab/config/gitlab.yml` same as /home/git/gitlab/config/gitlab.yml.example but with your settings. +Note: Under LDAP settings fill in the `group_base` setting. +* Make `/home/git/gitlab/config/unicorn.rb` same as /home/git/gitlab/config/unicorn.rb.example but with your settings. + +### 4. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL +sudo -u git -H bundle install --without development test postgres --deployment + +#PostgreSQL +sudo -u git -H bundle install --without development test mysql --deployment + +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production +sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production +``` + +### 5. Update Init script + +```bash +sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab +sudo chmod +x /etc/init.d/gitlab +``` + +### 6. Start application + + sudo service gitlab start + sudo service nginx restart + +### 7. Check application status + +Check if GitLab and its environment are configured correctly: + + sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production + +To make sure you didn't miss anything run a more thorough check with: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations upgrade complete! + +## Things went wrong? Revert to previous version (Community Edition 7.1) + +### 1. Revert the code to the previous version +```bash +cd /home/git/gitlab +sudo -u git -H git checkout 7-1-stable +``` + +### 2. Restore from the backup: + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` diff --git a/doc/update/7.10-ce-to-ee.md b/doc/update/7.10-ce-to-ee.md new file mode 100644 index 00000000000..81e2764b086 --- /dev/null +++ b/doc/update/7.10-ce-to-ee.md @@ -0,0 +1,78 @@ +# From Community Edition 7.10 to Enterprise Edition 7.10 + +This guide assumes you have a correctly configured and tested installation of GitLab Community Edition 7.10. +If you run into any trouble or if you have any questions please contact us at support@gitlab.com. + +### 0. Backup + +Make a backup just in case things go south: +(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version) + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 1. Stop server + + sudo service gitlab stop + +### 2. Get the EE code + +```bash +cd /home/git/gitlab +sudo -u git -H git remote add ee https://gitlab.com/subscribers/gitlab-ee.git +sudo -u git -H git fetch --all +sudo -u git -H git checkout 7-10-stable-ee +``` + +### 3. Update config files + +* Make `/home/git/gitlab/config/gitlab.yml` same as /home/git/gitlab/config/gitlab.yml.example but with your settings. + +### 4. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL +sudo -u git -H bundle install --without development test postgres --deployment + +#PostgreSQL +sudo -u git -H bundle install --without development test mysql --deployment + +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production +sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production +``` + +### 5. Start application + + sudo service gitlab start + sudo service nginx restart + +### 6. Check application status + +Check if GitLab and its environment are configured correctly: + + sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production + +To make sure you didn't miss anything run a more thorough check with: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations upgrade complete! + +## Things went wrong? Revert to previous version (Community Edition 7.10) + +### 1. Revert the code to the previous version +```bash +cd /home/git/gitlab +sudo -u git -H git checkout 7-10-stable +``` + +### 2. Restore from the backup: + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` diff --git a/doc/update/7.11-ce-to-ee.md b/doc/update/7.11-ce-to-ee.md new file mode 100644 index 00000000000..b72680f9a88 --- /dev/null +++ b/doc/update/7.11-ce-to-ee.md @@ -0,0 +1,78 @@ +# From Community Edition 7.11 to Enterprise Edition 7.11 + +This guide assumes you have a correctly configured and tested installation of GitLab Community Edition 7.11. +If you run into any trouble or if you have any questions please contact us at support@gitlab.com. + +### 0. Backup + +Make a backup just in case things go south: +(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version) + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 1. Stop server + + sudo service gitlab stop + +### 2. Get the EE code + +```bash +cd /home/git/gitlab +sudo -u git -H git remote add ee https://gitlab.com/gitlab-org/gitlab-ee.git +sudo -u git -H git fetch --all +sudo -u git -H git checkout 7-11-stable-ee +``` + +### 3. Update config files + +* Make `/home/git/gitlab/config/gitlab.yml` same as /home/git/gitlab/config/gitlab.yml.example but with your settings. + +### 4. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL +sudo -u git -H bundle install --without development test postgres --deployment + +#PostgreSQL +sudo -u git -H bundle install --without development test mysql --deployment + +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production +sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production +``` + +### 5. Start application + + sudo service gitlab start + sudo service nginx restart + +### 6. Check application status + +Check if GitLab and its environment are configured correctly: + + sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production + +To make sure you didn't miss anything run a more thorough check with: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations upgrade complete! + +## Things went wrong? Revert to previous version (Community Edition 7.11) + +### 1. Revert the code to the previous version +```bash +cd /home/git/gitlab +sudo -u git -H git checkout 7-11-stable +``` + +### 2. Restore from the backup: + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` diff --git a/doc/update/7.12-ce-to-ee.md b/doc/update/7.12-ce-to-ee.md new file mode 100644 index 00000000000..cb31437e598 --- /dev/null +++ b/doc/update/7.12-ce-to-ee.md @@ -0,0 +1,78 @@ +# From Community Edition 7.12 to Enterprise Edition 7.12 + +This guide assumes you have a correctly configured and tested installation of GitLab Community Edition 7.12. +If you run into any trouble or if you have any questions please contact us at support@gitlab.com. + +### 0. Backup + +Make a backup just in case things go south: +(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version) + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 1. Stop server + + sudo service gitlab stop + +### 2. Get the EE code + +```bash +cd /home/git/gitlab +sudo -u git -H git remote add ee https://gitlab.com/gitlab-org/gitlab-ee.git +sudo -u git -H git fetch --all +sudo -u git -H git checkout 7-12-stable-ee +``` + +### 3. Update config files + +* Make `/home/git/gitlab/config/gitlab.yml` same as /home/git/gitlab/config/gitlab.yml.example but with your settings. + +### 4. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL +sudo -u git -H bundle install --without development test postgres --deployment + +#PostgreSQL +sudo -u git -H bundle install --without development test mysql --deployment + +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production +sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production +``` + +### 5. Start application + + sudo service gitlab start + sudo service nginx restart + +### 6. Check application status + +Check if GitLab and its environment are configured correctly: + + sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production + +To make sure you didn't miss anything run a more thorough check with: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations upgrade complete! + +## Things went wrong? Revert to previous version (Community Edition 7.12) + +### 1. Revert the code to the previous version +```bash +cd /home/git/gitlab +sudo -u git -H git checkout 7-12-stable +``` + +### 2. Restore from the backup: + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` diff --git a/doc/update/7.13-ce-to-ee.md b/doc/update/7.13-ce-to-ee.md new file mode 100644 index 00000000000..b3f08195151 --- /dev/null +++ b/doc/update/7.13-ce-to-ee.md @@ -0,0 +1,78 @@ +# From Community Edition 7.13 to Enterprise Edition 7.13 + +This guide assumes you have a correctly configured and tested installation of GitLab Community Edition 7.13. +If you run into any trouble or if you have any questions please contact us at support@gitlab.com. + +### 0. Backup + +Make a backup just in case things go south: +(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version) + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 1. Stop server + + sudo service gitlab stop + +### 2. Get the EE code + +```bash +cd /home/git/gitlab +sudo -u git -H git remote add ee https://gitlab.com/gitlab-org/gitlab-ee.git +sudo -u git -H git fetch --all +sudo -u git -H git checkout 7-13-stable-ee +``` + +### 3. Update config files + +* Make `/home/git/gitlab/config/gitlab.yml` same as /home/git/gitlab/config/gitlab.yml.example but with your settings. + +### 4. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL +sudo -u git -H bundle install --without development test postgres --deployment + +#PostgreSQL +sudo -u git -H bundle install --without development test mysql --deployment + +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production +sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production +``` + +### 5. Start application + + sudo service gitlab start + sudo service nginx restart + +### 6. Check application status + +Check if GitLab and its environment are configured correctly: + + sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production + +To make sure you didn't miss anything run a more thorough check with: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations upgrade complete! + +## Things went wrong? Revert to previous version (Community Edition 7.13) + +### 1. Revert the code to the previous version +```bash +cd /home/git/gitlab +sudo -u git -H git checkout 7-13-stable +``` + +### 2. Restore from the backup: + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` diff --git a/doc/update/7.14-ce-to-ee.md b/doc/update/7.14-ce-to-ee.md new file mode 100644 index 00000000000..721e211959d --- /dev/null +++ b/doc/update/7.14-ce-to-ee.md @@ -0,0 +1,78 @@ +# From Community Edition 7.14 to Enterprise Edition 7.14 + +This guide assumes you have a correctly configured and tested installation of GitLab Community Edition 7.14. +If you run into any trouble or if you have any questions please contact us at support@gitlab.com. + +### 0. Backup + +Make a backup just in case things go south: +(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version) + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 1. Stop server + + sudo service gitlab stop + +### 2. Get the EE code + +```bash +cd /home/git/gitlab +sudo -u git -H git remote add ee https://gitlab.com/gitlab-org/gitlab-ee.git +sudo -u git -H git fetch --all +sudo -u git -H git checkout 7-14-stable-ee +``` + +### 3. Update config files + +* Make `/home/git/gitlab/config/gitlab.yml` same as /home/git/gitlab/config/gitlab.yml.example but with your settings. + +### 4. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL +sudo -u git -H bundle install --without development test postgres --deployment + +#PostgreSQL +sudo -u git -H bundle install --without development test mysql --deployment + +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production +sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production +``` + +### 5. Start application + + sudo service gitlab start + sudo service nginx restart + +### 6. Check application status + +Check if GitLab and its environment are configured correctly: + + sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production + +To make sure you didn't miss anything run a more thorough check with: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations upgrade complete! + +## Things went wrong? Revert to previous version (Community Edition 7.14) + +### 1. Revert the code to the previous version +```bash +cd /home/git/gitlab +sudo -u git -H git checkout 7-14-stable +``` + +### 2. Restore from the backup: + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` diff --git a/doc/update/7.3-ce-to-ee.md b/doc/update/7.3-ce-to-ee.md new file mode 100644 index 00000000000..b82e89e9f96 --- /dev/null +++ b/doc/update/7.3-ce-to-ee.md @@ -0,0 +1,78 @@ +# From Community Edition 7.3 to Enterprise Edition 7.3 + +This guide assumes you have a correctly configured and tested installation of GitLab Community Edition 7.3. +If you run into any trouble or if you have any questions please contact us at support@gitlab.com. + +### 0. Backup + +Make a backup just in case things go south: +(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version) + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 1. Stop server + + sudo service gitlab stop + +### 2. Get the EE code + +```bash +cd /home/git/gitlab +sudo -u git -H git remote add ee https://gitlab.com/subscribers/gitlab-ee.git +sudo -u git -H git fetch --all +sudo -u git -H git checkout 7-3-stable-ee +``` + +### 3. Update config files + +* Make `/home/git/gitlab/config/gitlab.yml` same as /home/git/gitlab/config/gitlab.yml.example but with your settings. + +### 4. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL +sudo -u git -H bundle install --without development test postgres --deployment + +#PostgreSQL +sudo -u git -H bundle install --without development test mysql --deployment + +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production +sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production +``` + +### 5. Start application + + sudo service gitlab start + sudo service nginx restart + +### 6. Check application status + +Check if GitLab and its environment are configured correctly: + + sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production + +To make sure you didn't miss anything run a more thorough check with: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations upgrade complete! + +## Things went wrong? Revert to previous version (Community Edition 7.3) + +### 1. Revert the code to the previous version +```bash +cd /home/git/gitlab +sudo -u git -H git checkout 7-3-stable +``` + +### 2. Restore from the backup: + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` diff --git a/doc/update/7.4-ce-to-ee.md b/doc/update/7.4-ce-to-ee.md new file mode 100644 index 00000000000..5f13db665c5 --- /dev/null +++ b/doc/update/7.4-ce-to-ee.md @@ -0,0 +1,78 @@ +# From Community Edition 7.4 to Enterprise Edition 7.4 + +This guide assumes you have a correctly configured and tested installation of GitLab Community Edition 7.4. +If you run into any trouble or if you have any questions please contact us at support@gitlab.com. + +### 0. Backup + +Make a backup just in case things go south: +(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version) + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 1. Stop server + + sudo service gitlab stop + +### 2. Get the EE code + +```bash +cd /home/git/gitlab +sudo -u git -H git remote add ee https://gitlab.com/subscribers/gitlab-ee.git +sudo -u git -H git fetch --all +sudo -u git -H git checkout 7-4-stable-ee +``` + +### 3. Update config files + +* Make `/home/git/gitlab/config/gitlab.yml` same as /home/git/gitlab/config/gitlab.yml.example but with your settings. + +### 4. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL +sudo -u git -H bundle install --without development test postgres --deployment + +#PostgreSQL +sudo -u git -H bundle install --without development test mysql --deployment + +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production +sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production +``` + +### 5. Start application + + sudo service gitlab start + sudo service nginx restart + +### 6. Check application status + +Check if GitLab and its environment are configured correctly: + + sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production + +To make sure you didn't miss anything run a more thorough check with: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations upgrade complete! + +## Things went wrong? Revert to previous version (Community Edition 7.4) + +### 1. Revert the code to the previous version +```bash +cd /home/git/gitlab +sudo -u git -H git checkout 7-4-stable +``` + +### 2. Restore from the backup: + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` diff --git a/doc/update/7.5-ce-to-ee.md b/doc/update/7.5-ce-to-ee.md new file mode 100644 index 00000000000..a8fb9e6a300 --- /dev/null +++ b/doc/update/7.5-ce-to-ee.md @@ -0,0 +1,78 @@ +# From Community Edition 7.5 to Enterprise Edition 7.5 + +This guide assumes you have a correctly configured and tested installation of GitLab Community Edition 7.5. +If you run into any trouble or if you have any questions please contact us at support@gitlab.com. + +### 0. Backup + +Make a backup just in case things go south: +(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version) + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 1. Stop server + + sudo service gitlab stop + +### 2. Get the EE code + +```bash +cd /home/git/gitlab +sudo -u git -H git remote add ee https://gitlab.com/subscribers/gitlab-ee.git +sudo -u git -H git fetch --all +sudo -u git -H git checkout 7-5-stable-ee +``` + +### 3. Update config files + +* Make `/home/git/gitlab/config/gitlab.yml` same as /home/git/gitlab/config/gitlab.yml.example but with your settings. + +### 4. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL +sudo -u git -H bundle install --without development test postgres --deployment + +#PostgreSQL +sudo -u git -H bundle install --without development test mysql --deployment + +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production +sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production +``` + +### 5. Start application + + sudo service gitlab start + sudo service nginx restart + +### 6. Check application status + +Check if GitLab and its environment are configured correctly: + + sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production + +To make sure you didn't miss anything run a more thorough check with: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations upgrade complete! + +## Things went wrong? Revert to previous version (Community Edition 7.5) + +### 1. Revert the code to the previous version +```bash +cd /home/git/gitlab +sudo -u git -H git checkout 7-5-stable +``` + +### 2. Restore from the backup: + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` diff --git a/doc/update/7.6-ce-to-ee.md b/doc/update/7.6-ce-to-ee.md new file mode 100644 index 00000000000..1e8c9434eff --- /dev/null +++ b/doc/update/7.6-ce-to-ee.md @@ -0,0 +1,78 @@ +# From Community Edition 7.6 to Enterprise Edition 7.6 + +This guide assumes you have a correctly configured and tested installation of GitLab Community Edition 7.6. +If you run into any trouble or if you have any questions please contact us at support@gitlab.com. + +### 0. Backup + +Make a backup just in case things go south: +(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version) + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 1. Stop server + + sudo service gitlab stop + +### 2. Get the EE code + +```bash +cd /home/git/gitlab +sudo -u git -H git remote add ee https://gitlab.com/subscribers/gitlab-ee.git +sudo -u git -H git fetch --all +sudo -u git -H git checkout 7-6-stable-ee +``` + +### 3. Update config files + +* Make `/home/git/gitlab/config/gitlab.yml` same as /home/git/gitlab/config/gitlab.yml.example but with your settings. + +### 4. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL +sudo -u git -H bundle install --without development test postgres --deployment + +#PostgreSQL +sudo -u git -H bundle install --without development test mysql --deployment + +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production +sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production +``` + +### 5. Start application + + sudo service gitlab start + sudo service nginx restart + +### 6. Check application status + +Check if GitLab and its environment are configured correctly: + + sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production + +To make sure you didn't miss anything run a more thorough check with: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations upgrade complete! + +## Things went wrong? Revert to previous version (Community Edition 7.6) + +### 1. Revert the code to the previous version +```bash +cd /home/git/gitlab +sudo -u git -H git checkout 7-6-stable +``` + +### 2. Restore from the backup: + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` diff --git a/doc/update/7.7-ce-to-ee.md b/doc/update/7.7-ce-to-ee.md new file mode 100644 index 00000000000..37d23ce2621 --- /dev/null +++ b/doc/update/7.7-ce-to-ee.md @@ -0,0 +1,78 @@ +# From Community Edition 7.7 to Enterprise Edition 7.7 + +This guide assumes you have a correctly configured and tested installation of GitLab Community Edition 7.7. +If you run into any trouble or if you have any questions please contact us at support@gitlab.com. + +### 0. Backup + +Make a backup just in case things go south: +(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version) + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 1. Stop server + + sudo service gitlab stop + +### 2. Get the EE code + +```bash +cd /home/git/gitlab +sudo -u git -H git remote add ee https://gitlab.com/subscribers/gitlab-ee.git +sudo -u git -H git fetch --all +sudo -u git -H git checkout 7-7-stable-ee +``` + +### 3. Update config files + +* Make `/home/git/gitlab/config/gitlab.yml` same as /home/git/gitlab/config/gitlab.yml.example but with your settings. + +### 4. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL +sudo -u git -H bundle install --without development test postgres --deployment + +#PostgreSQL +sudo -u git -H bundle install --without development test mysql --deployment + +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production +sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production +``` + +### 5. Start application + + sudo service gitlab start + sudo service nginx restart + +### 6. Check application status + +Check if GitLab and its environment are configured correctly: + + sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production + +To make sure you didn't miss anything run a more thorough check with: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations upgrade complete! + +## Things went wrong? Revert to previous version (Community Edition 7.7) + +### 1. Revert the code to the previous version +```bash +cd /home/git/gitlab +sudo -u git -H git checkout 7-7-stable +``` + +### 2. Restore from the backup: + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` diff --git a/doc/update/7.8-ce-to-ee.md b/doc/update/7.8-ce-to-ee.md new file mode 100644 index 00000000000..e0ab01d7092 --- /dev/null +++ b/doc/update/7.8-ce-to-ee.md @@ -0,0 +1,78 @@ +# From Community Edition 7.8 to Enterprise Edition 7.8 + +This guide assumes you have a correctly configured and tested installation of GitLab Community Edition 7.8. +If you run into any trouble or if you have any questions please contact us at support@gitlab.com. + +### 0. Backup + +Make a backup just in case things go south: +(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version) + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 1. Stop server + + sudo service gitlab stop + +### 2. Get the EE code + +```bash +cd /home/git/gitlab +sudo -u git -H git remote add ee https://gitlab.com/subscribers/gitlab-ee.git +sudo -u git -H git fetch --all +sudo -u git -H git checkout 7-8-stable-ee +``` + +### 3. Update config files + +* Make `/home/git/gitlab/config/gitlab.yml` same as /home/git/gitlab/config/gitlab.yml.example but with your settings. + +### 4. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL +sudo -u git -H bundle install --without development test postgres --deployment + +#PostgreSQL +sudo -u git -H bundle install --without development test mysql --deployment + +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production +sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production +``` + +### 5. Start application + + sudo service gitlab start + sudo service nginx restart + +### 6. Check application status + +Check if GitLab and its environment are configured correctly: + + sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production + +To make sure you didn't miss anything run a more thorough check with: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations upgrade complete! + +## Things went wrong? Revert to previous version (Community Edition 7.8) + +### 1. Revert the code to the previous version +```bash +cd /home/git/gitlab +sudo -u git -H git checkout 7-8-stable +``` + +### 2. Restore from the backup: + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` diff --git a/doc/update/7.9-ce-to-ee.md b/doc/update/7.9-ce-to-ee.md new file mode 100644 index 00000000000..78419f78a61 --- /dev/null +++ b/doc/update/7.9-ce-to-ee.md @@ -0,0 +1,78 @@ +# From Community Edition 7.9 to Enterprise Edition 7.9 + +This guide assumes you have a correctly configured and tested installation of GitLab Community Edition 7.9. +If you run into any trouble or if you have any questions please contact us at support@gitlab.com. + +### 0. Backup + +Make a backup just in case things go south: +(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version) + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 1. Stop server + + sudo service gitlab stop + +### 2. Get the EE code + +```bash +cd /home/git/gitlab +sudo -u git -H git remote add ee https://gitlab.com/subscribers/gitlab-ee.git +sudo -u git -H git fetch --all +sudo -u git -H git checkout 7-9-stable-ee +``` + +### 3. Update config files + +* Make `/home/git/gitlab/config/gitlab.yml` same as /home/git/gitlab/config/gitlab.yml.example but with your settings. + +### 4. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL +sudo -u git -H bundle install --without development test postgres --deployment + +#PostgreSQL +sudo -u git -H bundle install --without development test mysql --deployment + +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production +sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production +``` + +### 5. Start application + + sudo service gitlab start + sudo service nginx restart + +### 6. Check application status + +Check if GitLab and its environment are configured correctly: + + sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production + +To make sure you didn't miss anything run a more thorough check with: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations upgrade complete! + +## Things went wrong? Revert to previous version (Community Edition 7.9) + +### 1. Revert the code to the previous version +```bash +cd /home/git/gitlab +sudo -u git -H git checkout 7-9-stable +``` + +### 2. Restore from the backup: + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` diff --git a/doc/update/8.0-ce-to-ee.md b/doc/update/8.0-ce-to-ee.md new file mode 100644 index 00000000000..2ff91a913fe --- /dev/null +++ b/doc/update/8.0-ce-to-ee.md @@ -0,0 +1,91 @@ +# From Community Edition 8.0 to Enterprise Edition 8.0 + +This guide assumes you have a correctly configured and tested installation of GitLab Community Edition 8.0. +If you run into any trouble or if you have any questions please contact us at [support@gitlab.com]. + +### 0. Backup + +Make a backup just in case something goes wrong: + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +For installations using MySQL, this may require granting "LOCK TABLES" +privileges to the GitLab user on the database version. + +### 1. Stop server + + sudo service gitlab stop + +### 2. Get the EE code + +```bash +cd /home/git/gitlab +sudo -u git -H git remote add ee https://gitlab.com/gitlab-org/gitlab-ee.git +sudo -u git -H git fetch --all +sudo -u git -H git checkout 8-0-stable-ee +``` + +### 3. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL installations (note: the line below states '--without postgres') +sudo -u git -H bundle install --without postgres development test --deployment + +# PostgreSQL installations (note: the line below states '--without mysql') +sudo -u git -H bundle install --without mysql development test --deployment + +# Run database migrations +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production + +# Clean up assets and cache +sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production +``` + +### 4. Update config files + +There are new configuration options available for `gitlab.yml`. View them with +the command below and apply them to your current `config/gitlab.yml`: + +``` +git diff origin/7-14-stable:config/gitlab.yml.example origin/8-0-stable:config/gitlab.yml.example +``` + +### 5. Start application + + sudo service gitlab start + sudo service nginx restart + +### 6. Check application status + +Check if GitLab and its environment are configured correctly: + + sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production + +To make sure you didn't miss anything run a more thorough check with: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations upgrade complete! + +## Things went south? Revert to previous version (Community Edition 8.0) + +### 1. Revert the code to the previous version + +```bash +cd /home/git/gitlab +sudo -u git -H git checkout 8-0-stable +``` + +### 2. Restore from the backup + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` + +[support@gitlab.com]: mailto:support@gitlab.com diff --git a/doc/update/8.1-ce-to-ee.md b/doc/update/8.1-ce-to-ee.md new file mode 100644 index 00000000000..ce10da580cc --- /dev/null +++ b/doc/update/8.1-ce-to-ee.md @@ -0,0 +1,91 @@ +# From Community Edition 8.1 to Enterprise Edition 8.1 + +This guide assumes you have a correctly configured and tested installation of GitLab Community Edition 8.1. +If you run into any trouble or if you have any questions please contact us at [support@gitlab.com]. + +### 0. Backup + +Make a backup just in case something goes wrong: + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +For installations using MySQL, this may require granting "LOCK TABLES" +privileges to the GitLab user on the database version. + +### 1. Stop server + + sudo service gitlab stop + +### 2. Get the EE code + +```bash +cd /home/git/gitlab +sudo -u git -H git remote add ee https://gitlab.com/gitlab-org/gitlab-ee.git +sudo -u git -H git fetch --all +sudo -u git -H git checkout 8-1-stable-ee +``` + +### 3. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL installations (note: the line below states '--without postgres') +sudo -u git -H bundle install --without postgres development test --deployment + +# PostgreSQL installations (note: the line below states '--without mysql') +sudo -u git -H bundle install --without mysql development test --deployment + +# Run database migrations +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production + +# Clean up assets and cache +sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production +``` + +### 4. Update config files + +There are new configuration options available for `gitlab.yml`. View them with +the command below and apply them to your current `config/gitlab.yml`: + +``` +git diff origin/8-0-stable:config/gitlab.yml.example origin/8-1-stable:config/gitlab.yml.example +``` + +### 5. Start application + + sudo service gitlab start + sudo service nginx restart + +### 6. Check application status + +Check if GitLab and its environment are configured correctly: + + sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production + +To make sure you didn't miss anything run a more thorough check with: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations upgrade complete! + +## Things went south? Revert to previous version (Community Edition 8.1) + +### 1. Revert the code to the previous version + +```bash +cd /home/git/gitlab +sudo -u git -H git checkout 8-1-stable +``` + +### 2. Restore from the backup + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` + +[support@gitlab.com]: mailto:support@gitlab.com diff --git a/doc/update/8.2-ce-to-ee.md b/doc/update/8.2-ce-to-ee.md new file mode 100644 index 00000000000..febe49d39be --- /dev/null +++ b/doc/update/8.2-ce-to-ee.md @@ -0,0 +1,91 @@ +# From Community Edition 8.2 to Enterprise Edition 8.2 + +This guide assumes you have a correctly configured and tested installation of GitLab Community Edition 8.2. +If you run into any trouble or if you have any questions please contact us at [support@gitlab.com]. + +### 0. Backup + +Make a backup just in case something goes wrong: + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +For installations using MySQL, this may require granting "LOCK TABLES" +privileges to the GitLab user on the database version. + +### 1. Stop server + + sudo service gitlab stop + +### 2. Get the EE code + +```bash +cd /home/git/gitlab +sudo -u git -H git remote add ee https://gitlab.com/gitlab-org/gitlab-ee.git +sudo -u git -H git fetch --all +sudo -u git -H git checkout 8-2-stable-ee +``` + +### 3. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL installations (note: the line below states '--without postgres') +sudo -u git -H bundle install --without postgres development test --deployment + +# PostgreSQL installations (note: the line below states '--without mysql') +sudo -u git -H bundle install --without mysql development test --deployment + +# Run database migrations +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production + +# Clean up assets and cache +sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production +``` + +### 4. Update config files + +There are new configuration options available for `gitlab.yml`. View them with +the command below and apply them to your current `config/gitlab.yml`: + +``` +git diff origin/8-1-stable:config/gitlab.yml.example origin/8-2-stable:config/gitlab.yml.example +``` + +### 5. Start application + + sudo service gitlab start + sudo service nginx restart + +### 6. Check application status + +Check if GitLab and its environment are configured correctly: + + sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production + +To make sure you didn't miss anything run a more thorough check with: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations upgrade complete! + +## Things went south? Revert to previous version (Community Edition 8.2) + +### 1. Revert the code to the previous version + +```bash +cd /home/git/gitlab +sudo -u git -H git checkout 8-2-stable +``` + +### 2. Restore from the backup + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` + +[support@gitlab.com]: mailto:support@gitlab.com diff --git a/doc/update/README.md b/doc/update/README.md index 0472537eeb5..941e3581f67 100644 --- a/doc/update/README.md +++ b/doc/update/README.md @@ -1,5 +1,9 @@ Depending on the installation method and your GitLab version, there are multiple update guides. Choose one that fits your needs. +## CE to EE + +- [The CE to EE update guides](https://gitlab.com/gitlab-org/gitlab-ee/tree/master/doc/update) the steps are very similar to a version upgrade: stop the server, get the code, update config files for the new functionality, install libs and do migrations, update the init script, start the application and check the application status. + ## Omnibus Packages - [Omnibus update guide](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/update.md) contains the steps needed to update a GitLab [package](https://about.gitlab.com/downloads/). @@ -7,7 +11,7 @@ Depending on the installation method and your GitLab version, there are multiple ## Installation from source - [The individual upgrade guides](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update) are for those who have installed GitLab from source. -- [The CE to EE update guides](https://gitlab.com/subscribers/gitlab-ee/tree/master/doc/update) are for subscribers of the Enterprise Edition only. The steps are very similar to a version upgrade: stop the server, get the code, update config files for the new functionality, install libs and do migrations, update the init script, start the application and check the application status. +- [The CE to EE update guides](https://gitlab.com/gitlab-org/gitlab-ee/tree/master/doc/update). The steps are very similar to a version upgrade: stop the server, get the code, update config files for the new functionality, install libs and do migrations, update the init script, start the application and check the application status. - [Upgrader](upgrader.md) is an automatic ruby script that performs the update for installations from source. - [Patch versions](patch_versions.md) guide includes the steps needed for a patch version, eg. 6.2.0 to 6.2.1. diff --git a/doc/web_hooks/web_hooks.md b/doc/web_hooks/web_hooks.md index 7d838187a26..8d2936a22cc 100644 --- a/doc/web_hooks/web_hooks.md +++ b/doc/web_hooks/web_hooks.md @@ -6,6 +6,10 @@ You can configure web hooks to listen for specific events like pushes, issues or Web hooks can be used to update an external issue tracker, trigger CI builds, update a backup mirror, or even deploy to your production server. +If you have a big set of projects in the one group then it will be convenient for you to configure web hooks globally for the whole group. You can add the group level web hooks on the group settings page. + +If you send a web hook to an SSL endpoint [the certificate will not be verified](https://gitlab.com/gitlab-org/gitlab-ce/blob/ccd617e58ea71c42b6b073e692447d0fe3c00be6/app/models/web_hook.rb#L35) since many people use self-signed certificates. + ## SSL Verification By default, the SSL certificate of the webhook endpoint is verified based on diff --git a/doc/workflow/README.md b/doc/workflow/README.md index a2653704c33..823ee4a193f 100644 --- a/doc/workflow/README.md +++ b/doc/workflow/README.md @@ -5,13 +5,21 @@ - [Feature branch workflow](workflow.md) - [GitLab Flow](gitlab_flow.md) - [Groups](groups.md) +- [Importing to GitLab](doc/importing/README.md) - [Keyboard shortcuts](shortcuts.md) - [Labels](labels.md) +- [Manage large binaries with git annex](git_annex.md) +- [Merge Request Approvals](merge_request_approvals.md) - [Notification emails](notifications.md) - [Project Features](project_features.md) - [Project forking workflow](forking_workflow.md) - [Project users](add-user/add-user.md) - [Protected branches](protected_branches.md) +- [Fast-forward merge](ff_merge.md) +- [Rebase before merge](rebase_before_merge.md) +- [Sharing a project with a group](share_with_group.md) +- [Share projects with other groups](share_projects_with_other_groups.md) +- [Two-factor Authentication (2FA)](two_factor_authentication.md) - [Web Editor](web_editor.md) - [Releases](releases.md) - [Merge Requests](merge_requests.md) diff --git a/doc/workflow/ff_merge.md b/doc/workflow/ff_merge.md new file mode 100644 index 00000000000..3d9e500e997 --- /dev/null +++ b/doc/workflow/ff_merge.md @@ -0,0 +1,12 @@ +# Fast-forward merge + +GitLab Enterprise Edition offers a way to accept merge request without creating merge commit. +If you prefer linear git history - this might be a good feature for you. +You can configure this per project basis by navigating to the project settings page and selecting `Only fast-forward merging` checkbox. + + + +Now when you visit merge request page you will be able to accept it only if fast-forward merge is possible. +If target branch is ahead of source branch - you need to rebase source branch before you will be able to do fast-forward merge. + +For simple rebase operations you can use [Rebase before merge](rebase_before_merge.md) feature. diff --git a/doc/workflow/ff_merge.png b/doc/workflow/ff_merge.png Binary files differnew file mode 100644 index 00000000000..cab793afb18 --- /dev/null +++ b/doc/workflow/ff_merge.png diff --git a/doc/workflow/git_annex.md b/doc/workflow/git_annex.md new file mode 100644 index 00000000000..3efc882a123 --- /dev/null +++ b/doc/workflow/git_annex.md @@ -0,0 +1,84 @@ +# Git annex + +The biggest limitation of git compared to some older centralized version control systems has been the maximum size of the repositories. +The general recommendation is to not have git repositories larger than 1GB to preserve performance. +Although GitLab has no limit (some repositories in GitLab are over 50GB!) we subscribe to the advise to keep repositories as small as you can. + +Not being able to version control large binaries is a big problem for many larger organizations. +Video, photo's, audio, compiled binaries and many other types of files are too large. +As a workaround, people keep artwork-in-progress in a Dropbox folder and only check in the final result. +This results in using outdated files, not having a complete history and the risk of losing work. + +This problem is solved by integrating the awesome [git-annex](https://git-annex.branchable.com/). +Git-annex allows managing large binaries with git, without checking the contents into git. +You check in only a symlink that contains the SHA-1 of the large binary. +If you need the large binary you can sync it from the GitLab server over rsync, a very fast file copying tool. + +<!-- more --> + +## Using GitLab Annex + +For example, if you want to upload a very large file and check it into your Git repository: + +```bash +git clone git@gitlab.example.com:group/project.git +git annex init 'My Laptop' # initialize the annex project +cp ~/tmp/debian.iso ./ # copy a large file into the current directory +git annex add . # add the large file to git annex +git commit -am"Added Debian iso" # commit the file meta data +git annex sync --content # sync the git repo and large file to the GitLab server +``` + +Downloading a single large file is also very simple: + +```bash +git clone git@gitlab.example.com:group/project.git +git annex sync # sync git branches but not the large file +git annex get debian.iso # download the large file +``` + +To download all files: + +```bash +git clone git@gitlab.example.com:group/project.git +git annex sync --content # sync git branches and download all the large files +``` + +You don't have to setup git-annex on a separate server or add annex remotes to the repository. +Git-annex without GitLab gives everyone that can access the server access to the files of all projects. +GitLab annex ensures you can only acces files of projects you work on (developer, master or owner role). + +## How it works + +Internally GitLab uses [GitLab Shell](https://gitlab.com/gitlab-org/gitlab-shell) to handle ssh access and this was a great integration point for git-annex. +We've added a setting to GitLab Shell so you can disable GitLab Annex support if you don't want it. + +You'll have to use ssh style links for to git remote to your GitLab server instead of https style links. + +## Troubleshooting tips + +Differences in version of `git-annex` on `GitLab` server and on local machine can cause `git-annex` to raise unpredicted warnings and errors. +Although there is no general guide for `git-annex` errors, there are a few tips on how to go arround the warnings. + +### git-annex-shell: Not a git-annex or gcrypt repository. + +This warning can appear on inital `git annex sync --content`. This is caused by differences in `git-annex-shell`, read more about it in [this git-annex issue](https://git-annex.branchable.com/forum/Error_from_git-annex-shell_on_creation_of_gcrypt_special_remote/). + +Important thing to note is that the `sync` succeeds and the files are pushed to the GitLab repository. After this warning it is required to do: + +``` +git config remote.origin.annex-ignore false +``` + +in the repository that was pushed. + +Consecutive `git annex sync --content` **should not** produce this warning and the output should look like this: + +``` +commit ok +pull origin +ok +pull origin +ok +push origin +``` diff --git a/doc/workflow/gitlab_flow.md b/doc/workflow/gitlab_flow.md index 9a24a1e252a..136b5ff3df9 100644 --- a/doc/workflow/gitlab_flow.md +++ b/doc/workflow/gitlab_flow.md @@ -238,6 +238,16 @@ In a CI strategy you can merge in master at the start of the day to prevent pain In a synchronization point strategy you only merge in from well defined points in time, for example a tagged release. This strategy is [advocated by Linus Torvalds](https://www.mail-archive.com/dri-devel@lists.sourceforge.net/msg39091.html) because the state of the code at these points is better known. +GitLab Enterprise Edition offers a way to rebase before merging a merge request. You can configure this per project bassis by navigating to the project settings page and selecting `Merge Requests Rebase` checkbox. + + + +Before accepting a merge request, select `rebase before merge`. + + +GitLab will attempt to cleanly rebase before merging branches. If clean rebase is not possible, regular merge will be performed. +If clean rebase is possible and history of the traget branch will be altered with the the merge. + In conclusion, we can say that you should try to prevent merge commits, but not eliminate them. Your codebase should be clean but your history should represent what actually happened. Developing software happen in small messy steps and it is OK to have your history reflect this. @@ -310,7 +320,8 @@ If you have not pushed your commits to a shared location yet you can also rebase Do not merge in upstream if your code will work and merge cleanly without doing so, Linus even says that [you should never merge in upstream at random points, only at major releases](http://lwn.net/Articles/328438/). Merging only when needed prevents creating merge commits in your feature branch that later end up littering the master history. -### References +## References - [Sketch file](https://www.dropbox.com/s/58dvsj5votbwrzv/git_flows.sketch?dl=0) with vectors of images in this article - [Git Flow by Vincent Driessen](http://nvie.com/posts/a-successful-git-branching-model/) +- [Blog post with responses](https://about.gitlab.com/2014/09/29/gitlab-flow/)
\ No newline at end of file diff --git a/doc/workflow/groups.md b/doc/workflow/groups.md index 52bf611dc5e..ad795d72471 100644 --- a/doc/workflow/groups.md +++ b/doc/workflow/groups.md @@ -69,3 +69,29 @@ gitlab_rails['gitlab_default_can_create_group'] = false # For installations from source, uncomment the 'default_can_create_group' # line in /home/git/gitlab/config/gitlab.yml ``` + +## Lock project membership to members of the group + +In GitLab Enterprise Edition it is possible to lock membership in project to the level of members in group. + +This allows group owner to lock down any new project membership to any of the projects within the group allowing tighter control over project membership. + +To enable this feature, navigate to group settings page, select `Member lock` and `Save group`. + + + +This will disable the option for all users who previously had permissions to operate project memberships so no new users can be added. Furthermore, any request to add new user to project through API will not be possible. + +## Namespaces in groups + +By default, groups only get 20 namespaces at a time because the API results are paginated. + +To get more (up to 100), pass the following as an argument to the API call: +``` +/groups?per_page=100 +``` + +And to switch pages add: +``` +/groups?per_page=100&page=2 +``` diff --git a/doc/workflow/groups/max_access_level.png b/doc/workflow/groups/max_access_level.png Binary files differnew file mode 100644 index 00000000000..71106a8a5a0 --- /dev/null +++ b/doc/workflow/groups/max_access_level.png diff --git a/doc/workflow/groups/membership_lock.png b/doc/workflow/groups/membership_lock.png Binary files differnew file mode 100644 index 00000000000..e32fe2881e8 --- /dev/null +++ b/doc/workflow/groups/membership_lock.png diff --git a/doc/workflow/groups/other_group_sees_shared_project.png b/doc/workflow/groups/other_group_sees_shared_project.png Binary files differnew file mode 100644 index 00000000000..cbf2c3c1fdc --- /dev/null +++ b/doc/workflow/groups/other_group_sees_shared_project.png diff --git a/doc/workflow/groups/share_project_with_groups.png b/doc/workflow/groups/share_project_with_groups.png Binary files differnew file mode 100644 index 00000000000..a5dbc89fe90 --- /dev/null +++ b/doc/workflow/groups/share_project_with_groups.png diff --git a/doc/workflow/importing/import_projects_from_github.md b/doc/workflow/importing/import_projects_from_github.md index 2d77c6d1172..3ed271b6772 100644 --- a/doc/workflow/importing/import_projects_from_github.md +++ b/doc/workflow/importing/import_projects_from_github.md @@ -1,11 +1,13 @@ # Import your project from GitHub to GitLab
-It takes just a couple of steps to import your existing GitHub projects to GitLab. Keep in mind that it is possible only if
-GitHub support is enabled on your GitLab instance. You can read more about GitHub support [here](http://doc.gitlab.com/ce/integration/github.html)
+You can import your existing GitHub Enterprise projects following these steps.
+
+* First, you need to [enable GitHub Enterprise support](http://doc.gitlab.com/ee/integration/github.html) on your GitLab instance.
If you want to import from a GitHub Enterprise instance, you need to use GitLab Enterprise; please see the [EE docs for the GitHub integration](http://doc.gitlab.com/ee/integration/github.html).
* Sign in to GitLab.com and go to your dashboard.
+
* To get to the importer page, you need to go to the "New project" page.

diff --git a/doc/workflow/merge_request_approvals.md b/doc/workflow/merge_request_approvals.md new file mode 100644 index 00000000000..f77886e0115 --- /dev/null +++ b/doc/workflow/merge_request_approvals.md @@ -0,0 +1,66 @@ +# Merge Request Approvals + +If you want to make sure every merge request is approved by one or more +people, you can enforce this workflow by using merge request approvals. +Merge request approvals allow you to set the number of necessary approvals +and predefine a list of approvers that will need to approve every +merge request in a project. + + + +## Configuring Approvals + +You can configure the approvals in the project settings, under merge requests. + + + +To enable it, set **Approvals required** to 1 or higher. + +### Approvals Required + +This sets the amount of approvals required before being able to merge a merge request. +At 0, this disables the feature. Any value above requires that amount of different +users to approve the merge request. + +### Reset approvals on push + +With this setting turned on, approvals are reset when a new push +is done to the merge request branch. + +Turn "Reset approvals on push" off if you want approvals to persist, +independent of changes to the merge request. + +### Approvers + +At approvers you can define the default set of users that need to approve a +merge request. + +If there are more approvers than required approvals, any subset of these users +can approve the merge request. + +If there are less approvers than required approvals, all the set approvers plus +any other user(s) need to approve the merge request before being able to merge it. + + + +If the approvers are equal to the amount of required approvals, all the approvers are +required to approve merge requests. + + + +Note that approvers can be changed during merge request creation. + +## Using Approvals + +After configuring Approvals, you will see the following during merge request creation. + + + +You can change the default set of approvers before creating the merge request. +You can't change the amount of required approvals. This ensures that you're +not forced to adjust settings when someone is unavailable for approval, yet +the process is still enforced. + +To approve a merge request, simply press the button. + +
\ No newline at end of file diff --git a/doc/workflow/merge_request_approvals/1_named_approval.png b/doc/workflow/merge_request_approvals/1_named_approval.png Binary files differnew file mode 100644 index 00000000000..9757dd81d10 --- /dev/null +++ b/doc/workflow/merge_request_approvals/1_named_approval.png diff --git a/doc/workflow/merge_request_approvals/2_approvals.png b/doc/workflow/merge_request_approvals/2_approvals.png Binary files differnew file mode 100644 index 00000000000..14f631ca023 --- /dev/null +++ b/doc/workflow/merge_request_approvals/2_approvals.png diff --git a/doc/workflow/merge_request_approvals/2_named_approvals.png b/doc/workflow/merge_request_approvals/2_named_approvals.png Binary files differnew file mode 100644 index 00000000000..a4e275c61af --- /dev/null +++ b/doc/workflow/merge_request_approvals/2_named_approvals.png diff --git a/doc/workflow/merge_request_approvals/approvals_mr.png b/doc/workflow/merge_request_approvals/approvals_mr.png Binary files differnew file mode 100644 index 00000000000..6a1041105e0 --- /dev/null +++ b/doc/workflow/merge_request_approvals/approvals_mr.png diff --git a/doc/workflow/merge_request_approvals/approvals_settings.png b/doc/workflow/merge_request_approvals/approvals_settings.png Binary files differnew file mode 100644 index 00000000000..29502ce2a2e --- /dev/null +++ b/doc/workflow/merge_request_approvals/approvals_settings.png diff --git a/doc/workflow/merge_request_settings.png b/doc/workflow/merge_request_settings.png Binary files differnew file mode 100644 index 00000000000..2740aafa72f --- /dev/null +++ b/doc/workflow/merge_request_settings.png diff --git a/doc/workflow/merge_request_widget.png b/doc/workflow/merge_request_widget.png Binary files differnew file mode 100644 index 00000000000..cbacbabc605 --- /dev/null +++ b/doc/workflow/merge_request_widget.png diff --git a/doc/workflow/rebase_before_merge.md b/doc/workflow/rebase_before_merge.md new file mode 100644 index 00000000000..7128d3ae671 --- /dev/null +++ b/doc/workflow/rebase_before_merge.md @@ -0,0 +1,19 @@ +# Rebase before merge + +GitLab Enterprise Edition offers a way to rebase source branch of merge request. +This feature is part of [Fast-forward merge](ff_merge.md) feature. +It allows you to rebase source branch of merge request in order to perform fast-forward merge. + +You can configure this per project basis by navigating to the project settings page and selecting `Rebase button` checkbox. +This checkbox is visible only if you have `Only fast-forward merging` checkbox enabled. + + + + +Now if fast-forward merge requires rebase - you will see rebase button: + + + +GitLab will attempt to rebase source branch. If rebase succeed you will see `Accept merge request` button. +If clean rebase is not possible - you need to do rebase manually. +Possibly rebase requires some conflicts to be resolved by human. diff --git a/doc/workflow/rebase_request_widget.png b/doc/workflow/rebase_request_widget.png Binary files differnew file mode 100644 index 00000000000..b65b18d16be --- /dev/null +++ b/doc/workflow/rebase_request_widget.png diff --git a/doc/workflow/share_projects_with_other_groups.md b/doc/workflow/share_projects_with_other_groups.md new file mode 100644 index 00000000000..4c59f59c587 --- /dev/null +++ b/doc/workflow/share_projects_with_other_groups.md @@ -0,0 +1,30 @@ +# Share Projects with other Groups + +In GitLab Enterprise Edition you can share projects with other groups. +This makes it possible to add a group of users to a project with a single action. + +## Groups as collections of users + +In GitLab Community Edition groups are used primarily to [create collections of projects](groups.md). +In GitLab Enterprise Edition you can also take advantage of the fact that groups define collections of _users_, namely the group members. + +## Sharing a project with a group of users + +The primary mechanism to give a group of users, say 'Engineering', access to a project, say 'Project Acme', in GitLab is to make the 'Engineering' group the owner of 'Project Acme'. +But what if 'Project Acme' already belongs to another group, say 'Open Source'? +This is where the (Enterprise Edition only) group sharing feature can be of use. + +To share 'Project Acme' with the 'Engineering' group, go to the project settings page for 'Project Acme' and use the left navigation menu to go to the 'Groups' section. + + + +Now you can add the 'Engineering' group with the maximum access level of your choice. +After sharing 'Project Acme' with 'Engineering', the project is listed on the group dashboard. + + + +## Maximum access level + + + +In the screenshot above, the maximum access level of 'Developer' for members from 'Engineering' means that users with higher access levels in 'Engineering' ('Master' or 'Owner') will only have 'Developer' access to 'Project Acme'. diff --git a/doc/workflow/share_with_group.md b/doc/workflow/share_with_group.md new file mode 100644 index 00000000000..3b7690973cb --- /dev/null +++ b/doc/workflow/share_with_group.md @@ -0,0 +1,13 @@ +# Sharing a project with a group + +If you want to share a single project in a group with another group, +you can do so easily. By setting the permission you can quickly +give a select group of users access to a project in a restricted manner. + +In a project go to the project settings -> groups. + +Now you can select a group that you want to share this project with and with +which maximum access level. Users in that group are able to access this project +with their set group access level, up to the maximum level that you've set. + + diff --git a/doc/workflow/share_with_group.png b/doc/workflow/share_with_group.png Binary files differnew file mode 100644 index 00000000000..a0ca6f14552 --- /dev/null +++ b/doc/workflow/share_with_group.png diff --git a/features/admin/appearance.feature b/features/admin/appearance.feature new file mode 100644 index 00000000000..5c1dd7531c1 --- /dev/null +++ b/features/admin/appearance.feature @@ -0,0 +1,37 @@ +Feature: Admin Appearance + Scenario: Create new appearance + Given I sign in as an admin + And I visit admin appearance page + When submit form with new appearance + Then I should be redirected to admin appearance page + And I should see newly created appearance + + Scenario: Preview appearance + Given application has custom appearance + And I sign in as an admin + When I visit admin appearance page + And I click preview button + Then I should see a customized appearance + + Scenario: Custom sign-in page + Given application has custom appearance + When I visit login page + Then I should see a customized appearance + + Scenario: Appearance logo + Given application has custom appearance + And I sign in as an admin + And I visit admin appearance page + When I attach a logo + Then I should see a logo + And I remove the logo + Then I should see logo removed + + Scenario: Header logos + Given application has custom appearance + And I sign in as an admin + And I visit admin appearance page + When I attach header logos + Then I should see header logos + And I remove the header logos + Then I should see header logos removed diff --git a/features/admin/emails.feature b/features/admin/emails.feature new file mode 100644 index 00000000000..cf22e36d7b5 --- /dev/null +++ b/features/admin/emails.feature @@ -0,0 +1,17 @@ +Feature: Admin email + Background: + Given I sign in as an admin + And there are groups with projects + + @javascript + Scenario: Create a new email notification + Given I visit admin email page + When I submit form with email notification info + Then I should see a notification email is begin sent + And admin emails are being sent + + Scenario: Create a new email notification + Given I visit unsubscribe from admin notification page + When I click unsubscribe + Then I get redirected to the sign in path + And unsubscribed email is sent diff --git a/features/admin/git_hooks.feature b/features/admin/git_hooks.feature new file mode 100644 index 00000000000..ea358a5ea57 --- /dev/null +++ b/features/admin/git_hooks.feature @@ -0,0 +1,9 @@ +@admin +Feature: Admin git hooks sample + Background: + Given I sign in as an admin + And I visit git hooks page + + Scenario: I can create git hook sample + When I fill in a form and submit + Then I see my git hook saved
\ No newline at end of file diff --git a/features/admin/groups.feature b/features/admin/groups.feature index 973918086a3..c362775a080 100644 --- a/features/admin/groups.feature +++ b/features/admin/groups.feature @@ -21,6 +21,11 @@ Feature: Admin Groups When I select user "John Doe" from user list as "Reporter" Then I should see "John Doe" in team list in every project as "Reporter" + Scenario: Shared projects + Given group has shared projects + When I visit group page + Then I should see project shared with group + @javascript Scenario: Remove user from group Given we have user "John Doe" in group diff --git a/features/admin/license.feature b/features/admin/license.feature new file mode 100644 index 00000000000..d670473522f --- /dev/null +++ b/features/admin/license.feature @@ -0,0 +1,44 @@ +@admin +Feature: Admin license + Background: + Given I sign in as an admin + + Scenario: Viewing current license + Given there is a license + And I visit admin license page + Then I should see to whom the license is licensed + + Scenario: Viewing license when there is none + Given I visit admin license page + Then I should see a warning telling me there is no license + And I should be redirected to the license upload page + + Scenario: Viewing expired license + Given there is a license + And the current license is expired + And I visit admin license page + Then I should see a warning telling me the license has expired + + Scenario: Viewing license that blocks changes + Given there is a license + And the current license is expired + And the current license blocks changes + And I visit admin license page + Then I should see a warning telling me code pushes have been disabled + + Scenario: Viewing license history + Given there is a license + And there are multiple licenses + And I visit admin license page + Then I should see to whom the licenses were licensed + + Scenario: Uploading valid license + Given I visit admin upload license page + And I upload a valid license + Then I should see a notice telling me the license was uploaded + And I should see to whom the license is licensed + + Scenario: Uploading invalid license + Given I visit admin upload license page + Then I upload an invalid license + Then I should see a warning telling me it's invalid diff --git a/features/admin/settings.feature b/features/admin/settings.feature index e38eea2cfed..5a5d2afeeb0 100644 --- a/features/admin/settings.feature +++ b/features/admin/settings.feature @@ -8,6 +8,14 @@ Feature: Admin Settings When I modify settings and save form Then I should see application settings saved + Scenario: Help text + When I set the help text + Then I should see the help text + And I go to help page + Then I should see the help text + And I logout + Then I should see the help text + Scenario: Change Slack Service Template settings When I click on "Service Templates" And I click on "Slack" service diff --git a/features/group_active_tab.feature b/features/group_active_tab.feature new file mode 100644 index 00000000000..5355782fd2f --- /dev/null +++ b/features/group_active_tab.feature @@ -0,0 +1,18 @@ +Feature: Group Active Tab + Background: + Given I sign in as "John Doe" + And "John Doe" is owner of group "Owned" + When I visit group "Owned" settings page + + Scenario: On Audit events + When I go to "Audit Events" + Then the active sub nav should be Audit Events + And no other sub navs should be active + And the active main tab should be Settings + + Scenario: On Web Hooks + When I go to "Web Hooks" + Then the active sub nav should be Web Hooks + And no other sub navs should be active + And the active main tab should be Settings + diff --git a/features/group_hooks.feature b/features/group_hooks.feature new file mode 100644 index 00000000000..19658047ecd --- /dev/null +++ b/features/group_hooks.feature @@ -0,0 +1,37 @@ +Feature: Group Hooks + Background: + Given I sign in as a user + And I own group "Sourcing" + + Scenario: I should see hook list + Given I own project "Shop" in group "Sourcing" + And group has hook + When I visit group hooks page + Then I should see group hook + + Scenario: I add new hook + GivenI own project "Shop" in group "Sourcing" + And I visit group hooks page + When I submit new hook + Then I should see newly created hook + + Scenario: I test hook + Given I own project "Shop" in group "Sourcing" + And group has hook + And I visit group hooks page + When I click test hook button + Then hook should be triggered + + Scenario: I test a hook on empty project + Given I own empty project "Empty Shop" in group "Sourcing" + And group has hook + And I visit group hooks page + When I click test hook button + Then I should see hook error message + + Scenario: I test a hook on down URL + Given I own project "Shop" in group "Sourcing" + And group has hook + And I visit group hooks page + When I click test hook button with invalid URL + Then I should see hook service down error message diff --git a/features/groups.feature b/features/groups.feature index db37fa3b375..de2e1c49688 100644 --- a/features/groups.feature +++ b/features/groups.feature @@ -51,6 +51,13 @@ Feature: Groups Then I should not see group "Owned" avatar And I should not see the "Remove avatar" button + Scenario: Add new LDAP synchronization + Given LDAP enabled + When I visit Group "Owned" LDAP settings page + And I add a new LDAP synchronization + Then I see a new LDAP synchronization listed + And LDAP disabled + @javascript Scenario: Add user to group Given gitlab user "Mike" @@ -153,7 +160,18 @@ Feature: Groups Then I should see group milestone with descriptions and expiry date And I should see group milestone with all issues and MRs assigned to that milestone - # Group projects in settings + @javascript + Scenario: I should see audit events + Given User "Mary Jane" exists + When I visit group "Owned" members page + And I select user "Mary Jane" from list with role "Reporter" + And I change the role to "Developer" + And I click on the "Remove User From Group" button for "Mary Jane" + When I visit group "Owned" settings page + And I go to "Audit Events" + Then I should see the audit event listed + + # Group projects in settings Scenario: I should see all projects in the project list in settings Given Group "Owned" has archived project When I visit group "Owned" projects page diff --git a/features/groups_management.feature b/features/groups_management.feature new file mode 100644 index 00000000000..35f471fac67 --- /dev/null +++ b/features/groups_management.feature @@ -0,0 +1,22 @@ +Feature: Groups Management + Background: + Given "Pete Peters" is owner of group "Sourcing" + And "Open" is in group "Sourcing" + And "Mary Jane" has master access for project "Open" + + Scenario: Project master can add members before lock + Given I sign in as "Mary Jane" + And I go to "Open" project members page + Then I can control user membership + When Group membership lock is enabled + And I reload "Open" project members page + Then I cannot control user membership from project page + And I logout + + Scenario: Group owner lock membership controls + Given I sign in as "Pete Peters" + And I go to group settings page + And I enable membership lock + And I go to project settings + Then I cannot control user membership from project page + And I logout diff --git a/features/project/audit_event.feature b/features/project/audit_event.feature new file mode 100644 index 00000000000..c38ede795fe --- /dev/null +++ b/features/project/audit_event.feature @@ -0,0 +1,23 @@ +Feature: Audit Event + Background: + Given I sign in as a user + And I own project "Shop" + + Scenario: I add new deploy key + Given I created new depoloy key + When I visit audit event page + Then I see deploy key event + When I remove deploy key + And I visit audit event page + Then I see remove deploy key event + + @javascript + Scenario: I should see audit events + And gitlab user "Pete" + And "Pete" is "Shop" developer + When I visit project "Shop" page + And I go to "Members" + And I change "Pete" access level to master + And I visit project "Shop" settings page + And I go to "Audit Events" + Then I should see the audit event listed diff --git a/features/project/ff_merge_requests.feature b/features/project/ff_merge_requests.feature new file mode 100644 index 00000000000..7019330aabe --- /dev/null +++ b/features/project/ff_merge_requests.feature @@ -0,0 +1,25 @@ +Feature: Project Ff Merge Requests + Background: + Given I sign in as a user + And I own project "Shop" + And project "Shop" have "Bug NS-05" open merge request with diffs inside + And ff merge enabled + And merge request "Bug NS-05" is mergeable + + Scenario: I do ff-only merge + Given merge request "Bug NS-05" is rebased + When I visit merge request page "Bug NS-05" + Then I should see ff-only merge button + + @javascript + Scenario: I do rebase before ff-only merge + Given rebase before merge enabled + When I visit merge request page "Bug NS-05" + Then I should see rebase button + When I press rebase button + Then I should see rebase in progress message + + Scenario: I should do rebase before ff-only merge + When I visit merge request page "Bug NS-05" + Then I should not see rebase button + And I should see rebase message diff --git a/features/project/git_hooks.feature b/features/project/git_hooks.feature new file mode 100644 index 00000000000..b340b4a6c3b --- /dev/null +++ b/features/project/git_hooks.feature @@ -0,0 +1,8 @@ +Feature: Git Hooks + Background: + Given I sign in as a user + And I own project "Shop" + + Scenario: I should see git hook form + When I visit project git hooks page + Then I should see git hook form
\ No newline at end of file diff --git a/features/project/group_links.feature b/features/project/group_links.feature new file mode 100644 index 00000000000..2657c4487ad --- /dev/null +++ b/features/project/group_links.feature @@ -0,0 +1,16 @@ +Feature: Project Group Links + Background: + Given I sign in as a user + And I own project "Shop" + And project "Shop" is shared with group "Ops" + And project "Shop" is not shared with group "Market" + And I visit project group links page + + Scenario: I should see list of groups + Then I should see project already shared with group "Ops" + Then I should see project is not shared with group "Market" + + @javascript + Scenario: I share project with group + When I select group "Market" for share + Then I should see project is shared with group "Market" diff --git a/features/project/issues/issues.feature b/features/project/issues/issues.feature index 28cc43ef710..4e13c9b0dd8 100644 --- a/features/project/issues/issues.feature +++ b/features/project/issues/issues.feature @@ -186,7 +186,6 @@ Feature: Project Issues Then I should see that I am unsubscribed Scenario: I submit new unassigned issue as guest - Given I logout Given public project "Community" When I visit project "Community" page And I visit project "Community" issues page diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature index 6cd081c868e..3981d26d239 100644 --- a/features/project/merge_requests.feature +++ b/features/project/merge_requests.feature @@ -165,6 +165,11 @@ Feature: Project Merge Requests Then I should see a comment like "Line is wrong" in the third file And I should still see a comment like "Line is correct" in the second file + Scenario: I submit new unassigned merge request with template description + Given I click link "New Merge Request" + And I select "fix" as source + Then I should see description field pre-filled + @javascript Scenario: I unfold diff Given project "Shop" have "Bug NS-05" open merge request with diffs inside @@ -242,3 +247,49 @@ Feature: Project Merge Requests When I click the "Target branch" dropdown And I select a new target branch Then I should see new target branch changes + + Scenario: I approve merge request + Given merge request 'Bug NS-04' must be approved + And I click link "Bug NS-04" + And I should not see merge button + When I click link "Approve" + Then I should see approved merge request "Bug NS-04" + + Scenario: Reporter can approve merge request + Given I am a "Shop" reporter + And I visit project "Shop" merge requests page + And merge request 'Bug NS-04' must be approved + And I click link "Bug NS-04" + And I should not see merge button + When I click link "Approve" + Then I should see message that merge request can be merged + + Scenario: I approve merge request if I am an approver + Given merge request 'Bug NS-04' must be approved by current user + And I click link "Bug NS-04" + And I should not see merge button + And I should see message that MR require an approval from me + When I click link "Approve" + Then I should see approved merge request "Bug NS-04" + + Scenario: I can not approve merge request if I am not an approver + Given merge request 'Bug NS-04' must be approved by some user + And I click link "Bug NS-04" + And I should not see merge button + When I should not see Approve button + And I should see message that MR require an approval + + Scenario: I see suggested approvers on new merge request form + Given project settings contain list of approvers + When I click link "New Merge Request" + And I select "fix" as source + Then I see suggested approver + + @javascript + Scenario: I see auto-suggested approvers on new merge request form + Given project settings contain list of approvers + And there is one auto-suggested approver + When I click link "New Merge Request" + And I select "fix" as source + Then I see auto-suggested approver + And I can add it to approver list diff --git a/features/project/project.feature b/features/project/project.feature index 1a53945eb04..d580c10fd05 100644 --- a/features/project/project.feature +++ b/features/project/project.feature @@ -57,6 +57,13 @@ Feature: Project And change project path settings Then I should see project with new path settings + Scenario: I visit edit project and fill in merge request template + When I visit edit project "Shop" page + Then I should see project settings + And I fill in merge request template + And I save project + Then I should see project with merge request template saved + Scenario: I should change project default branch When I visit edit project "Shop" page And change project default branch diff --git a/features/project/service.feature b/features/project/service.feature index 5014b52b9f6..75e5d1bd6d3 100644 --- a/features/project/service.feature +++ b/features/project/service.feature @@ -61,6 +61,12 @@ Feature: Project Services And I fill email on push settings Then I should see email on push service settings saved + Scenario: Activate JIRA service + When I visit project "Shop" services page + And I click jira service link + And I fill jira settings + Then I should see jira service settings saved + Scenario: Activate Irker (IRC Gateway) service When I visit project "Shop" services page And I click Irker service link diff --git a/features/project/team_management.feature b/features/project/team_management.feature index 09a7df59df6..e5de4d819f0 100644 --- a/features/project/team_management.feature +++ b/features/project/team_management.feature @@ -41,3 +41,8 @@ Feature: Project Team Management And I click link "Import team from another project" And I submit "Website" project for import team Then I should see "Mike" in team list as "Reporter" + + Scenario: See all members of projects shared group + Given I share project with group "OpenSource" + And I visit project "Shop" team page + Then I should see "Opensource" group user listing diff --git a/features/steps/admin/appearance.rb b/features/steps/admin/appearance.rb new file mode 100644 index 00000000000..85eefca8e93 --- /dev/null +++ b/features/steps/admin/appearance.rb @@ -0,0 +1,72 @@ +class Spinach::Features::AdminAppearance < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + + step 'submit form with new appearance' do + fill_in 'appearance_title', with: 'MyCompany' + fill_in 'appearance_description', with: 'dev server' + click_button 'Save' + end + + step 'I should be redirected to admin appearance page' do + expect(current_path).to eq admin_appearances_path + expect(page).to have_content 'Appearance settings' + end + + step 'I should see newly created appearance' do + expect(page).to have_field('appearance_title', with: 'MyCompany') + expect(page).to have_field('appearance_description', with: 'dev server') + expect(page).to have_content 'Last edit' + end + + step 'I click preview button' do + click_link "Preview" + end + + step 'application has custom appearance' do + create(:appearance) + end + + step 'I should see a customized appearance' do + expect(page).to have_content appearance.title + expect(page).to have_content appearance.description + end + + step 'I attach a logo' do + attach_file(:appearance_logo, Rails.root.join('spec', 'fixtures', 'dk.png')) + click_button 'Save' + end + + step 'I attach header logos' do + attach_file(:appearance_light_logo, Rails.root.join('spec', 'fixtures', 'dk.png')) + click_button 'Save' + end + + step 'I should see a logo' do + expect(page).to have_xpath('//img[@src="/uploads/appearance/logo/1/dk.png"]') + end + + step 'I should see header logos' do + expect(page).to have_xpath('//img[@src="/uploads/appearance/light_logo/1/dk.png"]') + end + + step 'I remove the logo' do + click_link 'Remove logo' + end + + step 'I remove the header logos' do + click_link 'Remove header logo' + end + + step 'I should see logo removed' do + expect(page).not_to have_xpath('//img[@src="/uploads/appearance/logo/1/gitlab_logo.png"]') + end + + step 'I should see header logos removed' do + expect(page).not_to have_xpath('//img[@src="/uploads/appearance/light_logo/1/header_logo_light.png"]') + end + + def appearance + Appearance.last + end +end diff --git a/features/steps/admin/email.rb b/features/steps/admin/email.rb new file mode 100644 index 00000000000..4c9c70c798e --- /dev/null +++ b/features/steps/admin/email.rb @@ -0,0 +1,60 @@ +class Spinach::Features::AdminEmail < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + include SharedAdmin + + step 'I submit form with email notification info' do + ActionMailer::Base.deliveries = [] + @email_text = "Your project has been moved." + @selected_group = Group.last + # ensure there are ppl to be emailed + 2.times do + @selected_group.add_user(create(:user), Gitlab::Access::DEVELOPER) + end + + page.within('form#new-admin-email') do + fill_in :subject, with: 'my subject' + fill_in :body, with: @email_text + + # Note: Unable to use select2 helper because + # the helper uses select2 method "val" to select the group from the dropdown + # and the method "val" requires "initSelection" to be used in the select2 call + select2_container = first("#s2id_recipients") + select2_container.find(".select2-choice").click + find(:xpath, "//body").find("input.select2-input").set(@selected_group.name) + page.execute_script(%|$("input.select2-input:visible").keyup();|) + find(:xpath, "//body").find(".group-name", text: @selected_group.name).click + + find('.btn-create').click + end + end + + step 'I should see a notification email is begin sent' do + expect(find('.flash-notice')).to have_content 'Email sent' + end + + step 'admin emails are being sent' do + expect(ActionMailer::Base.deliveries.count).to eql @selected_group.users.count + mail = ActionMailer::Base.deliveries.last + expect(mail.text_part.body.decoded).to include @email_text + end + + step 'I visit unsubscribe from admin notification page' do + @user = create(:user) + urlsafe_email = Base64.urlsafe_encode64(@user.email) + visit unsubscribe_path(urlsafe_email) + end + + step 'I click unsubscribe' do + click_button 'Unsubscribe' + end + + step 'I get redirected to the sign in path' do + expect(current_path).to eq root_path + end + + step 'unsubscribed email is sent' do + mail = ActionMailer::Base.deliveries.last + expect(mail.text_part.body.decoded).to include "You have been unsubscribed from receiving GitLab administrator notifications." + end +end diff --git a/features/steps/admin/git_hooks.rb b/features/steps/admin/git_hooks.rb new file mode 100644 index 00000000000..b9d1305d0e5 --- /dev/null +++ b/features/steps/admin/git_hooks.rb @@ -0,0 +1,20 @@ +require 'webmock' + +class Spinach::Features::AdminGitHooksSample < Spinach::FeatureSteps + include SharedAuthentication + include SharedProject + include SharedPaths + include RSpec::Matchers + include RSpec::Mocks::ExampleMethods + include WebMock::API + + step 'I fill in a form and submit' do + fill_in "Commit message", with: "my_string" + click_button "Save Git hooks" + end + + step 'I see my git hook saved' do + visit admin_git_hooks_path + expect(page).to have_selector("input[value='my_string']") + end +end diff --git a/features/steps/admin/groups.rb b/features/steps/admin/groups.rb index d27634858a2..6475f22ab90 100644 --- a/features/steps/admin/groups.rb +++ b/features/steps/admin/groups.rb @@ -72,6 +72,21 @@ class Spinach::Features::AdminGroups < Spinach::FeatureSteps end end + step 'group has shared projects' do + share_link = shared_project.project_group_links.new(group_access: Gitlab::Access::MASTER) + share_link.group_id = current_group.id + share_link.save! + end + + step 'I visit group page' do + visit admin_group_path(current_group) + end + + step 'I should see project shared with group' do + expect(page).to have_content(shared_project.name_with_namespace) + expect(page).to have_content "Projects shared with" + end + step 'we have user "John Doe" in group' do current_group.add_reporter(user_john) end @@ -94,6 +109,10 @@ class Spinach::Features::AdminGroups < Spinach::FeatureSteps @group ||= Group.first end + def shared_project + @shared_project ||= create(:empty_project) + end + def user_john @user_john ||= User.find_by(name: "John Doe") end diff --git a/features/steps/admin/license.rb b/features/steps/admin/license.rb new file mode 100644 index 00000000000..f95c6dd892c --- /dev/null +++ b/features/steps/admin/license.rb @@ -0,0 +1,86 @@ +class Spinach::Features::AdminLicense < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + + step 'I should see to whom the license is licensed' do + expect(page).to have_content(license.licensee.values.first) + end + + step 'there is a license' do + create(:license) + end + + step 'I should see a warning telling me there is no license' do + expect(page).to have_content "No GitLab Enterprise Edition license has been provided yet." + end + + step 'I should be redirected to the license upload page' do + expect(current_path).to eq(new_admin_license_path) + end + + step 'the current license is expired' do + build(:license, data: build(:gitlab_license, expires_at: Date.yesterday).export).save(validate: false) + end + + step 'I should see a warning telling me the license has expired' do + expect(page).to have_content "The GitLab Enterprise Edition license expired" + end + + step 'the current license blocks changes' do + build(:license, data: build(:gitlab_license, expires_at: Date.yesterday, block_changes_at: Date.today).export).save(validate: false) + end + + step 'I should see a warning telling me code pushes have been disabled' do + expect(page).to have_content "Pushing code and creation of issues and merge requests has been disabled." + end + + step 'there are multiple licenses' do + create(:license) + create(:license) + end + + step 'I should see to whom the licenses were licensed' do + license_history = page.find("#license_history") + + License.previous.each do |license| + expect(license_history).to have_content(license.licensee.values.first) + end + end + + step 'I visit admin upload license page' do + visit new_admin_license_path + end + + step 'I upload a valid license' do + path = Rails.root.join("tmp/valid_license.gitlab-license") + + license = build(:gitlab_license) + File.write(path, license.export) + + attach_file 'license_data_file', path + click_button "Upload license" + end + + step 'I should see a notice telling me the license was uploaded' do + expect(page).to have_content "The license was successfully uploaded and is now active." + end + + step 'I upload an invalid license' do + path = Rails.root.join("tmp/invalid_license.gitlab-license") + + license = build(:gitlab_license, expires_at: Date.yesterday) + File.write(path, license.export) + + attach_file 'license_data_file', path + click_button "Upload license" + end + + step "I should see a warning telling me it's invalid" do + expect(page).to have_content "This license has already expired." + end + + def license + License.reset_current + License.current + end +end diff --git a/features/steps/admin/settings.rb b/features/steps/admin/settings.rb index 6acbf46eb20..c2094263199 100644 --- a/features/steps/admin/settings.rb +++ b/features/steps/admin/settings.rb @@ -17,6 +17,19 @@ class Spinach::Features::AdminSettings < Spinach::FeatureSteps expect(page).to have_content "Application settings saved successfully" end + step 'I set the help text' do + fill_in 'Help text', with: help_text + click_button 'Save' + end + + step 'I should see the help text' do + expect(page).to have_content help_text + end + + step 'I go to help page' do + visit '/help' + end + step 'I click on "Service Templates"' do click_link 'Service Templates' end @@ -56,4 +69,8 @@ class Spinach::Features::AdminSettings < Spinach::FeatureSteps expect(find_field('Username').value).to eq 'test_user' expect(find_field('Channel').value).to eq '#test_channel' end + + def help_text + 'For help related to GitLab contact Marc Smith at marc@smith.example or find him in office 42.' + end end diff --git a/features/steps/admin/users.rb b/features/steps/admin/users.rb index 4bc290b6bdf..8fb8a86d58b 100644 --- a/features/steps/admin/users.rb +++ b/features/steps/admin/users.rb @@ -158,7 +158,7 @@ class Spinach::Features::AdminUsers < Spinach::FeatureSteps step 'I should not see twitter details' do expect(page).to have_content 'Pete' - expect(page).to_not have_content 'twitter' + expect(page).not_to have_content 'twitter' end step 'click on ssh keys tab' do diff --git a/features/steps/group_active_tab.rb b/features/steps/group_active_tab.rb new file mode 100644 index 00000000000..eaae67dbcb7 --- /dev/null +++ b/features/steps/group_active_tab.rb @@ -0,0 +1,29 @@ +class Spinach::Features::GroupActiveTab < Spinach::FeatureSteps + include SharedAuthentication + include SharedUser + include SharedGroup + include SharedPaths + include SharedActiveTab + + step 'the active sub nav should be Audit Events' do + ensure_active_sub_nav('Audit Events') + end + + step 'the active main tab should be Settings' do + page.within '.nav-sidebar' do + expect(page).to have_content('Go to group') + end + end + + step 'the active sub nav should be Web Hooks' do + ensure_active_sub_nav('Web Hooks') + end + + step 'I go to "Audit Events"' do + click_link 'Audit Events' + end + + step 'I go to "Web Hooks"' do + click_link 'Web Hooks' + end +end diff --git a/features/steps/group_hooks.rb b/features/steps/group_hooks.rb new file mode 100644 index 00000000000..0b7d5261676 --- /dev/null +++ b/features/steps/group_hooks.rb @@ -0,0 +1,72 @@ +require 'webmock' + +class Spinach::Features::GroupHooks < Spinach::FeatureSteps + include SharedAuthentication + include SharedProject + include SharedPaths + include RSpec::Matchers + include RSpec::Mocks::ExampleMethods + include WebMock::API + + step 'I own group "Sourcing"' do + @group = create :group, name: "Sourcing" + @group.add_owner(current_user) + end + + step 'I own project "Shop" in group "Sourcing"' do + @project = create(:project, + name: 'Shop', group: @group) + end + + step 'I own empty project "Empty Shop" in group "Sourcing"' do + @project = create(:empty_project, + name: 'Shop', group: @group) + end + + step 'group has hook' do + @hook = create(:group_hook, group: @group) + end + + step 'I should see group hook' do + expect(page).to have_content @hook.url + end + + step 'I submit new hook' do + @url = FFaker::Internet.uri("http") + fill_in "hook_url", with: @url + expect { click_button "Add Web Hook" }.to change(GroupHook, :count).by(1) + end + + step 'I should see newly created hook' do + expect(current_path).to eq group_hooks_path(@group) + expect(page).to have_content(@url) + end + + step 'I click test hook button' do + stub_request(:post, @hook.url).to_return(status: 200) + click_link 'Test Hook' + end + + step 'I click test hook button with invalid URL' do + stub_request(:post, @hook.url).to_raise(SocketError) + click_link 'Test Hook' + end + + step 'hook should be triggered' do + expect(current_path).to eq group_hooks_path(@group) + expect(page).to have_selector '.flash-notice', + text: 'Hook successfully executed.' + end + + step 'I should see hook error message' do + expect(page).to have_selector '.flash-alert', + text: 'Hook execution failed. Ensure the group has a project with commits.' + end + + step 'I should see hook service down error message' do + expect(page).to have_selector '.flash-alert', + text: 'Hook execution failed. '\ + 'Ensure hook URL is correct and '\ + 'service is up.' + end +end diff --git a/features/steps/groups.rb b/features/steps/groups.rb index 70388c18fcf..42372f4e323 100644 --- a/features/steps/groups.rb +++ b/features/steps/groups.rb @@ -124,6 +124,33 @@ class Spinach::Features::Groups < Spinach::FeatureSteps expect(projects_with_access).not_to have_content("Mary Jane") end + step 'I change the role to "Developer"' do + user = User.find_by(name: "Mary Jane") + member = Group.find_by(name: "Owned").members.where(user_id: user.id).first + + page.within "#group_member_#{member.id}" do + find(".js-toggle-button").click + page.within "#edit_group_member_#{member.id}" do + select 'Developer', from: 'group_member_access_level' + click_on 'Save' + end + end + end + + step 'I go to "Audit Events"' do + find(:link, 'Audit Events').trigger('click') + end + + step 'I should see the audit event listed' do + page.within('table#audits') do + expect(page).to have_content 'Add user access as reporter' + expect(page).to have_content 'Change access level from reporter to developer' + expect(page).to have_content 'Remove user access' + expect(page).to have_content('John Doe', count: 3) + expect(page).to have_content('Mary Jane', count: 3) + end + end + step 'project from group "Owned" has issues assigned to me' do create :issue, project: project, @@ -139,6 +166,17 @@ class Spinach::Features::Groups < Spinach::FeatureSteps author: current_user end + Then 'I should be redirected to group page' do + expect(current_path).to eq group_path(Group.last) + end + + And 'I change group name' do + page.within '#tab-edit' do + fill_in 'group_name', with: 'new-name' + click_button "Save group" + end + end + step 'I change group "Owned" name to "new-name"' do fill_in 'group_name', with: 'new-name' fill_in 'group_path', with: 'new-name' @@ -330,4 +368,26 @@ class Spinach::Features::Groups < Spinach::FeatureSteps author: current_user, milestone: milestone2_project3 end + + step 'LDAP enabled' do + allow(Gitlab.config.ldap).to receive(:enabled).and_return(true) + end + + step 'LDAP disabled' do + allow(Gitlab.config.ldap).to receive(:enabled).and_return(false) + end + + step 'I add a new LDAP synchronization' do + page.within('form#new_ldap_group_link') do + find('#ldap_group_link_cn', visible: false).set('my-group-cn') + # fill_in('LDAP Group cn', with: 'my-group-cn', visible: false) + select 'Developer', from: "ldap_group_link_group_access" + click_button 'Add synchronization' + end + end + + step 'I see a new LDAP synchronization listed' do + expect(page).not_to have_content('No synchronizations yet') + expect(page).to have_content('As Developer on ldap server') + end end diff --git a/features/steps/groups_management.rb b/features/steps/groups_management.rb new file mode 100644 index 00000000000..3b7a74c0add --- /dev/null +++ b/features/steps/groups_management.rb @@ -0,0 +1,68 @@ +class Spinach::Features::GroupsManagement < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + include SharedGroup + include SharedUser + include Select2Helper + + step '"Open" is in group "Sourcing"' do + @group = Group.find_by(name: "Sourcing") + @project ||= create(:project, name: "Open", namespace: @group) + + end + + step '"Mary Jane" has master access for project "Open"' do + @user = User.find_by(name: "Mary Jane") || create(:user, name: "Mary Jane") + @project = Project.find_by(name: "Open") + @project.team << [@user, :master] + end + + step "Group membership lock is enabled" do + @group = Group.find_by(name: "Sourcing") + @group.update_attributes(membership_lock: true) + end + + step 'I go to "Open" project members page' do + click_link 'Sourcing / Open' + click_link 'Members' + end + + step 'I can control user membership' do + expect(page).to have_button 'Add members' + expect(page).to have_link 'Import members' + expect(page).to have_selector '#project_member_access_level' + end + + step 'I reload "Open" project members page' do + visit root_path + click_link 'Sourcing / Open' + page.within('.nav-sidebar') do + click_link 'Members' + end + end + + step 'I go to group settings page' do + visit dashboard_groups_path + click_link 'Sourcing' + click_link 'Settings' + end + + step 'I enable membership lock' do + check 'group_membership_lock' + click_button 'Save group' + end + + step 'I go to project settings' do + @project = Project.find_by(name: "Open") + click_link 'Projects' + + link = "/#{@project.path_with_namespace}/project_members" + find(:xpath, "//a[@href=\"#{link}\"]").click + end + + step 'I cannot control user membership from project page' do + expect(page).not_to have_button 'Add members' + expect(page).not_to have_link 'Import members' + expect(page).to have_selector '#project_member_access_level' + end +end diff --git a/features/steps/project/audit_event.rb b/features/steps/project/audit_event.rb new file mode 100644 index 00000000000..9c4cf7405b3 --- /dev/null +++ b/features/steps/project/audit_event.rb @@ -0,0 +1,70 @@ +class Spinach::Features::AuditEvent < Spinach::FeatureSteps + include SharedAuthentication + include SharedProject + include SharedPaths + + step 'I created new depoloy key' do + visit new_namespace_project_deploy_key_path(@project.namespace, @project) + + fill_in "deploy_key_title", with: "laptop" + fill_in "deploy_key_key", with: "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAzrEJUIR6Y03TCE9rIJ+GqTBvgb8t1jI9h5UBzCLuK4VawOmkLornPqLDrGbm6tcwM/wBrrLvVOqi2HwmkKEIecVO0a64A4rIYScVsXIniHRS6w5twyn1MD3sIbN+socBDcaldECQa2u1dI3tnNVcs8wi77fiRe7RSxePsJceGoheRQgC8AZ510UdIlO+9rjIHUdVN7LLyz512auAfYsgx1OfablkQ/XJcdEwDNgi9imI6nAXhmoKUm1IPLT2yKajTIC64AjLOnE0YyCh6+7RFMpiMyu1qiOCpdjYwTgBRiciNRZCH8xIedyCoAmiUgkUT40XYHwLuwiPJICpkAzp7Q== user@laptop" + + click_button "Create" + end + + step 'I remove deploy key' do + visit namespace_project_deploy_keys_path(@project.namespace, @project) + click_link "Remove" + end + + step 'I see remove deploy key event' do + expect(page).to have_content("Remove deploy key") + end + + step 'I see deploy key event' do + expect(page).to have_content("Add deploy key") + end + + step 'I should see the audit event listed' do + page.within('table#audits') do + expect(page).to have_content "Change access level from developer to master" + expect(page).to have_content(project.owner.name) + expect(page).to have_content('Pete') + end + end + + step 'gitlab user "Pete"' do + create(:user, name: "Pete") + end + + step '"Pete" is "Shop" developer' do + user = User.find_by(name: "Pete") + project = Project.find_by(name: "Shop") + project.team << [user, :developer] + end + + step 'I go to "Members"' do + find(:link, 'Members').trigger('click') + end + + step 'I visit project "Shop" settings page' do + find(:link, 'Settings').trigger('click') + end + + step 'I change "Pete" access level to master' do + user = User.find_by(name: "Pete") + project_member = @project.project_members.find_by(user_id: user) + + page.within "#project_member_#{project_member.id}" do + click_button "Edit access level" + select "Master", from: "project_member_access_level" + click_button "Save" + end + + sleep 0.05 + end + + step 'I go to "Audit Events"' do + find(:link, 'Audit Events').trigger('click') + end +end diff --git a/features/steps/project/ff_merge_requests.rb b/features/steps/project/ff_merge_requests.rb new file mode 100644 index 00000000000..c65d457d23f --- /dev/null +++ b/features/steps/project/ff_merge_requests.rb @@ -0,0 +1,87 @@ +class Spinach::Features::ProjectFfMergeRequests < Spinach::FeatureSteps + include SharedAuthentication + include SharedIssuable + include SharedProject + include SharedNote + include SharedPaths + include SharedMarkdown + include SharedDiffNote + include SharedUser + + step 'project "Shop" have "Bug NS-05" open merge request with diffs inside' do + create(:merge_request_with_diffs, + title: "Bug NS-05", + source_project: project, + target_project: project, + author: project.users.first) + end + + step 'merge request is mergeable' do + expect(page).to have_button 'Accept Merge Request' + end + + step 'I should see ff-only merge button' do + expect(page).to have_content "Fast-forward merge without creating merge commit" + expect(page).to have_button 'Accept Merge Request' + end + + step 'merge request "Bug NS-05" is mergeable' do + merge_request.mark_as_mergeable + end + + step 'I accept this merge request' do + page.within '.mr-state-widget' do + click_button "Accept Merge Request" + end + end + + step 'I should see merged request' do + page.within '.issue-box' do + expect(page).to have_content "Merged" + end + end + + step 'ff merge enabled' do + project = merge_request.target_project + project.merge_requests_ff_only_enabled = true + project.save! + end + + step 'I should not see rebase button' do + expect(page).to_not have_button "Rebase" + end + + step 'I should see rebase button' do + expect(page).to have_button "Rebase" + end + + step 'I should see rebase message' do + expect(page).to have_content "Fast-forward merge is not possible. Branch must be rebased first" + end + + step 'merge request "Bug NS-05" is rebased' do + merge_request.source_branch = 'flatten-dir' + merge_request.target_branch = 'improve/awesome' + merge_request.reload_code + merge_request.save! + end + + step 'rebase before merge enabled' do + project = merge_request.target_project + project.merge_requests_rebase_enabled = true + project.save! + end + + step 'I press rebase button' do + allow(RebaseWorker).to receive(:perform_async){ true } + click_button "Rebase" + end + + step "I should see rebase in progress message" do + expect(page).to have_content("Rebase in progress") + end + + def merge_request + @merge_request ||= MergeRequest.find_by!(title: "Bug NS-05") + end +end diff --git a/features/steps/project/git_hooks.rb b/features/steps/project/git_hooks.rb new file mode 100644 index 00000000000..b8ee48934c7 --- /dev/null +++ b/features/steps/project/git_hooks.rb @@ -0,0 +1,19 @@ +require 'webmock' + +class Spinach::Features::GitHooks < Spinach::FeatureSteps + include SharedAuthentication + include SharedProject + include SharedPaths + include RSpec::Matchers + include RSpec::Mocks::ExampleMethods + include WebMock::API + + + step 'I should see git hook form' do + expect(page).to have_selector('input#git_hook_commit_message_regex') + expect(page).to have_content "Commit message" + expect(page).to have_content "Commit author's email" + end + + +end diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index d5f2c4209a1..9db9c685d3e 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -188,24 +188,24 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps step 'I should see a discussion has started on diff' do page.within(".notes .discussion") do - page.should have_content "#{current_user.name} started a discussion" - page.should have_content sample_commit.line_code_path - page.should have_content "Line is wrong" + expect(page).to have_content "#{current_user.name} started a discussion" + expect(page).to have_content sample_commit.line_code_path + expect(page).to have_content "Line is wrong" end end step 'I should see a discussion has started on commit diff' do page.within(".notes .discussion") do - page.should have_content "#{current_user.name} started a discussion on commit" - page.should have_content sample_commit.line_code_path - page.should have_content "Line is wrong" + expect(page).to have_content "#{current_user.name} started a discussion on commit" + expect(page).to have_content sample_commit.line_code_path + expect(page).to have_content "Line is wrong" end end step 'I should see a discussion has started on commit' do page.within(".notes .discussion") do - page.should have_content "#{current_user.name} started a discussion on commit" - page.should have_content "One comment to rule them all" + expect(page).to have_content "#{current_user.name} started a discussion on commit" + expect(page).to have_content "One comment to rule them all" end end @@ -308,6 +308,16 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps end end + step 'I select "fix" as source' do + select "fix", from: "merge_request_source_branch" + select "feature", from: "merge_request_target_branch" + click_button "Compare branches" + end + + step 'I should see description field pre-filled' do + expect(find_field('merge_request_description').value).to eq 'This merge request should contain the following.' + end + step 'I unfold diff' do expect(page).to have_css('.js-unfold') @@ -333,6 +343,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps end step 'I click the "Target branch" dropdown' do + sleep 0.5 first('.target_branch').click end @@ -346,6 +357,108 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps expect(page).to have_content 'Target branch changed from master to feature' end + step 'project settings contain list of approvers' do + project.update(approvals_before_merge: 1) + project.approvers.create(user_id: current_user.id) + end + + step 'there is one auto-suggested approver' do + @user = create :user + allow_any_instance_of(Gitlab::AuthorityAnalyzer).to receive(:calculate).and_return([@user]) + end + + step 'I see suggested approver' do + page.within 'ul .project-approvers' do + expect(page).to have_content(current_user.name) + end + end + + step 'I see auto-suggested approver' do + page.within '.suggested-approvers' do + expect(page).to have_content(@user.name) + end + end + + step 'I can add it to approver list' do + click_link @user.name + + page.within 'ul.approver-list' do + expect(page).to have_content(@user.name) + end + + click_button "Submit new merge request" + click_link "Edit" + + page.within 'ul.approver-list' do + expect(page).to have_content(@user.name) + end + end + + step 'merge request \'Bug NS-04\' must be approved' do + merge_request = MergeRequest.find_by!(title: "Bug NS-04") + project = merge_request.target_project + project.approvals_before_merge = 1 + project.save! + end + + step 'merge request \'Bug NS-04\' must be approved by current user' do + merge_request = MergeRequest.find_by!(title: "Bug NS-04") + project = merge_request.target_project + project.approvals_before_merge = 1 + merge_request.approvers.create(user_id: current_user.id) + project.save! + end + + step 'merge request \'Bug NS-04\' must be approved by some user' do + merge_request = MergeRequest.find_by!(title: "Bug NS-04") + project = merge_request.target_project + project.approvals_before_merge = 1 + merge_request.approvers.create(user_id: create(:user).id) + project.save! + end + + step 'I click link "Approve"' do + page.within '.mr-state-widget' do + click_button 'Approve Merge Request' + end + end + + step 'I should not see merge button' do + page.within '.mr-state-widget' do + expect(page).not_to have_button("Accept Merge Request") + end + end + + step 'I should not see Approve button' do + page.within '.mr-state-widget' do + expect(page).not_to have_button("Approve") + end + end + + step 'I should see approved merge request "Bug NS-04"' do + page.within '.mr-state-widget' do + expect(page).to have_button("Accept Merge Request") + end + end + + step 'I should see message that merge request can be merged' do + page.within '.mr-state-widget' do + expect(page).to have_content("Ready to be merged automatically") + end + end + + step 'I should see message that MR require an approval from me' do + page.within '.mr-state-widget' do + expect(page).to have_content("Requires one more approval (from #{current_user.name})") + end + end + + step 'I should see message that MR require an approval' do + page.within '.mr-state-widget' do + expect(page).to have_content("Requires one more approval") + end + end + step 'I click on "Email Patches"' do click_link "Email Patches" end @@ -358,6 +471,15 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps expect(page).to have_content('diff --git') end + step 'I am a "Shop" reporter' do + user = create(:user, name: "Mike") + project = Project.find_by(name: "Shop") + project.team << [user, :reporter] + + logout + login_with user + end + step '"Bug NS-05" has CI status' do project = merge_request.source_project project.enable_ci diff --git a/features/steps/project/network_graph.rb b/features/steps/project/network_graph.rb index 7a83d32a240..5bd6d4b9f48 100644 --- a/features/steps/project/network_graph.rb +++ b/features/steps/project/network_graph.rb @@ -9,7 +9,7 @@ class Spinach::Features::ProjectNetworkGraph < Spinach::FeatureSteps When 'I visit project "Shop" network page' do # Stub Graph max_size to speed up test (10 commits vs. 650) - Network::Graph.stub(max_count: 10) + allow(Network::Graph).to receive(:max_count).and_return(10) @project = Project.find_by(name: "Shop") visit namespace_project_network_path(@project.namespace, @project, "master") diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb index 9ca7c8ebbc7..893968fa313 100644 --- a/features/steps/project/project.rb +++ b/features/steps/project/project.rb @@ -2,6 +2,7 @@ class Spinach::Features::Project < Spinach::FeatureSteps include SharedAuthentication include SharedProject include SharedPaths + include Select2Helper step 'change project settings' do fill_in 'project_name_edit', with: 'NewName' @@ -14,6 +15,8 @@ class Spinach::Features::Project < Spinach::FeatureSteps step 'I should see project with new settings' do expect(find_field('project_name').value).to eq 'NewName' + expect(find('#project_issues_enabled')).not_to be_checked + expect(find('#project_merge_requests_enabled')).to be_checked end step 'change project path settings' do @@ -66,6 +69,28 @@ class Spinach::Features::Project < Spinach::FeatureSteps expect(page).not_to have_link('Remove avatar') end + step 'I fill in merge request template' do + fill_in 'project_merge_requests_template', with: "This merge request should contain the following." + end + + step 'I should see project with merge request template saved' do + expect(find_field('project_merge_requests_template').value).to eq 'This merge request should contain the following.' + end + + step 'I fill in issues template' do + fill_in 'project_issues_template', with: "This issue should contain the following." + end + + step 'I should see project with issues template saved' do + expect(find_field('project_issues_template').value).to eq 'This issue should contain the following.' + end + + step 'I should see project "Shop" README link' do + page.within '.project-side' do + expect(page).to have_content "README.md" + end + end + step 'I should see project "Shop" version' do page.within '.project-side' do expect(page).to have_content '6.7.0.pre' @@ -97,6 +122,14 @@ class Spinach::Features::Project < Spinach::FeatureSteps end end + step 'I visit project "Shop" settings page' do + click_link 'Settings' + end + + step 'I go to "Members"' do + click_link 'Members' + end + step 'I add project tags' do fill_in 'Tags', with: 'tag1, tag2' end diff --git a/features/steps/project/project_group_links.rb b/features/steps/project/project_group_links.rb new file mode 100644 index 00000000000..739a85e5fa4 --- /dev/null +++ b/features/steps/project/project_group_links.rb @@ -0,0 +1,50 @@ +class Spinach::Features::ProjectGroupLinks < Spinach::FeatureSteps + include SharedAuthentication + include SharedProject + include SharedPaths + include Select2Helper + + step 'I should see project already shared with group "Ops"' do + page.within '.enabled-groups' do + expect(page).to have_content "Ops" + end + end + + step 'I should see project is not shared with group "Market"' do + page.within '.enabled-groups' do + expect(page).not_to have_content "Market" + end + end + + step 'I select group "Market" for share' do + group = Group.find_by(path: 'market') + select2(group.id, from: "#link_group_id") + select "Master", from: 'link_group_access' + click_button "Share" + end + + step 'I should see project is shared with group "Market"' do + page.within '.enabled-groups' do + expect(page).to have_content "Market" + end + end + + step 'project "Shop" is shared with group "Ops"' do + group = create(:group, name: 'Ops') + share_link = project.project_group_links.new(group_access: Gitlab::Access::MASTER) + share_link.group_id = group.id + share_link.save! + end + + step 'project "Shop" is not shared with group "Market"' do + create(:group, name: 'Market', path: 'market') + end + + step 'I visit project group links page' do + visit namespace_project_group_links_path(project.namespace, project) + end + + def project + @project ||= Project.find_by_name "Shop" + end +end diff --git a/features/steps/project/redirects.rb b/features/steps/project/redirects.rb index 1ffd5cb9de5..5b3b231effa 100644 --- a/features/steps/project/redirects.rb +++ b/features/steps/project/redirects.rb @@ -17,7 +17,7 @@ class Spinach::Features::ProjectRedirects < Spinach::FeatureSteps end step 'I should see project "Community" home page' do - Gitlab.config.gitlab.should_receive(:host).and_return("www.example.com") + expect(Gitlab.config.gitlab).to receive(:host).and_return("www.example.com") page.within '.navbar-gitlab .title' do expect(page).to have_content 'Community' end diff --git a/features/steps/project/services.rb b/features/steps/project/services.rb index 1c700df0c63..e2c9c19133a 100644 --- a/features/steps/project/services.rb +++ b/features/steps/project/services.rb @@ -183,6 +183,24 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps expect(find_field('Sound').find('option[selected]').value).to eq 'bike' end + step 'I click jira service link' do + click_link 'JIRA' + end + + step 'I fill jira settings' do + fill_in 'Project url', with: 'http://jira.example' + fill_in 'Username', with: 'gitlab' + fill_in 'Password', with: 'gitlab' + fill_in 'Api url', with: 'http://jira.example/rest/api/2' + click_button 'Save' + end + + step 'I should see jira service settings saved' do + expect(find_field('Project url').value).to eq 'http://jira.example' + expect(find_field('Username').value).to eq 'gitlab' + expect(find_field('Api url').value).to eq 'http://jira.example/rest/api/2' + end + step 'I click Atlassian Bamboo CI service link' do click_link 'Atlassian Bamboo CI' end diff --git a/features/steps/project/team_management.rb b/features/steps/project/team_management.rb index 97d63016458..ac2b0fbac42 100644 --- a/features/steps/project/team_management.rb +++ b/features/steps/project/team_management.rb @@ -127,4 +127,23 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps click_link('Remove user from team') end end + + step 'I share project with group "OpenSource"' do + project = Project.find_by(name: 'Shop') + os_group = create(:group, name: 'OpenSource') + create(:project, group: os_group) + @os_user1 = create(:user) + @os_user2 = create(:user) + os_group.add_owner(@os_user1) + os_group.add_user(@os_user2, Gitlab::Access::DEVELOPER) + share_link = project.project_group_links.new(group_access: Gitlab::Access::MASTER) + share_link.group_id = os_group.id + share_link.save! + end + + step 'I should see "Opensource" group user listing' do + expect(page).to have_content("Shared with OpenSource group, members with Master role (2)") + expect(page).to have_content(@os_user1.name) + expect(page).to have_content(@os_user2.name) + end end diff --git a/features/steps/project/wiki.rb b/features/steps/project/wiki.rb index 02207dbffa6..49ae8a3fba2 100644 --- a/features/steps/project/wiki.rb +++ b/features/steps/project/wiki.rb @@ -95,8 +95,8 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps step 'I click on existing image link' do file = Gollum::File.new(wiki.wiki) - Gollum::Wiki.any_instance.stub(:file).with("image.jpg", "master", true).and_return(file) - Gollum::File.any_instance.stub(:mime_type).and_return("image/jpeg") + allow_any_instance_of(Gollum::Wiki).to receive(:file).with("image.jpg", "master", true).and_return(file) + allow_any_instance_of(Gollum::File).to receive(:mime_type).and_return("image/jpeg") expect(page).to have_link('image', href: "image.jpg") click_on "image" end diff --git a/features/steps/search.rb b/features/steps/search.rb index 79273cbad9a..dc1cb0d5720 100644 --- a/features/steps/search.rb +++ b/features/steps/search.rb @@ -59,7 +59,7 @@ class Spinach::Features::Search < Spinach::FeatureSteps step 'I should see code results for project "Shop"' do page.within('.results') do - page.should have_content 'Update capybara, rspec-rails, poltergeist to recent versions' + expect(page).to have_content 'Update capybara, rspec-rails, poltergeist to recent versions' end end @@ -85,7 +85,7 @@ class Spinach::Features::Search < Spinach::FeatureSteps step 'I should see "Foo" link in the search results' do page.within('.results') do - find(:css, '.search-results').should have_link 'Foo' + expect(find(:css, '.search-results')).to have_link 'Foo' end end @@ -95,7 +95,7 @@ class Spinach::Features::Search < Spinach::FeatureSteps step 'I should see "test_wiki" link in the search results' do page.within('.results') do - find(:css, '.search-results').should have_link 'test_wiki.md' + expect(find(:css, '.search-results')).to have_link 'test_wiki.md' end end diff --git a/features/steps/shared/admin.rb b/features/steps/shared/admin.rb index fbaa408226e..245a42d71f9 100644 --- a/features/steps/shared/admin.rb +++ b/features/steps/shared/admin.rb @@ -8,4 +8,11 @@ module SharedAdmin step 'system has users' do 2.times { create(:user) } end + + And 'there are groups with projects' do + 2.times do + group = create :group + create :project, group: group + end + end end diff --git a/features/steps/shared/authentication.rb b/features/steps/shared/authentication.rb index 735e0ef6108..d08c2fbcd2f 100644 --- a/features/steps/shared/authentication.rb +++ b/features/steps/shared/authentication.rb @@ -20,6 +20,10 @@ module SharedAuthentication login_with(user_exists("Mary Jane")) end + step 'I sign in as "Pete Peters"' do + login_with(user_exists("Pete Peters")) + end + step 'I should be redirected to sign in page' do expect(current_path).to eq new_user_session_path end diff --git a/features/steps/shared/group.rb b/features/steps/shared/group.rb index 83a04576973..5648610bdec 100644 --- a/features/steps/shared/group.rb +++ b/features/steps/shared/group.rb @@ -21,6 +21,10 @@ module SharedGroup is_member_of("Mary Jane", "Guest", Gitlab::Access::GUEST) end + step '"Pete Peters" is owner of group "Sourcing"' do + is_member_of("Pete Peters", "Sourcing", Gitlab::Access::OWNER) + end + step 'I should see group "TestGroup"' do expect(page).to have_content "TestGroup" end diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb index eb978620da6..7cde1bef45c 100644 --- a/features/steps/shared/paths.rb +++ b/features/steps/shared/paths.rb @@ -7,6 +7,10 @@ module SharedPaths visit new_project_path end + step 'I visit login page' do + visit new_user_session_path + end + # ---------------------------------------- # User # ---------------------------------------- @@ -39,6 +43,10 @@ module SharedPaths visit edit_group_path(Group.find_by(name: "Owned")) end + step 'I visit group "Owned" LDAP settings page' do + visit group_ldap_group_links_path(Group.find_by(name:"Owned")) + end + step 'I visit group "Owned" projects page' do visit projects_group_path(Group.find_by(name: "Owned")) end @@ -63,6 +71,10 @@ module SharedPaths visit edit_group_path(Group.find_by(name: "Guest")) end + step 'I visit audit event page' do + visit namespace_project_audit_events_path(@project.namespace, @project) + end + # ---------------------------------------- # Dashboard # ---------------------------------------- @@ -179,10 +191,18 @@ module SharedPaths visit admin_groups_path end + step 'I visit admin appearance page' do + visit admin_appearances_path + end + step 'I visit admin teams page' do visit admin_teams_path end + step 'I visit admin email page' do + visit admin_email_path + end + step 'I visit admin settings page' do visit admin_application_settings_path end @@ -191,6 +211,14 @@ module SharedPaths visit admin_applications_path end + step 'I visit git hooks page' do + visit admin_git_hooks_path + end + + step 'I visit admin license page' do + visit admin_license_path + end + # ---------------------------------------- # Generic Project # ---------------------------------------- @@ -226,7 +254,7 @@ module SharedPaths step "I visit my project's network page" do # Stub Graph max_size to speed up test (10 commits vs. 650) - Network::Graph.stub(max_count: 10) + allow(Network::Graph).to receive(:max_count).and_return(10) visit namespace_project_network_path(@project.namespace, @project, root_ref) end @@ -251,6 +279,14 @@ module SharedPaths visit namespace_project_hooks_path(@project.namespace, @project) end + step 'I visit group hooks page' do + visit group_hooks_path(@group) + end + + step 'I visit project git hooks page' do + visit namespace_project_git_hooks_path(@project.namespace, @project) + end + step 'I visit project deploy keys page' do visit namespace_project_deploy_keys_path(@project.namespace, @project) end diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb index 7021fac5fe4..f6203f9191c 100644 --- a/features/steps/shared/project.rb +++ b/features/steps/shared/project.rb @@ -10,7 +10,7 @@ module SharedProject # Create a specific project called "Shop" step 'I own project "Shop"' do @project = Project.find_by(name: "Shop") - @project ||= create(:project, name: "Shop", namespace: @user.namespace, snippets_enabled: true) + @project ||= create(:project, name: "Shop", namespace: @user.namespace, snippets_enabled: true, issues_template: "This issue should contain the following.", merge_requests_template: "This merge request should contain the following.") @project.team << [@user, :master] end diff --git a/features/steps/user.rb b/features/steps/user.rb index 3230234cb6d..a2627d4f1ff 100644 --- a/features/steps/user.rb +++ b/features/steps/user.rb @@ -8,6 +8,23 @@ class Spinach::Features::User < Spinach::FeatureSteps expect(title).to match(/^\s*John Doe/) end + step 'I visit unsubscribe link' do + email = Base64.urlsafe_encode64("joh@doe.org") + visit "/unsubscribes/#{email}" + end + + step 'I should see unsubscribe text and button' do + expect(page).to have_content "Unsubscribe from Admin notifications Yes, I want to unsubscribe joh@doe.org from any further admin emails." + end + + step 'I press the unsubscribe button' do + click_button("Unsubscribe") + end + + step 'I should be unsubscribed' do + expect(current_path).to eq root_path + end + step '"John Doe" has contributions' do user = User.find_by(name: 'John Doe') project = contributed_project diff --git a/features/support/env.rb b/features/support/env.rb index 62c80b9c948..edd58451dae 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -15,7 +15,7 @@ require 'sidekiq/testing/inline' require_relative 'capybara' require_relative 'db_cleaner' -%w(select2_helper test_env repo_helpers).each do |f| +%w(select2_helper test_env repo_helpers license).each do |f| require Rails.root.join('spec', 'support', f) end @@ -27,6 +27,7 @@ Spinach.hooks.before_run do include RSpec::Mocks::ExampleMethods RSpec::Mocks.setup TestEnv.init(mailer: false) + TestLicense.init # skip pre-receive hook check so we can use # web editor and merge diff --git a/features/user.feature b/features/user.feature index 35eae842e77..c756affdfa5 100644 --- a/features/user.feature +++ b/features/user.feature @@ -63,6 +63,13 @@ Feature: User And I should not see project "Internal" And I should not see project "Community" + Scenario: I unsubscribe from admin notifications + Given I sign in as "John Doe" + When I visit unsubscribe link + Then I should see unsubscribe text and button + And I press the unsubscribe button + Then I should be unsubscribed + @javascript Scenario: "John Doe" contribution profile Given I sign in as a user diff --git a/lib/api/api.rb b/lib/api/api.rb index fe1bf8a4816..4f66dad6aa8 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -43,6 +43,9 @@ module API mount ProjectMembers mount DeployKeys mount ProjectHooks + mount ProjectGitHook + mount Ldap + mount LdapGroupLinks mount Services mount Files mount Commits diff --git a/lib/api/entities.rb b/lib/api/entities.rb index d6aec03d7f5..0cd21c0e5e9 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -48,6 +48,11 @@ module API expose :issues_events, :merge_requests_events, :tag_push_events, :note_events, :enable_ssl_verification end + class ProjectGitHook < Grape::Entity + expose :id, :project_id, :created_at + expose :commit_message_regex, :deny_delete_tag + end + class ForkedFromProject < Grape::Entity expose :id expose :name, :name_with_namespace @@ -76,8 +81,13 @@ module API end end + class LdapGroupLink < Grape::Entity + expose :cn, :group_access, :provider + end + class Group < Grape::Entity - expose :id, :name, :path, :description + expose :id, :name, :path, :ldap_cn, :ldap_access, :description + expose :ldap_group_links, using: Entities::LdapGroupLink, if: lambda{ | group, options | group.ldap_group_links.any? } expose :avatar_url expose :web_url do |group, options| @@ -229,6 +239,14 @@ module API end end + class LdapGroup < Grape::Entity + expose :cn + end + + class ProjectGroupLink < Grape::Entity + expose :id, :project_id, :group_id, :group_access + end + class Namespace < Grape::Entity expose :id, :path, :kind end diff --git a/lib/api/groups.rb b/lib/api/groups.rb index 024aeec2e14..9465afd3eed 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -23,18 +23,30 @@ module API # Create group. Available only for users who can create groups. # # Parameters: - # name (required) - The name of the group - # path (required) - The path of the group + # name (required) - The name of the group + # path (required) - The path of the group + # description (optional) - The details of the group + # membership_lock (optional, boolean) - Prevent adding new members to project membership within this group + # share_with_group_lock (optional, boolean) - Prevent sharing a project with another group within this group # Example Request: # POST /groups post do authorize! :create_group, current_user required_attributes! [:name, :path] - attrs = attributes_for_keys [:name, :path, :description] + attrs = attributes_for_keys [:name, :path, :description, :membership_lock, :share_with_group_lock] @group = Group.new(attrs) if @group.save + # NOTE: add backwards compatibility for single ldap link + ldap_attrs = attributes_for_keys [:ldap_cn, :ldap_access] + if ldap_attrs.present? + @group.ldap_group_links.create({ + cn: ldap_attrs[:ldap_cn], + group_access: ldap_attrs[:ldap_access] + }) + end + @group.add_owner(current_user) present @group, with: Entities::Group else @@ -42,6 +54,29 @@ module API end end + # Update group. Available only for users who can manage this group. + # + # Parameters: + # id (required) - The ID of a group + # name (required) - The name of the group + # path (required) - The path of the group + # description (optional) - The details of the group + # membership_lock (optional, boolean) - Prevent adding new members to project membership within this group + # share_with_group_lock (optional, boolean) - Prevent sharing a project with another group within this group + # Example Request: + # PUT /groups/:id + put ":id" do + attrs = attributes_for_keys [:name, :path, :description, :membership_lock, :share_with_group_lock] + @group = find_group(params[:id]) + authorize! :admin_group, @group + + if @group.update_attributes(attrs) + present @group, with: Entities::Group + else + render_api_error!("Failed to update group #{@group.errors.messages}", 400) + end + end + # Get a single group, with containing projects # # Parameters: diff --git a/lib/api/ldap.rb b/lib/api/ldap.rb new file mode 100644 index 00000000000..d50bfee438b --- /dev/null +++ b/lib/api/ldap.rb @@ -0,0 +1,37 @@ +module API + # groups API + class Ldap < Grape::API + before { authenticate! } + + resource :ldap do + helpers do + def get_group_list(provider, search) + search ||= "" + search = Net::LDAP::Filter.escape(search) + Gitlab::LDAP::Adapter.new(provider).groups("#{search}*", 20) + end + end + + # Get a LDAP groups list. Limit size to 20 of them. + # Filter results by name using search param + # + # Example Request: + # GET /ldap/groups + get 'groups' do + provider = Gitlab::LDAP::Config.servers.first['provider_name'] + @groups = get_group_list(provider, params[:search]) + present @groups, with: Entities::LdapGroup + end + + # Get a LDAP groups list by the requested provider. Lited size to 20 of them. + # Filter results by name using search param + # + # Example Request: + # GET /ldap/ldapmain/groups + get ':provider/groups' do + @groups = get_group_list(params[:provider], params[:search]) + present @groups, with: Entities::LdapGroup + end + end + end +end diff --git a/lib/api/ldap_group_links.rb b/lib/api/ldap_group_links.rb new file mode 100644 index 00000000000..dd62bbffccd --- /dev/null +++ b/lib/api/ldap_group_links.rb @@ -0,0 +1,78 @@ +module API + # LDAP group links API + class LdapGroupLinks < Grape::API + before { authenticate! } + + resource :groups do + + # Add a linked LDAP group to group + # + # Parameters: + # id (required) - The ID of a group + # cn (required) - The CN of a LDAP group + # group_access (required) - Level of permissions for the linked LDAP group + # provider (required) - the LDAP provider for this LDAP group + # + # Example Request: + # POST /groups/:id/ldap_group_links + post ":id/ldap_group_links" do + group = find_group(params[:id]) + authorize! :admin_group, group + required_attributes! [:cn, :group_access, :provider] + unless validate_access_level?(params[:group_access]) + render_api_error!("Wrong group access level", 422) + end + + attrs = attributes_for_keys [:cn, :group_access, :provider] + + ldap_group_link = group.ldap_group_links.new(attrs) + if ldap_group_link.save + present ldap_group_link, with: Entities::LdapGroupLink + else + render_api_error!(ldap_group_link.errors.full_messages.first, 409) + end + end + + # Remove a linked LDAP group from group + # + # Parameters: + # id (required) - The ID of a group + # cn (required) - The CN of a LDAP group + # + # Example Request: + # DELETE /groups/:id/ldap_group_links/:cn + delete ":id/ldap_group_links/:cn" do + group = find_group(params[:id]) + authorize! :admin_group, group + + ldap_group_link = group.ldap_group_links.find_by(cn: params[:cn]) + if ldap_group_link + ldap_group_link.destroy + else + render_api_error!('Linked LDAP group not found', 404) + end + end + + # Remove a linked LDAP group from group for a specific LDAP provider + # + # Parameters: + # id (required) - The ID of a group + # provider (required) - A LDAP provider + # cn (required) - The CN of a LDAP group + # + # Example Request: + # DELETE /groups/:id/ldap_group_links/:provider/:cn + delete ":id/ldap_group_links/:provider/:cn" do + group = find_group(params[:id]) + authorize! :admin_group, group + + ldap_group_link = group.ldap_group_links.find_by(cn: params[:cn], provider: params[:provider]) + if ldap_group_link + ldap_group_link.destroy + else + render_api_error!('Linked LDAP group not found', 404) + end + end + end + end +end diff --git a/lib/api/project_git_hook.rb b/lib/api/project_git_hook.rb new file mode 100644 index 00000000000..5a1523ab9c5 --- /dev/null +++ b/lib/api/project_git_hook.rb @@ -0,0 +1,76 @@ +module API + # Projects git hook API + class ProjectGitHook < Grape::API + before { authenticate! } + before { authorize_admin_project } + + resource :projects do + # Get project git hook + # + # Parameters: + # id (required) - The ID of a project + # Example Request: + # GET /projects/:id/git_hook + get ":id/git_hook" do + @git_hooks = user_project.git_hook + present @git_hooks, with: Entities::ProjectGitHook + end + + # Add git hook to project + # + # Parameters: + # id (required) - The ID of a project + # Example Request: + # POST /projects/:id/git_hook + post ":id/git_hook" do + attrs = attributes_for_keys [ + :commit_message_regex, + :deny_delete_tag + ] + + if user_project.git_hook + error!("Project git hook exists", 422) + else + @git_hook = user_project.create_git_hook(attrs) + present @git_hook, with: Entities::ProjectGitHook + end + end + + # Update an existing project git hook + # + # Parameters: + # id (required) - The ID of a project + # Example Request: + # PUT /projects/:id/git_hook + put ":id/git_hook" do + @git_hook = user_project.git_hook + + attrs = attributes_for_keys [ + :commit_message_regex, + :deny_delete_tag + ] + + if @git_hook && @git_hook.update_attributes(attrs) + present @git_hook, with: Entities::ProjectGitHook + else + not_found! + end + end + + # Deletes project git hook. This is an idempotent function. + # + # Parameters: + # id (required) - The ID of a project + # Example Request: + # DELETE /projects/:id/git_hook + delete ":id/git_hook" do + @git_hook = user_project.git_hook + if @git_hook + @git_hook.destroy + else + not_found! + end + end + end + end +end diff --git a/lib/api/project_members.rb b/lib/api/project_members.rb index c756bb479fc..b88f13887d3 100644 --- a/lib/api/project_members.rb +++ b/lib/api/project_members.rb @@ -45,6 +45,10 @@ module API authorize! :admin_project, user_project required_attributes! [:user_id, :access_level] + if user_project.group && user_project.group.membership_lock + not_allowed! + end + # either the user is already a team member or a new one project_member = user_project.project_member_by_id(params[:user_id]) if project_member.nil? diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 2b4ada6e2eb..3f6b5cae01c 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -257,6 +257,34 @@ module API user_project.forked_project_link.destroy end end + + # Share project with group + # + # Parameters: + # id (required) - The ID of a project + # group_id (required) - The ID of a group + # group_access (required) - Level of permissions for sharing + # + # Example Request: + # POST /projects/:id/share + post ":id/share" do + authorize! :admin_project, user_project + required_attributes! [:group_id, :group_access] + + unless user_project.allowed_to_share_with_group? + return render_api_error!("The project sharing with group is disabled", 400) + end + + link = user_project.project_group_links.new + link.group_id = params[:group_id] + link.group_access = params[:group_access] + if link.save + present link, with: Entities::ProjectGroupLink + else + render_api_error!(link.errors.full_messages.first, 409) + end + end + # search for projects current_user has access to # # Parameters: diff --git a/lib/api/users.rb b/lib/api/users.rb index a98d668e02d..e850a869cae 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -9,8 +9,11 @@ module API # Example Request: # GET /users get do + skip_ldap = params[:skip_ldap].present? && params[:skip_ldap] == 'true' + @users = User.all @users = @users.active if params[:active].present? + @users = @users.non_ldap if skip_ldap @users = @users.search(params[:search]) if params[:search].present? @users = paginate @users diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index 30509528b8b..6520b82e2aa 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -2,6 +2,11 @@ module Gitlab class Auth def find(login, password) user = User.by_login(login) + + if Devise.omniauth_providers.include?(:kerberos) + kerberos_user = Gitlab::Kerberos::Authentication.login(login, password) + return kerberos_user if kerberos_user + end # If no user is found, or it's an LDAP server, try LDAP. # LDAP users are only authenticated via LDAP diff --git a/lib/gitlab/authority_analyzer.rb b/lib/gitlab/authority_analyzer.rb new file mode 100644 index 00000000000..4553ba490c1 --- /dev/null +++ b/lib/gitlab/authority_analyzer.rb @@ -0,0 +1,44 @@ +module Gitlab + class AuthorityAnalyzer + COMMITS_TO_CONSIDER = 5 + + def initialize(merge_request, current_user) + @merge_request = merge_request + @current_user = current_user + @users = Hash.new(0) + end + + def calculate(number_of_approvers) + involved_users + + # Picks most active users from hash like: {user1: 2, user2: 6} + @users.sort_by { |user, count| count }.map(&:first).take(number_of_approvers) + end + + private + + def involved_users + @repo = @merge_request.target_project.repository + + list_of_involved_files.each do |path| + @repo.commits(@merge_request.target_branch, path, COMMITS_TO_CONSIDER).each do |commit| + @users[commit.author] += 1 if commit.author + end + end + end + + def list_of_involved_files + compare_diffs = @merge_request.compare_diffs || @merge_request.diffs + + return [] unless compare_diffs.present? + + compare_diffs.map do |diff| + if diff.deleted_file || diff.renamed_file + diff.old_path + else + diff.new_path + end + end + end + end +end diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb index 0d156047ff0..0f6588e9932 100644 --- a/lib/gitlab/backend/grack_auth.rb +++ b/lib/gitlab/backend/grack_auth.rb @@ -42,28 +42,79 @@ module Grack elsif @user.nil? && !@ci unauthorized else - render_not_found + apply_negotiate_final_leg(render_not_found) end end private + def allow_basic_auth? + return true unless Gitlab.config.kerberos.enabled && + Gitlab.config.kerberos.use_dedicated_port && + @env['SERVER_PORT'] == Gitlab.config.kerberos.port.to_s + end + + def allow_kerberos_auth? + return false unless Gitlab.config.kerberos.enabled + return true unless Gitlab.config.kerberos.use_dedicated_port + # When using a dedicated port, allow Kerberos auth only if port matches the configured one + @env['SERVER_PORT'] == Gitlab.config.kerberos.port.to_s + end + + def spnego_challenge + return "Negotiate" unless @auth.spnego_response_token + "Negotiate #{::Base64.strict_encode64(@auth.spnego_response_token)}" + end + + def challenge + challenges = [] + challenges << super if allow_basic_auth? + challenges << spnego_challenge if allow_kerberos_auth? + # Use \n separator to generate multiple WWW-Authenticate headers in case of multiple challenges + challenges.join("\n") + end + + def apply_negotiate_final_leg(response) + return response unless allow_kerberos_auth? && @auth.spnego_response_token + # As per RFC4559, we may have a final WWW-Authenticate header to send in + # the response even if it's not a 401 status + status, headers, body = response + headers['WWW-Authenticate'] = spnego_challenge + + [status, headers, body] + end + + def valid_auth_method? + (allow_basic_auth? && @auth.basic?) || (allow_kerberos_auth? && @auth.negotiate?) + end + def auth! return unless @auth.provided? - return bad_request unless @auth.basic? + return bad_request unless valid_auth_method? - # Authentication with username and password - login, password = @auth.credentials + if @auth.negotiate? + # Authentication with Kerberos token + krb_principal = @auth.spnego_credentials! + return unless krb_principal - # Allow authentication for GitLab CI service - # if valid token passed - if ci_request?(login, password) - @ci = true - return - end + # Set @user if authentication succeeded + identity = ::Identity.find_by(provider: 'kerberos', extern_uid: krb_principal) + identity ||= ::Identity.find_by(provider: 'kerberos', extern_uid: krb_principal.split("@")[0]) + @user = identity.user if identity + else + # Authentication with username and password + login, password = @auth.credentials + + # Allow authentication for GitLab CI service + # if valid token passed + if ci_request?(login, password) + @ci = true + return + end - @user = authenticate_user(login, password) + @user = authenticate_user(login, password) + end if @user Gitlab::ShellEnv.set_env(@user) @@ -216,5 +267,43 @@ module Grack def render_not_found [404, { "Content-Type" => "text/plain" }, ["Not Found"]] end + + class Request < Rack::Auth::Basic::Request + attr_reader :spnego_response_token + + def negotiate? + parts.first && scheme == "negotiate" + end + + def spnego_token + ::Base64.strict_decode64(params) + end + + def spnego_credentials! + require 'gssapi' + gss = GSSAPI::Simple.new(nil, nil, Gitlab.config.kerberos.keytab) + # the GSSAPI::Simple constructor transforms a nil service name into a default value, so + # pass service name to acquire_credentials explicitly to support the special meaning of nil + gss_service_name = + if Gitlab.config.kerberos.service_principal_name.present? + gss.import_name(Gitlab.config.kerberos.service_principal_name) + else + nil # accept any valid service principal name from keytab + end + gss.acquire_credentials(gss_service_name) # grab credentials from keytab + + # Decode token + gss_result = gss.accept_context(spnego_token) + + # gss_result will be 'true' if nothing has to be returned to the client + @spnego_response_token = gss_result if gss_result && gss_result != true + + # Return user principal name if authentication succeeded + gss.display_name + rescue GSSAPI::GssApiError => ex + Rails.logger.error "#{self.class.name}: failed to process Negotiate/Kerberos authentication: #{ex.message}" + false + end + end end end diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index 3ed1eec517c..ee89c5052c0 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -2,6 +2,7 @@ module Gitlab class GitAccess DOWNLOAD_COMMANDS = %w{ git-upload-pack git-upload-archive } PUSH_COMMANDS = %w{ git-receive-pack } + GIT_ANNEX_COMMANDS = %w{ git-annex-shell } attr_reader :actor, :project @@ -66,6 +67,8 @@ module Gitlab download_access_check when *PUSH_COMMANDS push_access_check(changes) + when *GIT_ANNEX_COMMANDS + git_annex_access_check(project, changes) else build_status_object(false, "The command you're trying to execute is not allowed.") end @@ -108,6 +111,11 @@ module Gitlab return build_status_object(false, "A repository for this project does not exist yet.") end + if ::License.block_changes? + message = ::LicenseHelper.license_message(signed_in: true, is_admin: (user && user.is_admin?)) + return build_status_object(false, message) + end + changes = changes.lines if changes.kind_of?(String) # Iterate over all changes to find if user allowed all of them to be applied @@ -152,13 +160,94 @@ module Gitlab return status end - build_status_object(true) + # Return build_status_object(true) if all git hook checks passed successfully + # or build_status_object(false) if any hook fails + git_hook_check(user, project, ref, oldrev, newrev) end def forced_push?(oldrev, newrev) Gitlab::ForcePushCheck.force_push?(project, oldrev, newrev) end + def git_hook_check(user, project, ref, oldrev, newrev) + return build_status_object(true) unless project.git_hook + + return build_status_object(true) unless newrev && oldrev + + git_hook = project.git_hook + + # Prevent tag removal + if Gitlab::Git.tag_ref?(ref) + if git_hook.deny_delete_tag && protected_tag?(tag_name(ref)) && Gitlab::Git.blank_ref?(newrev) + return build_status_object(false, "You can not delete tag") + end + else + return build_status_object(true) unless git_hook.commit_validation? + return build_status_object(true) if Gitlab::Git.blank_ref?(newrev) + + oldrev = project.default_branch if Gitlab::Git.blank_ref?(oldrev) + + commits = + if oldrev + project.repository.commits_between(oldrev, newrev) + else + project.repository.commits(newrev) + end + + commits.each do |commit| + if git_hook.commit_message_regex.present? + unless commit.safe_message =~ Regexp.new(git_hook.commit_message_regex) + return build_status_object(false, "Commit message does not follow the pattern '#{git_hook.commit_message_regex}'") + end + end + + if git_hook.author_email_regex.present? + unless commit.committer_email =~ Regexp.new(git_hook.author_email_regex) + return build_status_object(false, "Committer's email '#{commit.committer_email}' does not follow the pattern '#{git_hook.author_email_regex}'") + end + + unless commit.author_email =~ Regexp.new(git_hook.author_email_regex) + return build_status_object(false, "Author's email '#{commit.author_email}' does not follow the pattern '#{git_hook.author_email_regex}'") + end + end + + # Check whether author is a GitLab member + if git_hook.member_check + unless User.existing_member?(commit.author_email.downcase) + return build_status_object(false, "Author '#{commit.author_email}' is not a member of team") + end + + if commit.author_email.downcase != commit.committer_email.downcase + unless User.existing_member?(commit.committer_email.downcase) + return build_status_object(false, "Committer '#{commit.committer_email}' is not a member of team") + end + end + end + + if git_hook.file_name_regex.present? + commit.diffs.each do |diff| + if (diff.renamed_file || diff.new_file) && diff.new_path =~ Regexp.new(git_hook.file_name_regex) + return build_status_object(false, "File name #{diff.new_path.inspect} does not follow the pattern '#{git_hook.file_name_regex}'") + end + end + end + + if git_hook.max_file_size > 0 + commit.diffs.each do |diff| + next if diff.deleted_file + + blob = project.repository.blob_at(commit.id, diff.new_path) + if blob.size > git_hook.max_file_size.megabytes + return build_status_object(false, "File #{diff.new_path.inspect} is larger than the allowed size of #{git_hook.max_file_size} MB") + end + end + end + end + end + + build_status_object(true) + end + private def protected_branch_action(oldrev, newrev, branch_name) @@ -206,5 +295,21 @@ module Gitlab def build_status_object(status, message = '') GitAccessStatus.new(status, message) end + + def git_annex_access_check(project, changes) + unless user && user_allowed? + return build_status_object(false, "You don't have access") + end + + unless project.repository.exists? + return build_status_object(false, "Repository does not exist") + end + + if user.can?(:push_code, project) + build_status_object(true) + else + build_status_object(false, "You don't have permission") + end + end end end diff --git a/lib/gitlab/github_import/client.rb b/lib/gitlab/github_import/client.rb index 270cbcd9ccd..1af924c394b 100644 --- a/lib/gitlab/github_import/client.rb +++ b/lib/gitlab/github_import/client.rb @@ -12,7 +12,7 @@ module Gitlab if access_token ::Octokit.auto_paginate = true - @api = ::Octokit::Client.new(access_token: access_token) + @api = ::Octokit::Client.new(access_token: access_token, api_endpoint: github_options[:site]) end end @@ -42,11 +42,11 @@ module Gitlab private def config - Gitlab.config.omniauth.providers.find{|provider| provider.name == "github"} + Gitlab.config.omniauth.providers.find { |provider| provider.name == "github"} end def github_options - OmniAuth::Strategies::GitHub.default_options[:client_options].symbolize_keys + config["args"]["client_options"].deep_symbolize_keys end end end diff --git a/lib/gitlab/kerberos/authentication.rb b/lib/gitlab/kerberos/authentication.rb new file mode 100644 index 00000000000..6e9a2e62164 --- /dev/null +++ b/lib/gitlab/kerberos/authentication.rb @@ -0,0 +1,41 @@ +require "krb5_auth" +# This calls helps to authenticate to Kerberos by providing username and password + +module Gitlab + module Kerberos + class Authentication + def self.login(login, password) + return unless Devise.omniauth_providers.include?(:kerberos) + return unless login.present? && password.present? + + auth = new(login, password) + auth.login + end + + def initialize(login, password) + @login = login + @password = password + @krb5 = ::Krb5Auth::Krb5.new + end + + def valid? + @krb5.get_init_creds_password(@login, @password) + rescue ::Krb5Auth::Krb5::Exception + false + end + + def login + valid? && find_by_login(@login) + end + + private + + def find_by_login(login) + identity = ::Identity. + where(provider: :kerberos). + where('lower(extern_uid) = ?', login).last + identity && identity.user + end + end + end +end diff --git a/lib/gitlab/ldap/access.rb b/lib/gitlab/ldap/access.rb index 16ff03c38d4..cc8b14cc5ba 100644 --- a/lib/gitlab/ldap/access.rb +++ b/lib/gitlab/ldap/access.rb @@ -1,11 +1,12 @@ # LDAP authorization model # # * Check if we are allowed access (not blocked) +# * Update authorizations and associations # module Gitlab module LDAP class Access - attr_reader :adapter, :provider, :user + attr_reader :adapter, :provider, :user, :ldap_user def self.open(user, &block) Gitlab::LDAP::Adapter.open(user.ldap_identity.provider) do |adapter| @@ -16,6 +17,8 @@ module Gitlab def self.allowed?(user) self.open(user) do |access| if access.allowed? + access.update_permissions + access.update_email user.last_credential_check_at = Time.now user.save true @@ -57,6 +60,136 @@ module Gitlab def ldap_config Gitlab::LDAP::Config.new(provider) end + + def ldap_user + @ldap_user ||= Gitlab::LDAP::Person.find_by_dn(user.ldap_identity.extern_uid, adapter) + end + + def update_permissions + if sync_ssh_keys? + update_ssh_keys + end + + # Skip updating group permissions + # if instance does not use group_base setting + return true unless group_base.present? + + update_ldap_group_links + + if admin_group.present? + update_admin_status + end + end + + # Update user ssh keys if they changed in LDAP + def update_ssh_keys + user.keys.ldap.where.not(key: ldap_user.ssh_keys).each do |deleted_key| + Rails.logger.info "#{self.class.name}: removing LDAP SSH key #{deleted_key.key} from #{user.name} (#{user.id})" + unless deleted_key.destroy + Rails.logger.error "#{self.class.name}: failed to remove LDAP SSH key #{key.inspect} from #{user.name} (#{user.id})" + end + end + + (ldap_user.ssh_keys - user.keys.ldap.pluck(:key)).each do |key| + Rails.logger.info "#{self.class.name}: adding LDAP SSH key #{key.inspect} to #{user.name} (#{user.id})" + new_key = LDAPKey.new(title: "LDAP - #{ldap_config.sync_ssh_keys}", key: key) + new_key.user = user + unless new_key.save + Rails.logger.error "#{self.class.name}: failed to add LDAP SSH key #{key.inspect} to #{user.name} (#{user.id})\n"\ + "error messages: #{new_key.errors.messages}" + end + end + end + + # Update user email if it changed in LDAP + def update_email + return false unless ldap_user.try(:email) + + ldap_email = ldap_user.email.last.to_s.downcase + + return false if user.email == ldap_email + + user.skip_reconfirmation! + user.update(email: ldap_email) + end + + def update_admin_status + admin_group = Gitlab::LDAP::Group.find_by_cn(ldap_config.admin_group, adapter) + admin_user = Gitlab::LDAP::Person.find_by_dn(user.ldap_identity.extern_uid, adapter) + + if admin_group && admin_group.has_member?(admin_user) + unless user.admin? + user.admin = true + user.save + end + else + if user.admin? + user.admin = false + user.save + end + end + end + + # Loop throug all ldap conneted groups, and update the users link with it + # + # We documented what sort of queries an LDAP server can expect from + # GitLab EE in doc/integration/ldap.md. Please remember to update that + # documentation if you change the algorithm below. + def update_ldap_group_links + gitlab_groups_with_ldap_link.each do |group| + active_group_links = group.ldap_group_links.where(cn: cns_with_access) + + if active_group_links.any? + group.add_users([user.id], fetch_group_access(group, user, active_group_links), skip_notification: true) + elsif group.last_owner?(user) + Rails.logger.warn "#{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" + else + group.users.delete(user) + end + end + end + + def ldap_groups + @ldap_groups ||= ::LdapGroupLink.with_provider(provider).distinct(:cn).pluck(:cn).map do |cn| + Gitlab::LDAP::Group.find_by_cn(cn, adapter) + end.compact + end + + # returns a collection of cn strings to which the user has access + def cns_with_access + @ldap_groups_with_access ||= ldap_groups.select do |ldap_group| + ldap_group.has_member?(ldap_user) + end.map(&:cn) + end + + def sync_ssh_keys? + ldap_config.sync_ssh_keys? + end + + def group_base + ldap_config.group_base + end + + def admin_group + ldap_config.admin_group + end + + private + def gitlab_groups_with_ldap_link + ::Group.includes(:ldap_group_links).references(:ldap_group_links). + where.not(ldap_group_links: { id: nil }). + where(ldap_group_links: { provider: provider }) + end + + # Get the group_access for a give user. + # Always respect the current level, never downgrade it. + def fetch_group_access(group, user, active_group_links) + current_access_level = group.group_members.where(user_id: user).maximum(:access_level) + max_group_access_level = active_group_links.maximum(:group_access) + + # TODO: Test if nil value of current_access_level in handled properly + [current_access_level, max_group_access_level].compact.max + end end end end diff --git a/lib/gitlab/ldap/adapter.rb b/lib/gitlab/ldap/adapter.rb index 577a890a7d9..b127094a51d 100644 --- a/lib/gitlab/ldap/adapter.rb +++ b/lib/gitlab/ldap/adapter.rb @@ -22,6 +22,30 @@ module Gitlab Gitlab::LDAP::Config.new(provider) end + # 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) + } + + options.merge!(size: size) if size + + ldap_search(options).map do |entry| + Gitlab::LDAP::Group.new(entry, self) + end + end + + def group(*args) + groups(*args).first + end + def users(field, value, limit = nil) if field.to_sym == :dn options = { diff --git a/lib/gitlab/ldap/config.rb b/lib/gitlab/ldap/config.rb index 101a3285f4b..127239c5046 100644 --- a/lib/gitlab/ldap/config.rb +++ b/lib/gitlab/ldap/config.rb @@ -4,12 +4,16 @@ module Gitlab class Config attr_accessor :provider, :options + class InvalidProvider < StandardError; end + def self.enabled? Gitlab.config.ldap.enabled end def self.servers Gitlab.config.ldap.servers.values + rescue Settingslogic::MissingSetting + [] end def self.providers @@ -21,7 +25,7 @@ module Gitlab end def self.invalid_provider(provider) - raise "Unknown provider (#{provider}). Available providers: #{providers}" + raise InvalidProvider.new("Unknown provider (#{provider}). Available providers: #{providers}") end def initialize(provider) @@ -30,6 +34,7 @@ module Gitlab else self.class.invalid_provider(provider) end + @options = config_for(@provider) # Use @provider, not provider end @@ -55,6 +60,10 @@ module Gitlab options['uid'] end + def label + options['label'] + end + def sync_ssh_keys? sync_ssh_keys.present? end @@ -89,6 +98,7 @@ module Gitlab end protected + def base_config Gitlab.config.ldap end diff --git a/lib/gitlab/ldap/group.rb b/lib/gitlab/ldap/group.rb new file mode 100644 index 00000000000..53ec0d585a4 --- /dev/null +++ b/lib/gitlab/ldap/group.rb @@ -0,0 +1,80 @@ +module Gitlab + module LDAP + class Group + attr_accessor :adapter + + 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 cn + entry.cn.first + end + + def name + cn + end + + def path + name.parameterize + end + + def memberuid? + entry.respond_to? :memberuid + end + + def member_uids + entry.memberuid + end + + def has_member?(user) + user_uid = user.uid.downcase + user_dn = user.dn.downcase + + if memberuid? + member_uids.any? { |member_uid| member_uid.downcase == user_uid } + elsif member_dns.any? { |member_dn| member_dn.downcase == user_dn } + true + elsif member_dns.any? { |member_dn| member_dn.downcase == "uid=" + user_uid } + true + elsif adapter.config.active_directory + adapter.dn_matches_filter?(user.dn, active_directory_recursive_memberof_filter) + end + end + + def member_dns + if (entry.respond_to? :member) && (entry.respond_to? :submember) + entry.member + entry.submember + elsif entry.respond_to? :member + entry.member + elsif entry.respond_to? :uniquemember + entry.uniquemember + elsif entry.respond_to? :memberof + entry.memberof + else + Rails.logger.warn("Could not find member DNs for LDAP group #{entry.inspect}") + [] + end + end + + private + + # We use the ActiveDirectory LDAP_MATCHING_RULE_IN_CHAIN matching rule; see + # http://msdn.microsoft.com/en-us/library/aa746475%28VS.85%29.aspx#code-snippet-5 + def active_directory_recursive_memberof_filter + Net::LDAP::Filter.ex("memberOf:1.2.840.113556.1.4.1941", entry.dn) + end + + def entry + @entry + end + end + end +end diff --git a/lib/gitlab/ldap/person.rb b/lib/gitlab/ldap/person.rb index b81f3e8e8f5..aa93fcd5952 100644 --- a/lib/gitlab/ldap/person.rb +++ b/lib/gitlab/ldap/person.rb @@ -47,6 +47,16 @@ module Gitlab entry.dn 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 + private def entry diff --git a/lib/gitlab/ldap/user.rb b/lib/gitlab/ldap/user.rb index 4be99dd88c2..802e1ef89f0 100644 --- a/lib/gitlab/ldap/user.rb +++ b/lib/gitlab/ldap/user.rb @@ -24,7 +24,9 @@ module Gitlab update_user_attributes end - # instance methods + delegate :otp_required_for_login?, :otp_backup_codes, :otp_attempt, + to: :gl_user + def gl_user @gl_user ||= find_by_uid_and_provider || find_by_email || build_new_user end diff --git a/lib/gitlab/markdown/external_issue_reference_filter.rb b/lib/gitlab/markdown/external_issue_reference_filter.rb index 8f86f13976a..ea28c026a72 100644 --- a/lib/gitlab/markdown/external_issue_reference_filter.rb +++ b/lib/gitlab/markdown/external_issue_reference_filter.rb @@ -23,6 +23,18 @@ module Gitlab end end + def self.referenced_by(node) + project = Project.find(node.attr("data-project")) rescue nil + return unless project + + id = node.attr("data-external-issue") + external_issue = ExternalIssue.new(id, project) + + return unless external_issue + + { external_issue: external_issue } + end + def call # Early return if the project isn't using an external tracker return doc if project.nil? || project.default_issues_tracker? @@ -42,12 +54,14 @@ module Gitlab def issue_link_filter(text) project = context[:project] - self.class.references_in(text) do |match, issue| - url = url_for_issue(issue, project, only_path: context[:only_path]) + self.class.references_in(text) do |match, id| + ExternalIssue.new(id, project) + + url = url_for_issue(id, project, only_path: context[:only_path]) title = escape_once("Issue in #{project.external_issue_tracker.title}") klass = reference_class(:issue) - data = data_attribute(project: project.id) + data = data_attribute(project: project.id, external_issue: id) %(<a href="#{url}" #{data} title="#{title}" diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb index da8df8a3025..e85dc9cefe5 100644 --- a/lib/gitlab/reference_extractor.rb +++ b/lib/gitlab/reference_extractor.rb @@ -22,6 +22,14 @@ module Gitlab end end + def issues + if project && project.jira_tracker? + references[:external_issue] + else + references[:issue] + end + end + private def references diff --git a/lib/gitlab/upgrader.rb b/lib/gitlab/upgrader.rb index f3567f3ef85..d329e3fdf78 100644 --- a/lib/gitlab/upgrader.rb +++ b/lib/gitlab/upgrader.rb @@ -44,13 +44,13 @@ module Gitlab def latest_version_raw git_tags = fetch_git_tags - git_tags = git_tags.select { |version| version =~ /v\d+\.\d+\.\d+\Z/ } - git_versions = git_tags.map { |tag| Gitlab::VersionInfo.parse(tag.match(/v\d+\.\d+\.\d+/).to_s) } + git_tags = git_tags.select { |version| version =~ /v\d+\.\d+\.\d+-ee\Z/ } + git_versions = git_tags.map { |tag| Gitlab::VersionInfo.parse(tag.match(/v\d+\.\d+\.\d+-ee/).to_s) } "v#{git_versions.sort.last.to_s}" end def fetch_git_tags - remote_tags, _ = Gitlab::Popen.popen(%W(#{Gitlab.config.git.bin_path} ls-remote --tags https://gitlab.com/gitlab-org/gitlab-ce.git)) + remote_tags, _ = Gitlab::Popen.popen(%W(#{Gitlab.config.git.bin_path} ls-remote --tags https://gitlab.com/gitlab-org/gitlab-ee.git)) remote_tags.split("\n").grep(/tags\/v#{current_version.major}/) end @@ -58,7 +58,7 @@ module Gitlab { "Stash changed files" => %W(#{Gitlab.config.git.bin_path} stash), "Get latest code" => %W(#{Gitlab.config.git.bin_path} fetch), - "Switch to new version" => %W(#{Gitlab.config.git.bin_path} checkout v#{latest_version}), + "Switch to new version" => %W(#{Gitlab.config.git.bin_path} checkout v#{latest_version}-ee), "Install gems" => %W(bundle), "Migrate DB" => %W(bundle exec rake db:migrate), "Recompile assets" => %W(bundle exec rake assets:clean assets:precompile), diff --git a/lib/tasks/migrate/ldap.rake b/lib/tasks/migrate/ldap.rake new file mode 100644 index 00000000000..ec002fc4407 --- /dev/null +++ b/lib/tasks/migrate/ldap.rake @@ -0,0 +1,19 @@ +desc "GITLAB | migrate provider names to multiple ldap setup" +namespace :gitlab do + task migrate_ldap_providers: :environment do + config = Gitlab::LDAP::Config + raise 'No LDAP server hash defined. See config/gitlab.yml.example for an example' unless config.servers.any? + + provider = config.servers.first['provider_name'] + valid_providers = config.providers + unmigrated_group_links = LdapGroupLink.where('provider IS NULL OR provider NOT IN (?)', config.providers) + puts "found #{unmigrated_group_links.count} unmigrated LDAP links" + puts "setting provider to #{provider}" + unmigrated_group_links.update_all provider: provider + + unmigrated_ldap_identities = Identity.where(provider: 'ldap') + puts "found #{unmigrated_ldap_identities.count} unmigrated LDAP users" + puts "setting provider to #{provider}" + unmigrated_ldap_identities.update_all provider: provider + end +end diff --git a/public/500.html b/public/500.html index 08c11bbd05a..95e55562efc 100644 --- a/public/500.html +++ b/public/500.html @@ -9,6 +9,6 @@ <h3>Whoops, something went wrong on our end.</h3> <hr/> <p>Try refreshing the page, or going back and attempting the action again.</p> - <p>Please contact your GitLab administrator if this problem persists.</p> + <p><a href="/help" >Please contact your GitLab administrator</a> if this problem persists.</p> </body> </html> diff --git a/public/502.html b/public/502.html index 9480a928439..15da9f9e295 100644 --- a/public/502.html +++ b/public/502.html @@ -9,6 +9,6 @@ <h3>Whoops, GitLab is taking too much time to respond.</h3> <hr/> <p>Try refreshing the page, or going back and attempting the action again.</p> - <p>Please contact your GitLab administrator if this problem persists.</p> + <p><a href="/help" >Please contact your GitLab administrator</a> if this problem persists.</p> </body> </html> diff --git a/public/header_logo_dark.png b/public/header_logo_dark.png Binary files differnew file mode 100644 index 00000000000..4a96572d570 --- /dev/null +++ b/public/header_logo_dark.png diff --git a/public/header_logo_light.png b/public/header_logo_light.png Binary files differnew file mode 100644 index 00000000000..bc2ef601a53 --- /dev/null +++ b/public/header_logo_light.png diff --git a/spec/controllers/import/github_controller_spec.rb b/spec/controllers/import/github_controller_spec.rb index bbf8adef534..bcc713dce2a 100644 --- a/spec/controllers/import/github_controller_spec.rb +++ b/spec/controllers/import/github_controller_spec.rb @@ -22,6 +22,8 @@ describe Import::GithubController do token = "asdasd12345" allow_any_instance_of(Gitlab::GithubImport::Client). to receive(:get_token).and_return(token) + allow_any_instance_of(Gitlab::GithubImport::Client). + to receive(:github_options).and_return({}) stub_omniauth_provider('github') get :callback diff --git a/spec/controllers/unsubscribes_controller_spec.rb b/spec/controllers/unsubscribes_controller_spec.rb new file mode 100644 index 00000000000..ea9fe79a25f --- /dev/null +++ b/spec/controllers/unsubscribes_controller_spec.rb @@ -0,0 +1,34 @@ +require 'spec_helper' + +describe UnsubscribesController do + let!(:user) { create :user, email: 'me@example.com' } + + describe "show" do + it "responds with success" do + get :show, email: Base64.urlsafe_encode64('me@example.com') + + assert_response :success + end + + it "behaves the same if email address isn't known in the system" do + get :show, email: Base64.urlsafe_encode64('i@dont_exists.com') + + assert_response :success + end + end + + describe "create" do + it "unsubscribes the connected user" do + post :create, email: Base64.urlsafe_encode64('me@example.com') + + assert user.reload.admin_email_unsubscribed_at + end + + # Don't tell if the email does not exists + it "behaves the same if email address isn't known in the system" do + post :create, email: Base64.urlsafe_encode64('i@dont_exists.com') + + assert_response :redirect + end + end +end diff --git a/spec/factories.rb b/spec/factories.rb index 200f18f660d..517018ae6ef 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -152,6 +152,10 @@ FactoryGirl.define do url end + factory :group_hook do + url + end + factory :project_snippet do project author @@ -195,8 +199,28 @@ FactoryGirl.define do project end + factory :ldap_group_link do + cn 'group1' + group_access Gitlab::Access::GUEST + provider 'ldapmain' + group + end + factory :identity do provider 'ldapmain' extern_uid 'my-ldap-id' end + + factory :gitlab_license, class: "Gitlab::License" do + starts_at { Date.today - 1.month } + licensee do + { "Name" => FFaker::Name.name } + end + notify_users_at { |l| l.expires_at } + notify_admins_at { |l| l.expires_at } + end + + factory :license do + data { build(:gitlab_license).export } + end end diff --git a/spec/factories/appearances.rb b/spec/factories/appearances.rb new file mode 100644 index 00000000000..b301fa93791 --- /dev/null +++ b/spec/factories/appearances.rb @@ -0,0 +1,8 @@ +# Read about factories at https://github.com/thoughtbot/factory_girl + +FactoryGirl.define do + factory :appearance do + title "GitLab Enterprise Edition" + description "Open source software to collaborate on code" + end +end diff --git a/spec/factories/approvals.rb b/spec/factories/approvals.rb new file mode 100644 index 00000000000..5b5069e93fd --- /dev/null +++ b/spec/factories/approvals.rb @@ -0,0 +1,8 @@ +# Read about factories at https://github.com/thoughtbot/factory_girl + +FactoryGirl.define do + factory :approval do + merge_request + user + end +end diff --git a/spec/factories/approvers.rb b/spec/factories/approvers.rb new file mode 100644 index 00000000000..6d7d61d35a2 --- /dev/null +++ b/spec/factories/approvers.rb @@ -0,0 +1,8 @@ +# Read about factories at https://github.com/thoughtbot/factory_girl + +FactoryGirl.define do + factory :approver do + target factory: :merge_request + user + end +end diff --git a/spec/factories/git_hooks.rb b/spec/factories/git_hooks.rb new file mode 100644 index 00000000000..b11102010a3 --- /dev/null +++ b/spec/factories/git_hooks.rb @@ -0,0 +1,15 @@ +# Read about factories at https://github.com/thoughtbot/factory_girl + +FactoryGirl.define do + factory :git_hook do + force_push_regex "MyString" + deny_delete_tag false + delete_branch_regex "MyString" + project + commit_message_regex "MyString" + + factory :git_hook_sample do + is_sample true + end + end +end diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb index 729a49c9f72..c524618a4f1 100644 --- a/spec/factories/merge_requests.rb +++ b/spec/factories/merge_requests.rb @@ -65,8 +65,15 @@ FactoryGirl.define do target_branch "master" end + trait :with_approver do + after :create do |merge_request| + create :approver, target: merge_request + end + end + factory :closed_merge_request, traits: [:closed] factory :reopened_merge_request, traits: [:reopened] factory :merge_request_with_diffs, traits: [:with_diffs] + factory :merge_request_with_approver, traits: [:with_approver] end end diff --git a/spec/factories/project_group_links.rb b/spec/factories/project_group_links.rb new file mode 100644 index 00000000000..e73cc05f9d7 --- /dev/null +++ b/spec/factories/project_group_links.rb @@ -0,0 +1,6 @@ +FactoryGirl.define do + factory :project_group_link do + project + group + end +end diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index 32fd4065bb4..219197ef28d 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -36,7 +36,7 @@ describe 'Issues', feature: true do end it 'does not change issue count' do - expect { click_button 'Save changes' }.to_not change { Issue.count } + expect { click_button 'Save changes' }.not_to change { Issue.count } end it 'should update issue fields' do diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb index 57563add74c..a82374d8e8b 100644 --- a/spec/features/security/project/internal_access_spec.rb +++ b/spec/features/security/project/internal_access_spec.rb @@ -238,4 +238,32 @@ describe "Internal Project Access", feature: true do it { is_expected.to be_denied_for :user } it { is_expected.to be_denied_for :visitor } end + + context "when license blocks changes" do + before do + allow(License).to receive(:block_changes?).and_return(true) + end + + describe "GET /:project_path/issues/new" do + subject { new_namespace_project_issue_path(project.namespace, project) } + + it { is_expected.to be_denied_for master } + it { is_expected.to be_denied_for reporter } + it { is_expected.to be_denied_for :admin } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } + end + + describe "GET /:project_path/merge_requests/new" do + subject { new_namespace_project_merge_request_path(project.namespace, project) } + + it { is_expected.to be_denied_for master } + it { is_expected.to be_denied_for reporter } + it { is_expected.to be_denied_for :admin } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } + end + end end diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb index a1e111c6cab..10e69ad89b5 100644 --- a/spec/features/security/project/private_access_spec.rb +++ b/spec/features/security/project/private_access_spec.rb @@ -216,4 +216,32 @@ describe "Private Project Access", feature: true do it { is_expected.to be_denied_for :user } it { is_expected.to be_denied_for :visitor } end + + context "when license blocks changes" do + before do + allow(License).to receive(:block_changes?).and_return(true) + end + + describe "GET /:project_path/issues/new" do + subject { new_namespace_project_issue_path(project.namespace, project) } + + it { is_expected.to be_denied_for master } + it { is_expected.to be_denied_for reporter } + it { is_expected.to be_denied_for :admin } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } + end + + describe "GET /:project_path/merge_requests/new" do + subject { new_namespace_project_merge_request_path(project.namespace, project) } + + it { is_expected.to be_denied_for master } + it { is_expected.to be_denied_for reporter } + it { is_expected.to be_denied_for :admin } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } + end + end end diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb index 655d2c8b7d9..d8ee44dc283 100644 --- a/spec/features/security/project/public_access_spec.rb +++ b/spec/features/security/project/public_access_spec.rb @@ -242,4 +242,32 @@ describe "Public Project Access", feature: true do it { is_expected.to be_denied_for :user } it { is_expected.to be_denied_for :visitor } end + + context "when license blocks changes" do + before do + allow(License).to receive(:block_changes?).and_return(true) + end + + describe "GET /:project_path/issues/new" do + subject { new_namespace_project_issue_path(project.namespace, project) } + + it { is_expected.to be_denied_for master } + it { is_expected.to be_denied_for reporter } + it { is_expected.to be_denied_for :admin } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } + end + + describe "GET /:project_path/merge_requests/new" do + subject { new_namespace_project_merge_request_path(project.namespace, project) } + + it { is_expected.to be_denied_for master } + it { is_expected.to be_denied_for reporter } + it { is_expected.to be_denied_for :admin } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } + end + end end diff --git a/spec/finders/projects_finder_spec.rb b/spec/finders/projects_finder_spec.rb index de9d4cd6128..f272297e291 100644 --- a/spec/finders/projects_finder_spec.rb +++ b/spec/finders/projects_finder_spec.rb @@ -3,11 +3,16 @@ require 'spec_helper' describe ProjectsFinder do let(:user) { create :user } let(:group) { create :group } + let(:group2) { create :group } let(:project1) { create(:empty_project, :public, group: group) } let(:project2) { create(:empty_project, :internal, group: group) } let(:project3) { create(:empty_project, :private, group: group) } let(:project4) { create(:empty_project, :private, group: group) } + let(:project5) { create(:empty_project, :private, group: group2) } + let(:project6) { create(:empty_project, :internal, group: group2) } + let(:project7) { create(:empty_project, :public, group: group2) } + let(:project8) { create(:empty_project, :private, group: group2) } context 'non authenticated' do subject { ProjectsFinder.new.execute(nil, group: group) } @@ -48,4 +53,18 @@ describe ProjectsFinder do it { is_expected.to include(project3) } it { is_expected.to include(project4) } end + + context 'authenticated, group member with project shared with group' do + before do + group.add_user(user, Gitlab::Access::DEVELOPER) + project5.project_group_links.create group_access: Gitlab::Access::MASTER, group: group + end + + subject { ProjectsFinder.new.execute(user, group: group2) } + + it { should include(project5) } + it { should include(project6) } + it { should include(project7) } + it { should_not include(project8) } + end end diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index 1dfae0fbd3f..ced562efd8d 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -280,6 +280,10 @@ describe ApplicationHelper do it 'allows the script tag to be excluded' do expect(element(skip_js: true)).not_to include 'script' end + + it 'converts to Time' do + expect { helper.time_ago_with_tooltip(Date.today) }.not_to raise_error + end end describe 'render_markup' do diff --git a/spec/helpers/merge_request_helper_spec.rb b/spec/helpers/merge_request_helper_spec.rb new file mode 100644 index 00000000000..e3935132a76 --- /dev/null +++ b/spec/helpers/merge_request_helper_spec.rb @@ -0,0 +1,100 @@ +require "spec_helper" + +describe MergeRequestsHelper do + let(:project) { create :project } + let(:merge_request) { MergeRequest.new } + let(:ci_service) { CiService.new } + let(:last_commit) { Commit.new({}, project) } + + before do + allow(merge_request).to receive(:source_project) { project } + allow(merge_request).to receive(:last_commit) { last_commit } + allow(project).to receive(:ci_service) { ci_service } + allow(last_commit).to receive(:sha) { '12d65c' } + end + + describe 'ci_build_details_path' do + it 'does not include api credentials in a link' do + allow(ci_service).to receive(:build_page) { "http://secretuser:secretpass@jenkins.example.com:8888/job/test1/scm/bySHA1/12d65c" } + expect(ci_build_details_path(merge_request)).not_to match("secret") + end + end + + describe 'issues_sentence' do + subject { issues_sentence(issues) } + let(:issues) do + [build(:issue, iid: 1), build(:issue, iid: 2), build(:issue, iid: 3)] + end + + it { is_expected.to eq('#1, #2, and #3') } + + context 'for JIRA issues' do + let(:issues) do + [ + JiraIssue.new('JIRA-123', project), + JiraIssue.new('JIRA-456', project), + JiraIssue.new('FOOBAR-7890', project) + ] + end + + it { is_expected.to eq('JIRA-123, JIRA-456, and FOOBAR-7890') } + end + end + + describe 'render_items_list' do + it "returns one item in the list" do + expect(render_items_list(["user"])).to eq("user") + end + + it "returns two items in the list" do + expect(render_items_list(["user", "user1"])).to eq("user and user1") + end + + it "returns three items in the list" do + expect(render_items_list(["user", "user1", "user2"])).to eq("user, user1 and user2") + end + end + + describe 'render_require_section' do + it "returns correct value in case - one approval" do + project.update(approvals_before_merge: 1) + merge_request = create(:merge_request, target_project: project, source_project: project) + expect(render_require_section(merge_request)).to eq("Requires one more approval") + end + + it "returns correct value in case - two approval" do + project.update(approvals_before_merge: 2) + merge_request = create(:merge_request, target_project: project, source_project: project) + expect(render_require_section(merge_request)).to eq("Requires 2 more approvals") + end + + it "returns correct value in case - one approver" do + project.update(approvals_before_merge: 1) + merge_request = create(:merge_request, target_project: project, source_project: project) + user = create :user + merge_request.approvers.create(user_id: user.id) + + expect(render_require_section(merge_request)).to eq("Requires one more approval (from #{user.name})") + end + + it "returns correct value in case - one approver and one more" do + project.update(approvals_before_merge: 2) + merge_request = create(:merge_request, target_project: project, source_project: project) + user = create :user + merge_request.approvers.create(user_id: user.id) + + expect(render_require_section(merge_request)).to eq("Requires 2 more approvals (from #{user.name} and 1 more)") + end + + it "returns correct value in case - two approver and one more" do + project.update(approvals_before_merge: 3) + merge_request = create(:merge_request, target_project: project, source_project: project) + user = create :user + user1 = create :user + merge_request.approvers.create(user_id: user.id) + merge_request.approvers.create(user_id: user1.id) + + expect(render_require_section(merge_request)).to eq("Requires 3 more approvals (from #{user1.name}, #{user.name} and 1 more)") + end + end +end diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index 72806bebe1f..318bb29710f 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -35,6 +35,19 @@ describe Gitlab::Auth do expect( gl_auth.find(username, password) ).not_to eql user end + context "with kerberos" do + before do + allow(Devise).to receive_messages(omniauth_providers: [:kerberos]) + end + + it "finds user" do + allow(Gitlab::Kerberos::Authentication).to receive_messages(valid?: true) + allow(Gitlab::Kerberos::Authentication).to receive_messages(email: user.email) + + expect( gl_auth.find(username, password) ).to eql user + end + end + context "with ldap enabled" do before do allow(Gitlab::LDAP::Config).to receive(:enabled?).and_return(true) diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index c7291689e32..ea9f78213ae 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -28,7 +28,7 @@ describe Gitlab::GitAccess do before do @branch = create :protected_branch, project: project end - + it "returns true if user is a master" do project.team << [user, :master] expect(access.can_push_to_branch?(@branch.name)).to be_truthy @@ -49,7 +49,7 @@ describe Gitlab::GitAccess do before do @branch = create :protected_branch, project: project, developers_can_push: true end - + it "returns true if user is a master" do project.team << [user, :master] expect(access.can_push_to_branch?(@branch.name)).to be_truthy @@ -223,5 +223,94 @@ describe Gitlab::GitAccess do end end end + + context "when license blocks changes" do + before do + allow(License).to receive(:block_changes?).and_return(true) + end + + permissions_matrix.keys.each do |role| + describe "#{role} access" do + before { protect_feature_branch } + before { project.team << [user, role] } + + permissions_matrix[role].each do |action, allowed| + context action do + subject { access.push_access_check(changes[action]) } + + it { expect(subject.allowed?).to be_falsey } + end + end + end + end + end + end + + describe "git_hook_check" do + describe "author email check" do + it 'returns true' do + expect(access.git_hook_check(user, project, 'refs/heads/master', '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9', '570e7b2abdd848b95f2f578043fc23bd6f6fd24d')).to be_truthy + end + + it 'returns false' do + project.create_git_hook + project.git_hook.update(commit_message_regex: "@only.com") + expect(access.git_hook_check(user, project, 'refs/heads/master', '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9', '570e7b2abdd848b95f2f578043fc23bd6f6fd24d')).not_to be_allowed + end + + it 'returns true for tags' do + project.create_git_hook + project.git_hook.update(commit_message_regex: "@only.com") + expect(access.git_hook_check(user, project, 'refs/tags/v1', '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9', '570e7b2abdd848b95f2f578043fc23bd6f6fd24d')).to be_allowed + end + end + + describe "member_check" do + before do + project.create_git_hook + project.git_hook.update(member_check: true) + end + + it 'returns false for non-member user' do + expect(access.git_hook_check(user, project, 'refs/heads/master', '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9', '570e7b2abdd848b95f2f578043fc23bd6f6fd24d')).not_to be_allowed + end + + it 'returns true if committer is a gitlab member' do + create(:user, email: 'dmitriy.zaporozhets@gmail.com') + expect(access.git_hook_check(user, project, 'refs/heads/master', '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9', '570e7b2abdd848b95f2f578043fc23bd6f6fd24d')).to be_allowed + end + end + + describe "file names check" do + it 'returns false when filename is prohibited' do + project.create_git_hook + project.git_hook.update(file_name_regex: "jpg$") + expect(access.git_hook_check(user, project, 'refs/heads/master', '913c66a37b4a45b9769037c55c2d238bd0942d2e', '33f3729a45c02fc67d00adb1b8bca394b0e761d9')).not_to be_allowed + end + + it 'returns true if file name is allowed' do + project.create_git_hook + project.git_hook.update(file_name_regex: "exe$") + expect(access.git_hook_check(user, project, 'refs/heads/master', '913c66a37b4a45b9769037c55c2d238bd0942d2e', '33f3729a45c02fc67d00adb1b8bca394b0e761d9')).to be_allowed + end + end + + describe "max file size check" do + before do + allow_any_instance_of(Gitlab::Git::Blob).to receive(:size).and_return(1.5.megabytes.to_i) + end + + it "returns false when size is too large" do + project.create_git_hook + project.git_hook.update(max_file_size: 1) + expect(access.git_hook_check(user, project, 'refs/heads/master', 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660', '913c66a37b4a45b9769037c55c2d238bd0942d2e')).not_to be_allowed + end + + it "returns true when size is allowed" do + project.create_git_hook + project.git_hook.update(max_file_size: 2) + expect(access.git_hook_check(user, project, 'refs/heads/master', 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660', '913c66a37b4a45b9769037c55c2d238bd0942d2e')).to be_allowed + end + end end end diff --git a/spec/lib/gitlab/github_import/client_spec.rb b/spec/lib/gitlab/github_import/client_spec.rb index 26618120316..f91878d1b07 100644 --- a/spec/lib/gitlab/github_import/client_spec.rb +++ b/spec/lib/gitlab/github_import/client_spec.rb @@ -5,7 +5,8 @@ describe Gitlab::GithubImport::Client do let(:client) { Gitlab::GithubImport::Client.new(token) } before do - Gitlab.config.omniauth.providers << OpenStruct.new(app_id: "asd123", app_secret: "asd123", name: "github") + github_provider = OpenStruct.new(app_id: "asd123", app_secret: "asd123", name: "github", args: { "client_options" => {} }) + allow(Gitlab.config.omniauth).to receive(:providers).and_return([github_provider]) end it 'all OAuth2 client options are symbols' do diff --git a/spec/lib/gitlab/kerberos/authentication_spec.rb b/spec/lib/gitlab/kerberos/authentication_spec.rb new file mode 100644 index 00000000000..1d1fb742226 --- /dev/null +++ b/spec/lib/gitlab/kerberos/authentication_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +describe Gitlab::Kerberos::Authentication do + let(:klass) { Gitlab::Kerberos::Authentication } + let(:user) { create(:omniauth_user, provider: :kerberos, extern_uid: 'gitlab') } + let(:login) { 'john' } + let(:password) { 'password' } + + describe :login do + before do + allow(Devise).to receive_messages(omniauth_providers: [:kerberos]) + end + + it "finds the user if authentication is successful" do + kerberos_realm = user.email.sub(/.*@/, '') + allow_any_instance_of(::Krb5Auth::Krb5).to receive_messages(get_init_creds_password: true, get_default_realm: kerberos_realm) + + expect(klass.login('gitlab', password)).to be_truthy + end + + it "returns false if there is no such user in kerberos" do + kerberos_login = "some-login" + kerberos_realm = user.email.sub(/.*@/, '') + allow_any_instance_of(::Krb5Auth::Krb5).to receive_messages(get_init_creds_password: true, get_default_realm: kerberos_realm) + + expect(klass.login(kerberos_login, password)).to be_falsy + end + end +end diff --git a/spec/lib/gitlab/ldap/access_spec.rb b/spec/lib/gitlab/ldap/access_spec.rb index c38f212b405..901f2bf4667 100644 --- a/spec/lib/gitlab/ldap/access_spec.rb +++ b/spec/lib/gitlab/ldap/access_spec.rb @@ -72,7 +72,7 @@ describe Gitlab::LDAP::Access do end end - context 'without ActiveDirectory enabled' do + context 'withoud ActiveDirectory enabled' do before do allow(Gitlab::LDAP::Config).to receive(:enabled?).and_return(true) allow_any_instance_of(Gitlab::LDAP::Config). @@ -83,4 +83,337 @@ describe Gitlab::LDAP::Access do end end end + + describe :update_permissions do + subject { access.update_permissions } + + it "syncs ssh keys if enabled by configuration" do + allow(access).to receive_messages(sync_ssh_keys?: 'sshpublickey') + expect(access).to receive(:update_ssh_keys).once + + subject + end + + it "does update group permissions with a group base configured" do + allow(access).to receive_messages(group_base: 'my-group-base') + expect(access).to receive(:update_ldap_group_links) + + subject + end + + it "does not update group permissions without a group base configured" do + allow(access).to receive_messages(group_base: '') + expect(access).not_to receive(:update_ldap_group_links) + + subject + end + + it "does update admin group permissions if admin group is configured" do + allow(access).to receive_messages(admin_group: 'my-admin-group', update_ldap_group_links: nil) + expect(access).to receive(:update_admin_status) + + subject + end + end + + describe :update_ssh_keys do + let(:ssh_key) { "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCrSQHff6a1rMqBdHFt+FwIbytMZ+hJKN3KLkTtOWtSvNIriGhnTdn4rs+tjD/w+z+revytyWnMDM9dS7J8vQi006B16+hc9Xf82crqRoPRDnBytgAFFQY1G/55ql2zdfsC5yvpDOFzuwIJq5dNGsojS82t6HNmmKPq130fzsenFnj5v1pl3OJvk513oduUyKiZBGTroWTn7H/eOPtu7s9MD7pAdEjqYKFLeaKmyidiLmLqQlCRj3Tl2U9oyFg4PYNc0bL5FZJ/Z6t0Ds3i/a2RanQiKxrvgu3GSnUKMx7WIX373baL4jeM7cprRGiOY/1NcS+1cAjfJ8oaxQF/1dYj" } + let(:ssh_key_attribute_name) { 'altSecurityIdentities' } + let(:entry) do + Net::LDAP::Entry.from_single_ldif_string("dn: cn=foo, dc=bar, dc=com\n#{ssh_key_attribute_name}: SSHKey:#{ssh_key}\n#{ssh_key_attribute_name}: KerberosKey:bogus") + end + + before do + allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(sync_ssh_keys: ssh_key_attribute_name) + allow(access).to receive_messages(sync_ssh_keys?: true) + end + + it "should add a SSH key if it is in LDAP but not in gitlab" do + allow_any_instance_of(Gitlab::LDAP::Adapter).to receive(:user) { Gitlab::LDAP::Person.new(entry, 'ldapmain') } + + expect{ access.update_ssh_keys }.to change(user.keys, :count).from(0).to(1) + end + + it "should add a SSH key and give it a proper name" do + allow_any_instance_of(Gitlab::LDAP::Adapter).to receive(:user) { Gitlab::LDAP::Person.new(entry, 'ldapmain') } + + access.update_ssh_keys + expect(user.keys.last.title).to match(/LDAP/) + expect(user.keys.last.title).to match(/#{access.ldap_config.sync_ssh_keys}/) + end + + it "should not add a SSH key if it is invalid" do + entry = Net::LDAP::Entry.from_single_ldif_string("dn: cn=foo, dc=bar, dc=com\n#{ssh_key_attribute_name}: I am not a valid key") + allow_any_instance_of(Gitlab::LDAP::Adapter).to receive(:user) { Gitlab::LDAP::Person.new(entry, 'ldapmain') } + + expect{ access.update_ssh_keys }.not_to change(user.keys, :count) + end + + context 'user has at least one LDAPKey' do + before { user.keys.ldap.create key: ssh_key, title: 'to be removed' } + + it "should remove a SSH key if it is no longer in LDAP" do + entry = Net::LDAP::Entry.from_single_ldif_string("dn: cn=foo, dc=bar, dc=com\n#{ssh_key_attribute_name}:\n") + allow_any_instance_of(Gitlab::LDAP::Adapter).to receive(:user) { Gitlab::LDAP::Person.new(entry, 'ldapmain') } + + expect{ access.update_ssh_keys }.to change(user.keys, :count).from(1).to(0) + end + + it "should remove a SSH key if the ldap attribute was removed" do + entry = Net::LDAP::Entry.from_single_ldif_string("dn: cn=foo, dc=bar, dc=com") + allow_any_instance_of(Gitlab::LDAP::Adapter).to receive(:user) { Gitlab::LDAP::Person.new(entry, 'ldapmain') } + + expect{ access.update_ssh_keys }.to change(user.keys, :count).from(1).to(0) + end + end + end + + describe :update_user_email do + let(:entry) { Net::LDAP::Entry.new } + + before do + allow(access).to receive_messages(ldap_user: Gitlab::LDAP::Person.new(entry, user.ldap_identity.provider)) + end + + it "should not update email if email attribute is not set" do + expect{ access.update_email }.not_to change(user, :email) + end + + it "should not update the email if the user has the same email in GitLab and in LDAP" do + entry['mail'] = [user.email] + expect{ access.update_email }.not_to change(user, :email) + end + + it "should not update the email if the user has the same email GitLab and in LDAP, but with upper case in LDAP" do + entry['mail'] = [user.email.upcase] + expect{ access.update_email }.not_to change(user, :email) + end + + it "should update the email if the user email is different" do + entry['mail'] = ["new_email@example.com"] + expect{ access.update_email }.to change(user, :email) + end + end + + + describe :update_admin_status do + before do + allow(access).to receive_messages(admin_group: "GLAdmins") + ldap_user_entry = Net::LDAP::Entry.new + allow_any_instance_of(Gitlab::LDAP::Adapter).to receive(:user) { Gitlab::LDAP::Person.new(ldap_user_entry, user.ldap_identity.provider) } + allow_any_instance_of(Gitlab::LDAP::Person).to receive(:uid) { 'admin2' } + end + + it "should give admin privileges to an User" do + admin_group = Net::LDAP::Entry.from_single_ldif_string( +%Q{dn: cn=#{access.admin_group},ou=groups,dc=bar,dc=com +cn: #{access.admin_group} +description: GitLab admins +gidnumber: 42 +memberuid: admin1 +memberuid: admin2 +memberuid: admin3 +objectclass: top +objectclass: posixGroup +}) + allow_any_instance_of(Gitlab::LDAP::Adapter).to receive(:group) { Gitlab::LDAP::Group.new(admin_group) } + + expect{ access.update_admin_status }.to change(user, :admin?).to(true) + end + + it "should remove admin privileges from an User" do + user.update_attribute(:admin, true) + admin_group = Net::LDAP::Entry.from_single_ldif_string( +%Q{dn: cn=#{access.admin_group},ou=groups,dc=bar,dc=com +cn: #{access.admin_group} +description: GitLab admins +gidnumber: 42 +memberuid: admin1 +memberuid: admin3 +objectclass: top +objectclass: posixGroup +}) + allow_any_instance_of(Gitlab::LDAP::Adapter).to receive(:group) { Gitlab::LDAP::Group.new(admin_group) } + expect{ access.update_admin_status }.to change(user, :admin?).to(false) + end + end + + + describe :update_ldap_group_links do + let(:cns_with_access) { %w(ldap-group1 ldap-group2) } + let(:gitlab_group_1) { create :group } + let(:gitlab_group_2) { create :group } + + before do + allow(access).to receive_messages(cns_with_access: cns_with_access) + end + + context "non existing access for group-1, allowed via ldap-group1 as MASTER" do + before do + gitlab_group_1.ldap_group_links.create({ + cn: 'ldap-group1', group_access: Gitlab::Access::MASTER, provider: 'ldapmain' }) + end + + it "gives the user master access for group 1" do + access.update_ldap_group_links + expect( gitlab_group_1.has_master?(user) ).to be_truthy + end + + it "doesn't send a notification email" do + expect { access.update_ldap_group_links }.not_to \ + change { ActionMailer::Base.deliveries } + end + end + + context "existing access as guest for group-1, allowed via ldap-group1 as DEVELOPER" do + before do + gitlab_group_1.group_members.guests.create(user_id: user.id) + gitlab_group_1.ldap_group_links.create({ + cn: 'ldap-group1', group_access: Gitlab::Access::MASTER, provider: 'ldapmain' }) + end + + it "upgrades the users access to master for group 1" do + expect { access.update_ldap_group_links }.to \ + change{ gitlab_group_1.has_master?(user) }.from(false).to(true) + end + + it "doesn't send a notification email" do + expect { access.update_ldap_group_links }.not_to \ + change { ActionMailer::Base.deliveries } + end + end + + context "existing access as MASTER for group-1, allowed via ldap-group1 as DEVELOPER" do + before do + gitlab_group_1.group_members.masters.create(user_id: user.id) + gitlab_group_1.ldap_group_links.create({ + cn: 'ldap-group1', group_access: Gitlab::Access::DEVELOPER, provider: 'ldapmain' }) + end + + it "keeps the users master access for group 1" do + expect { access.update_ldap_group_links }.not_to \ + change{ gitlab_group_1.has_master?(user) } + end + + it "doesn't send a notification email" do + expect { access.update_ldap_group_links }.not_to \ + change { ActionMailer::Base.deliveries } + end + end + + context "existing access as master for group-1, not allowed" do + before do + gitlab_group_1.group_members.masters.create(user_id: user.id) + gitlab_group_1.ldap_group_links.create(cn: 'ldap-group1', group_access: Gitlab::Access::MASTER, provider: 'ldapmain') + allow(access).to receive_messages(cns_with_access: ['ldap-group2']) + end + + it "removes user from gitlab_group_1" do + expect { access.update_ldap_group_links }.to \ + change{ gitlab_group_1.members.where(user_id: user).any? }.from(true).to(false) + end + end + + context "existing access as owner for group-1 with no other owner, not allowed" do + before do + gitlab_group_1.group_members.owners.create(user_id: user.id) + gitlab_group_1.ldap_group_links.create(cn: 'ldap-group1', group_access: Gitlab::Access::OWNER, provider: 'ldapmain') + allow(access).to receive_messages(cns_with_access: ['ldap-group2']) + end + + it "does not remove the user from gitlab_group_1 since it's the last owner" do + expect { access.update_ldap_group_links }.not_to \ + change{ gitlab_group_1.has_owner?(user) } + end + end + + context "existing access as owner for group-1 while other owners present, not allowed" do + before do + owner2 = create(:user) # a 2nd owner + gitlab_group_1.group_members.owners.create([{ user_id: user.id }, { user_id: owner2.id }]) + gitlab_group_1.ldap_group_links.create(cn: 'ldap-group1', group_access: Gitlab::Access::OWNER, provider: 'ldapmain') + allow(access).to receive_messages(cns_with_access: ['ldap-group2']) + end + + it "removes user from gitlab_group_1" do + expect { access.update_ldap_group_links }.to \ + change{ gitlab_group_1.members.where(user_id: user).any? }.from(true).to(false) + end + end + end + + describe 'ldap_groups' do + let(:ldap_group_1) do + Net::LDAP::Entry.from_single_ldif_string( +%Q{dn: cn=#{access.ldap_config.admin_group},ou=groups,dc=bar,dc=com +cn: #{access.ldap_config.admin_group} +description: GitLab group 1 +gidnumber: 42 +memberuid: user1 +memberuid: user2 +objectclass: top +objectclass: posixGroup +}) + end + + it "returns an interator of LDAP Groups" do + ::LdapGroupLink.create({ + cn: 'example', group_access: Gitlab::Access::DEVELOPER, group_id: 42, provider: 'ldapmain' }) + allow_any_instance_of(Gitlab::LDAP::Adapter).to receive(:group) { Gitlab::LDAP::Group.new(ldap_group_1) } + + expect(access.ldap_groups.first).to be_a Gitlab::LDAP::Group + end + + it "only returns found ldap groups" do + ::LdapGroupLink.create cn: 'example', group_access: Gitlab::Access::DEVELOPER, group_id: 42 + allow(Gitlab::LDAP::Group).to receive_messages(find_by_cn: nil) # group not found + + expect(access.ldap_groups).to be_empty + end + end + + describe :cns_with_access do + let(:ldap_group_response_1) do + Net::LDAP::Entry.from_single_ldif_string( +%Q{dn: cn=group1,ou=groups,dc=bar,dc=com +cn: group1 +description: GitLab group 1 +gidnumber: 21 +uniquemember: #{ldap_user.dn.downcase} +uniquemember: uid=user2,ou=people,dc=example +objectclass: top +objectclass: posixGroup +}) + end + let(:ldap_group_response_2) do + Net::LDAP::Entry.from_single_ldif_string( +%Q{dn: cn=group2,ou=groups,dc=bar,dc=com +cn: group2 +description: GitLab group 2 +gidnumber: 42 +memberuid: user3 +memberuid: user4 +objectclass: top +objectclass: posixGroup +}) + end + let(:ldap_groups) do + [ + Gitlab::LDAP::Group.new(ldap_group_response_1), + Gitlab::LDAP::Group.new(ldap_group_response_2) + ] + end + let(:ldap_user) { Gitlab::LDAP::Person.new(Net::LDAP::Entry.new, user.ldap_identity.provider) } + + before do + allow(access).to receive_messages(ldap_user: ldap_user) + allow(ldap_user).to receive(:uid) { 'user1' } + allow(ldap_user).to receive(:dn) { 'uid=user1,ou=People,dc=example' } + end + + it "only returns ldap cns to which the user has access" do + allow(access).to receive_messages(ldap_groups: ldap_groups) + expect(access.cns_with_access).to eql ['group1'] + end + end end diff --git a/spec/lib/gitlab/ldap/config_spec.rb b/spec/lib/gitlab/ldap/config_spec.rb index 3548d647c84..4d3fa919542 100644 --- a/spec/lib/gitlab/ldap/config_spec.rb +++ b/spec/lib/gitlab/ldap/config_spec.rb @@ -1,12 +1,12 @@ require 'spec_helper' describe Gitlab::LDAP::Config do - let(:config) { Gitlab::LDAP::Config.new provider } + let(:config) { described_class.new provider } let(:provider) { 'ldapmain' } describe '#initalize' do it 'requires a provider' do - expect{ Gitlab::LDAP::Config.new }.to raise_error ArgumentError + expect{ described_class.new }.to raise_error ArgumentError end it "works" do @@ -14,7 +14,8 @@ describe Gitlab::LDAP::Config do end it "raises an error if a unknow provider is used" do - expect{ Gitlab::LDAP::Config.new 'unknown' }.to raise_error(RuntimeError) + expect { described_class.new 'unknown' }. + to raise_error(described_class::InvalidProvider) end end end diff --git a/spec/lib/gitlab/ldap/person_spec.rb b/spec/lib/gitlab/ldap/person_spec.rb new file mode 100644 index 00000000000..0ae621f476e --- /dev/null +++ b/spec/lib/gitlab/ldap/person_spec.rb @@ -0,0 +1,75 @@ +require "spec_helper" + +describe Gitlab::LDAP::Person do + + describe "#ssh_keys" do + + let(:ssh_key) { "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCrSQHff6a1rMqBdHFt+FwIbytMZ+hJKN3KLkTtOWtSvNIriGhnTdn4rs+tjD/w+z+revytyWnMDM9dS7J8vQi006B16+hc9Xf82crqRoPRDnBytgAFFQY1G/55ql2zdfsC5yvpDOFzuwIJq5dNGsojS82t6HNmmKPq130fzsenFnj5v1pl3OJvk513oduUyKiZBGTroWTn7H/eOPtu7s9MD7pAdEjqYKFLeaKmyidiLmLqQlCRj3Tl2U9oyFg4PYNc0bL5FZJ/Z6t0Ds3i/a2RanQiKxrvgu3GSnUKMx7WIX373baL4jeM7cprRGiOY/1NcS+1cAjfJ8oaxQF/1dYj" } + let(:ssh_key_attribute_name) { 'altSecurityIdentities' } + let(:entry) do + Net::LDAP::Entry.from_single_ldif_string("dn: cn=foo, dc=bar, dc=com\n#{keys}") + end + + subject { Gitlab::LDAP::Person.new(entry, 'ldapmain') } + + before do + allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(sync_ssh_keys: ssh_key_attribute_name) + end + + context "when the SSH key is literal" do + + let(:keys) { "#{ssh_key_attribute_name}: #{ssh_key}" } + + it "includes the SSH key" do + expect(subject.ssh_keys).to include(ssh_key) + end + end + + context "when the SSH key is prefixed" do + + let(:keys) { "#{ssh_key_attribute_name}: SSHKey:#{ssh_key}" } + + it "includes the SSH key" do + expect(subject.ssh_keys).to include(ssh_key) + end + end + + context "when the SSH key is suffixed" do + + let(:keys) { "#{ssh_key_attribute_name}: #{ssh_key} (SSH key)" } + + it "includes the SSH key" do + expect(subject.ssh_keys).to include(ssh_key) + end + end + + context "when the SSH key is followed by a newline" do + + let(:keys) { "#{ssh_key_attribute_name}: #{ssh_key}\n" } + + it "includes the SSH key" do + expect(subject.ssh_keys).to include(ssh_key) + end + end + + context "when the key is not an SSH key" do + + let(:keys) { "#{ssh_key_attribute_name}: KerberosKey:bogus" } + + it "is empty" do + expect(subject.ssh_keys).to be_empty + end + end + + context "when there are multiple keys" do + + let(:keys) { "#{ssh_key_attribute_name}: #{ssh_key}\n#{ssh_key_attribute_name}: KerberosKey:bogus\n#{ssh_key_attribute_name}: ssh-rsa keykeykey" } + + it "includes both SSH keys" do + expect(subject.ssh_keys).to include(ssh_key) + expect(subject.ssh_keys).to include("ssh-rsa keykeykey") + expect(subject.ssh_keys).not_to include("KerberosKey:bogus") + end + end + end +end diff --git a/spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb b/spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb index d8c2970b6bd..e1c70729bcb 100644 --- a/spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb @@ -74,6 +74,14 @@ module Gitlab::Markdown expect(link).to eq helper.url_for_issue("#{reference}", project, only_path: true) end + + it 'adds to the results hash' do + ext = JiraIssue.new(reference, project) + + result = reference_pipeline_result("Issue #{reference}") + expect(result[:references][:external_issue]).not_to be_empty + expect(result[:references][:external_issue]).to eq [ext] + end end end end diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb index ad84d2274e8..ac23b0091b6 100644 --- a/spec/lib/gitlab/reference_extractor_spec.rb +++ b/spec/lib/gitlab/reference_extractor_spec.rb @@ -97,6 +97,16 @@ describe Gitlab::ReferenceExtractor do expect(extracted.first.commit_to).to eq commit end + context 'with an external issue tracker' do + let(:project) { create(:jira_project) } + subject { described_class.new(project, project.creator) } + + it 'returns JIRA issues for a JIRA-integrated project' do + subject.analyze('JIRA-123 and FOOBAR-4567') + expect(subject.issues).to eq [JiraIssue.new('JIRA-123', project), JiraIssue.new('FOOBAR-4567', project)] + end + end + context 'with a project with an underscore' do let(:other_project) { create(:project, path: 'test_project') } let(:issue) { create(:issue, project: other_project) } diff --git a/spec/lib/gitlab/upgrader_spec.rb b/spec/lib/gitlab/upgrader_spec.rb index 8df84665e16..af523c6c9d9 100644 --- a/spec/lib/gitlab/upgrader_spec.rb +++ b/spec/lib/gitlab/upgrader_spec.rb @@ -16,23 +16,18 @@ describe Gitlab::Upgrader do end describe 'latest_version_raw' do - it 'should be latest version for GitLab 5' do - allow(upgrader).to receive(:current_version_raw).and_return("5.3.0") - expect(upgrader.latest_version_raw).to eq("v5.4.2") - end - it 'should get the latest version from tags' do allow(upgrader).to receive(:fetch_git_tags).and_return([ - '6f0733310546402c15d3ae6128a95052f6c8ea96 refs/tags/v7.1.1', - 'facfec4b242ce151af224e20715d58e628aa5e74 refs/tags/v7.1.1^{}', - 'f7068d99c79cf79befbd388030c051bb4b5e86d4 refs/tags/v7.10.4', - '337225a4fcfa9674e2528cb6d41c46556bba9dfa refs/tags/v7.10.4^{}', - '880e0ba0adbed95d087f61a9a17515e518fc6440 refs/tags/v7.11.1', - '6584346b604f981f00af8011cd95472b2776d912 refs/tags/v7.11.1^{}', - '43af3e65a486a9237f29f56d96c3b3da59c24ae0 refs/tags/v7.11.2', - 'dac18e7728013a77410e926a1e64225703754a2d refs/tags/v7.11.2^{}', - '0bf21fd4b46c980c26fd8c90a14b86a4d90cc950 refs/tags/v7.9.4', - 'b10de29edbaff7219547dc506cb1468ee35065c3 refs/tags/v7.9.4^{}']) + '6f0733310546402c15d3ae6128a95052f6c8ea96 refs/tags/v7.1.1-ee', + 'facfec4b242ce151af224e20715d58e628aa5e74 refs/tags/v7.1.1-ee^{}', + 'f7068d99c79cf79befbd388030c051bb4b5e86d4 refs/tags/v7.10.4-ee', + '337225a4fcfa9674e2528cb6d41c46556bba9dfa refs/tags/v7.10.4-ee^{}', + '880e0ba0adbed95d087f61a9a17515e518fc6440 refs/tags/v7.11.1-ee', + '6584346b604f981f00af8011cd95472b2776d912 refs/tags/v7.11.1-ee^{}', + '43af3e65a486a9237f29f56d96c3b3da59c24ae0 refs/tags/v7.11.2-ee', + 'dac18e7728013a77410e926a1e64225703754a2d refs/tags/v7.11.2-ee^{}', + '0bf21fd4b46c980c26fd8c90a14b86a4d90cc950 refs/tags/v7.9.4-ee', + 'b10de29edbaff7219547dc506cb1468ee35065c3 refs/tags/v7.9.4-ee^{}']) expect(upgrader.latest_version_raw).to eq("v7.11.2") end end diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 47863d54579..2b36fc42e65 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -276,6 +276,7 @@ describe Notify do let(:merge_author) { create(:user) } let(:merge_request) { create(:merge_request, author: current_user, assignee: assignee, source_project: project, target_project: project) } let(:merge_request_with_description) { create(:merge_request, author: current_user, assignee: assignee, source_project: project, target_project: project, description: FFaker::Lorem.sentence) } + let(:merge_request_with_approver) { create(:merge_request_with_approver, author: current_user, assignee: assignee, source_project: project, target_project: project) } describe 'that are new' do subject { Notify.new_merge_request_email(merge_request.assignee_id, merge_request.id) } @@ -304,8 +305,26 @@ describe Notify do end end + describe "that are new with approver" do + subject do + Notify.new_merge_request_email( + merge_request_with_approver.assignee_id, + merge_request_with_approver.id + ) + end + + it "contains the approvers list" do + is_expected.to have_body_text /#{merge_request_with_approver.approvers.first.user.name}/ + end + end + describe 'that are new with a description' do - subject { Notify.new_merge_request_email(merge_request_with_description.assignee_id, merge_request_with_description.id) } + subject do + Notify.new_merge_request_email( + merge_request_with_description.assignee_id, + merge_request_with_description.id + ) + end it 'contains the description' do is_expected.to have_body_text /#{merge_request_with_description.description}/ @@ -800,4 +819,30 @@ describe Notify do is_expected.to have_body_text /#{diff_path}/ end end + + describe 'admin notification' do + let(:example_site_path) { root_path } + let(:user) { create(:user) } + + subject { @email = Notify.send_admin_notification(user.id, 'Admin announcement','Text') } + + it 'is sent as the author' do + sender = subject.header[:from].addrs[0] + expect(sender.display_name).to eq("GitLab") + expect(sender.address).to eq(gitlab_sender) + end + + it 'is sent to recipient' do + should deliver_to user.email + end + + it 'has the correct subject' do + should have_subject 'Admin announcement' + end + + it 'includes unsubscribe link' do + unsubscribe_link = "http://localhost/unsubscribes/#{Base64.urlsafe_encode64(user.email)}" + should have_body_text(unsubscribe_link) + end + end end diff --git a/spec/models/appearance_spec.rb b/spec/models/appearance_spec.rb new file mode 100644 index 00000000000..3ed74e24754 --- /dev/null +++ b/spec/models/appearance_spec.rb @@ -0,0 +1,7 @@ +require 'spec_helper' + +describe Appearance do + subject { create(:appearance) } + + it { should be_valid } +end diff --git a/spec/models/approval_spec.rb b/spec/models/approval_spec.rb new file mode 100644 index 00000000000..ecd5edb454e --- /dev/null +++ b/spec/models/approval_spec.rb @@ -0,0 +1,7 @@ +require 'spec_helper' + +describe Approval do + subject { create(:approval) } + + it { should be_valid } +end diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb index 6179882e935..6653621a83e 100644 --- a/spec/models/concerns/mentionable_spec.rb +++ b/spec/models/concerns/mentionable_spec.rb @@ -1,5 +1,18 @@ require 'spec_helper' +describe Mentionable do + include Mentionable + + describe :references do + let(:project) { create(:project) } + + it 'excludes JIRA references' do + allow(project).to receive_messages(jira_tracker?: true) + expect(referenced_mentionables(project, 'JIRA-123')).to be_empty + end + end +end + describe Issue, "Mentionable" do describe '#mentioned_users' do let!(:user) { create(:user, username: 'stranger') } diff --git a/spec/models/git_hook_spec.rb b/spec/models/git_hook_spec.rb new file mode 100644 index 00000000000..8c14de15ba6 --- /dev/null +++ b/spec/models/git_hook_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe GitHook do + describe "Associations" do + it { should belong_to(:project) } + end + + describe "Validation" do + it { should validate_presence_of(:project) } + end +end diff --git a/spec/models/historical_data_spec.rb b/spec/models/historical_data_spec.rb new file mode 100644 index 00000000000..015838ec01e --- /dev/null +++ b/spec/models/historical_data_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' + +describe HistoricalData do + before do + (1..12).each do |i| + HistoricalData.create!(date: Date.new(2014, i, 1), active_user_count: i * 100) + end + end + + describe ".during" do + it "returns the historical data during the specified period" do + expect(HistoricalData.during(Date.new(2014, 1, 1)..Date.new(2014, 12, 31)).average(:active_user_count)).to eq(650) + end + end + + describe ".up_until" do + it "returns the historical data up until the specified date" do + expect(HistoricalData.up_until(Date.new(2014, 6, 1)).average(:active_user_count)).to eq(350) + end + end + + describe ".at" do + it "returns the historical data at the specified date" do + expect(HistoricalData.at(Date.new(2014, 8, 1)).active_user_count).to eq(800) + end + end + + describe ".track!" do + before do + allow(User).to receive(:active).and_return([1,2,3,4,5]) + end + + it "creates a new historical data record" do + HistoricalData.track! + + data = HistoricalData.last + expect(data.date).to eq(Date.today) + expect(data.active_user_count).to eq(5) + end + end +end diff --git a/spec/models/hooks/group_hook_spec.rb b/spec/models/hooks/group_hook_spec.rb new file mode 100644 index 00000000000..c668a14f82d --- /dev/null +++ b/spec/models/hooks/group_hook_spec.rb @@ -0,0 +1,24 @@ +# == Schema Information +# +# Table name: web_hooks +# +# id :integer not null, primary key +# url :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# type :string(255) default("ProjectHook") +# service_id :integer +# push_events :boolean default(TRUE), not null +# issues_events :boolean default(FALSE), not null +# merge_requests_events :boolean default(FALSE), not null +# tag_push_events :boolean default(FALSE) +# + +require 'spec_helper' + +describe GroupHook do + describe "Associations" do + it { is_expected.to belong_to :group } + end +end diff --git a/spec/models/jira_issue_spec.rb b/spec/models/jira_issue_spec.rb new file mode 100644 index 00000000000..1634265b439 --- /dev/null +++ b/spec/models/jira_issue_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +describe JiraIssue do + let(:project) { create(:project) } + subject { JiraIssue.new('JIRA-123', project) } + + describe 'id' do + subject { super().id } + it { is_expected.to eq('JIRA-123') } + end + + describe 'iid' do + subject { super().iid } + it { is_expected.to eq('JIRA-123') } + end + + describe 'to_s' do + subject { super().to_s } + it { is_expected.to eq('JIRA-123') } + end + + describe :== do + specify { expect(subject).to eq(JiraIssue.new('JIRA-123', project)) } + specify { expect(subject).not_to eq(JiraIssue.new('JIRA-124', project)) } + + it 'only compares with JiraIssues' do + expect(subject).not_to eq('JIRA-123') + end + end +end diff --git a/spec/models/ldap_group_link_spec.rb b/spec/models/ldap_group_link_spec.rb new file mode 100644 index 00000000000..d2b1af20238 --- /dev/null +++ b/spec/models/ldap_group_link_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +describe LdapGroupLink do + let(:klass) { LdapGroupLink } + let(:ldap_group_link) { build :ldap_group_link } + + describe "validation" do + describe "cn" do + it "validates uniquiness based on group_id and provider" do + create(:ldap_group_link, cn: 'group1', group_id: 1, provider: 'ldapmain') + + group_link = build(:ldap_group_link, + cn: 'group1', group_id: 1, provider: 'ldapmain') + expect(group_link).not_to be_valid + + group_link.group_id = 2 + expect(group_link).to be_valid + + group_link.group_id = 1 + group_link.provider = 'ldapalt' + expect(group_link).to be_valid + end + end + + describe :provider do + it "shows the set value" do + ldap_group_link.provider = '1235' + expect( ldap_group_link.provider ).to eql '1235' + end + + it "defaults to the first ldap server if empty" do + expect( klass.new.provider ).to eql Gitlab::LDAP::Config.providers.first + end + end + end +end diff --git a/spec/models/license_spec.rb b/spec/models/license_spec.rb new file mode 100644 index 00000000000..7583d4ee412 --- /dev/null +++ b/spec/models/license_spec.rb @@ -0,0 +1,204 @@ +require "spec_helper" + +describe License do + let(:gl_license) { build(:gitlab_license) } + let(:license) { build(:license, data: gl_license.export) } + + describe "Validation" do + describe "Valid license" do + context "when the license is provided" do + it "is valid" do + expect(license).to be_valid + end + end + + context "when no license is provided" do + before do + license.data = nil + end + + it "is invalid" do + expect(license).not_to be_valid + end + end + end + + describe "Historical active user count" do + let(:active_user_count) { User.active.count + 10 } + let(:date) { License.current.starts_at } + let!(:historical_data) { HistoricalData.create!(date: date, active_user_count: active_user_count) } + + context "when there is no active user count restriction" do + it "is valid" do + expect(license).to be_valid + end + end + + context "when the active user count restriction is exceeded" do + before do + gl_license.restrictions = { active_user_count: active_user_count - 1 } + end + + context "when the license started" do + it "is invalid" do + expect(license).not_to be_valid + end + end + + context "after the license started" do + let(:date) { Date.today } + + it "is valid" do + expect(license).to be_valid + end + end + + context "in the year before the license started" do + let(:date) { License.current.starts_at - 6.months } + + it "is invalid" do + expect(license).not_to be_valid + end + end + + context "earlier than a year before the license started" do + let(:date) { License.current.starts_at - 2.years } + + it "is valid" do + expect(license).to be_valid + end + end + end + + context "when the active user count restriction is not exceeded" do + before do + gl_license.restrictions = { active_user_count: active_user_count + 1 } + end + + it "is valid" do + expect(license).to be_valid + end + end + end + + describe "Not expired" do + context "when the license doesn't expire" do + it "is valid" do + expect(license).to be_valid + end + end + + context "when the license has expired" do + before do + gl_license.expires_at = Date.yesterday + end + + it "is valid" do + expect(license).not_to be_valid + end + + end + + context "when the license has yet to expire" do + before do + gl_license.expires_at = Date.tomorrow + end + + it "is valid" do + expect(license).to be_valid + end + end + end + end + + describe "Class methods" do + let!(:license) { License.last } + + before do + License.reset_current + allow(License).to receive(:last).and_return(license) + end + + describe ".current" do + context "when there is no license" do + let!(:license) { nil } + + it "returns nil" do + expect(License.current).to be_nil + end + end + + context "when the license is invalid" do + before do + allow(license).to receive(:valid?).and_return(false) + end + + it "returns nil" do + expect(License.current).to be_nil + end + end + + context "when the license is valid" do + it "returns the license" do + expect(License.current) + end + end + end + + describe ".block_changes?" do + context "when there is no current license" do + before do + allow(License).to receive(:current).and_return(nil) + end + + it "returns true" do + expect(License.block_changes?).to be_truthy + end + end + + context "when the current license is set to block changes" do + before do + allow(license).to receive(:block_changes?).and_return(true) + end + + it "returns true" do + expect(License.block_changes?).to be_truthy + end + end + + context "when the current license doesn't block changes" do + it "returns false" do + expect(License.block_changes?).to be_falsey + end + end + end + end + + describe "#license" do + context "when no data is provided" do + before do + license.data = nil + end + + it "returns nil" do + expect(license.license).to be_nil + end + end + + context "when corrupt license data is provided" do + before do + license.data = "whatever" + end + + it "returns nil" do + expect(license.license).to be_nil + end + end + + context "when valid license data is provided" do + it "returns the license" do + expect(license.license).not_to be_nil + end + end + end +end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 90af75ff0e3..b59916a1283 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -144,6 +144,17 @@ describe MergeRequest do expect(subject.closes_issues).to include(issue2) end + + context 'for a project with JIRA integration' do + let(:issue0) { JiraIssue.new('JIRA-123', subject.project) } + let(:issue1) { JiraIssue.new('FOOBAR-4567', subject.project) } + + it 'returns sorted JiraIssues' do + allow(subject.project).to receive_messages(default_branch: subject.target_branch) + + expect(subject.closes_issues).to eq([issue1, issue0]) + end + end end describe "#work_in_progress?" do @@ -172,6 +183,30 @@ describe MergeRequest do end end + describe "approvers_left" do + let(:merge_request) {create :merge_request} + + it "returns correct value" do + user = create(:user) + user1 = create(:user) + merge_request.approvers.create(user_id: user.id) + merge_request.approvers.create(user_id: user1.id) + merge_request.approvals.create(user_id: user1.id) + + expect(merge_request.approvers_left).to eq [user] + end + end + + describe "approvals_required" do + let(:merge_request) {create :merge_request} + + it "takes approvals_before_merge" do + merge_request.target_project.update(approvals_before_merge: 2) + + expect(merge_request.approvals_required).to eq 2 + end + end + describe "#hook_attrs" do it "has all the required keys" do attrs = subject.hook_attrs @@ -183,6 +218,12 @@ describe MergeRequest do end end + describe "#source_sha_parent" do + it "returns the sha of the parent commit of the first commit in the MR" do + expect(subject.source_sha_parent).to eq("ae73cb07c9eeaf35924a10f713b364d32b2dd34f") + end + end + it_behaves_like 'an editable mentionable' do subject { create(:merge_request) } diff --git a/spec/models/project_group_link_spec.rb b/spec/models/project_group_link_spec.rb new file mode 100644 index 00000000000..2fa6715fcaf --- /dev/null +++ b/spec/models/project_group_link_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' + +describe ProjectGroupLink do + describe "Associations" do + it { should belong_to(:group) } + it { should belong_to(:project) } + end + + describe "Validation" do + let!(:project_group_link) { create(:project_group_link) } + + it { should validate_presence_of(:project_id) } + it { should validate_uniqueness_of(:group_id).scoped_to(:project_id).with_message(/already shared/) } + it { should validate_presence_of(:group_id) } + it { should validate_presence_of(:group_access) } + end +end diff --git a/spec/models/project_services/jenkins_service_spec.rb b/spec/models/project_services/jenkins_service_spec.rb new file mode 100644 index 00000000000..756dbfda7df --- /dev/null +++ b/spec/models/project_services/jenkins_service_spec.rb @@ -0,0 +1,119 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# template :boolean default(FALSE) +# push_events :boolean default(TRUE) +# issues_events :boolean default(TRUE) +# merge_requests_events :boolean default(TRUE) +# tag_push_events :boolean default(TRUE) +# + +require 'spec_helper' + +describe JenkinsService do + describe "Associations" do + it { is_expected.to belong_to :project } + it { is_expected.to have_one :service_hook } + end + + describe 'commits methods' do + def status_body_for_icon(state) + <<eos + <h1 class="build-caption page-headline"><img style="width: 48px; height: 48px; " alt="Success" class="icon-#{state} icon-xlg" src="/static/855d7c3c/images/48x48/#{state}" tooltip="Success" title="Success"> + Build #188 + (Oct 15, 2014 9:45:21 PM) + </h1> +eos + end + + describe :commit_status do + before do + @service = JenkinsService.new + allow(@service).to receive_messages( + service_hook: true, + project_url: 'http://jenkins.gitlab.org/job/2', + multiproject_enabled: '0', + pass_unstable: '0', + token: 'verySecret' + ) + end + + statuses = { 'blue.png' => 'success', 'yellow.png' => 'failed', 'red.png' => 'failed', 'aborted.png' => 'failed', 'blue-anime.gif' => 'running', 'grey.png' => 'pending' } + statuses.each do |icon, state| + it "should have a status of #{state} when the icon #{icon} exists." do + stub_request(:get, "http://jenkins.gitlab.org/job/2/scm/bySHA1/2ab7834c").to_return(status: 200, body: status_body_for_icon(icon), headers: {}) + expect(@service.commit_status("2ab7834c", 'master')).to eq(state) + end + end + end + + describe 'commit status with passing unstable' do + before do + @service = JenkinsService.new + allow(@service).to receive_messages( + service_hook: true, + project_url: 'http://jenkins.gitlab.org/job/2', + multiproject_enabled: '0', + pass_unstable: '1', + token: 'verySecret' + ) + end + + it "should have a status of success when the icon yellow exists." do + stub_request(:get, "http://jenkins.gitlab.org/job/2/scm/bySHA1/2ab7834c").to_return(status: 200, body: status_body_for_icon('yellow.png'), headers: {}) + expect(@service.commit_status("2ab7834c", 'master')).to eq('success') + end + end + + describe 'multiproject enabled' do + let!(:project) { create(:project) } + before do + @service = JenkinsService.new + allow(@service).to receive_messages( + service_hook: true, + project_url: 'http://jenkins.gitlab.org/job/2', + multiproject_enabled: '1', + token: 'verySecret', + project: project + ) + end + + describe :build_page do + it { expect(@service.build_page("2ab7834c", 'master')).to eq("http://jenkins.gitlab.org/job/#{project.name}_master/scm/bySHA1/2ab7834c") } + end + + describe :build_page_with_branch do + it { expect(@service.build_page("2ab7834c", 'test_branch')).to eq("http://jenkins.gitlab.org/job/#{project.name}_test_branch/scm/bySHA1/2ab7834c") } + end + end + + describe 'multiproject disabled' do + before do + @service = JenkinsService.new + allow(@service).to receive_messages( + service_hook: true, + project_url: 'http://jenkins.gitlab.org/job/2', + multiproject_enabled: '0', + token: 'verySecret' + ) + end + + describe :build_page do + it { expect(@service.build_page("2ab7834c", 'master')).to eq("http://jenkins.gitlab.org/job/2/scm/bySHA1/2ab7834c") } + end + + describe :build_page_with_branch do + it { expect(@service.build_page("2ab7834c", 'test_branch')).to eq("http://jenkins.gitlab.org/job/2/scm/bySHA1/2ab7834c") } + end + end + end +end diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb index ddd2cce212c..846f61783dc 100644 --- a/spec/models/project_services/jira_service_spec.rb +++ b/spec/models/project_services/jira_service_spec.rb @@ -21,11 +21,120 @@ require 'spec_helper' describe JiraService do + include RepoHelpers + describe "Associations" do it { is_expected.to belong_to :project } it { is_expected.to have_one :service_hook } end + describe "Execute" do + let(:user) { create(:user) } + let(:project) { create(:project) } + let(:merge_request) { create(:merge_request) } + + before do + @jira_service = JiraService.new + allow(@jira_service).to receive_messages( + project_id: project.id, + project: project, + service_hook: true, + project_url: 'http://jira.example.com', + username: 'gitlab_jira_username', + password: 'gitlab_jira_password' + ) + @jira_service.save # will build API URL, as api_url was not specified above + @sample_data = Gitlab::PushDataBuilder.build_sample(project, user) + # https://github.com/bblimke/webmock#request-with-basic-authentication + @api_url = 'http://gitlab_jira_username:gitlab_jira_password@jira.example.com/rest/api/2/issue/JIRA-123/transitions' + @comment_url = 'http://gitlab_jira_username:gitlab_jira_password@jira.example.com/rest/api/2/issue/JIRA-123/comment' + + WebMock.stub_request(:post, @api_url) + WebMock.stub_request(:post, @comment_url) + end + + it "should call JIRA API" do + @jira_service.execute(merge_request, JiraIssue.new("JIRA-123", project)) + expect(WebMock).to have_requested(:post, @comment_url).with( + body: /Issue solved with/ + ).once + end + + it "calls the api with jira_issue_transition_id" do + @jira_service.jira_issue_transition_id = 'this-is-a-custom-id' + @jira_service.execute(merge_request, JiraIssue.new("JIRA-123", project)) + expect(WebMock).to have_requested(:post, @api_url).with( + body: /this-is-a-custom-id/ + ).once + end + end + + describe "Stored password invalidation" do + let(:project) { create(:project) } + + context "when a password was previously set" do + before do + @jira_service = JiraService.create( + project: create(:project), + properties: { + api_url: 'http://jira.example.com/rest/api/2', + username: 'mic', + password: "password" + } + ) + end + + it "reset password if url changed" do + @jira_service.api_url = 'http://jira_edited.example.com/rest/api/2' + @jira_service.save + expect(@jira_service.password).to be_nil + end + + it "does not reset password if username changed" do + @jira_service.username = "some_name" + @jira_service.save + expect(@jira_service.password).to eq("password") + end + + it "does not reset password if new url is set together with password, even if it's the same password" do + @jira_service.api_url = 'http://jira_edited.example.com/rest/api/2' + @jira_service.password = 'password' + @jira_service.save + expect(@jira_service.password).to eq("password") + expect(@jira_service.api_url).to eq("http://jira_edited.example.com/rest/api/2") + end + + it "should reset password if url changed, even if setter called multiple times" do + @jira_service.api_url = 'http://jira1.example.com/rest/api/2' + @jira_service.api_url = 'http://jira1.example.com/rest/api/2' + @jira_service.save + expect(@jira_service.password).to be_nil + end + end + + context "when no password was previously set" do + before do + @jira_service = JiraService.create( + project: create(:project), + properties: { + api_url: 'http://jira.example.com/rest/api/2', + username: 'mic' + } + ) + end + + it "saves password if new url is set together with password" do + @jira_service.api_url = 'http://jira_edited.example.com/rest/api/2' + @jira_service.password = 'password' + @jira_service.save + expect(@jira_service.password).to eq("password") + expect(@jira_service.api_url).to eq("http://jira_edited.example.com/rest/api/2") + end + + end + end + + describe "Validations" do context "active" do before do diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 8d7e6e76766..0aef78fe005 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -372,6 +372,23 @@ describe Project do end end + describe :execute_hooks do + it "triggers project and group hooks" do + group = create :group, name: 'gitlab' + project = create(:project, name: 'gitlabhq', namespace: group) + project_hook = create(:project_hook, push_events: true, project: project) + group_hook = create(:group_hook, push_events: true, group: group) + + stub_request(:post, project_hook.url) + stub_request(:post, group_hook.url) + + expect_any_instance_of(ProjectHook).to receive(:async_execute).and_return(true) + expect_any_instance_of(GroupHook).to receive(:async_execute).and_return(true) + + project.execute_hooks({}, :push_hooks) + end + end + describe :avatar_url do subject { project.avatar_url } @@ -403,6 +420,19 @@ describe Project do end end + describe :allowed_to_share_with_group? do + let(:project) { create(:project) } + + it "returns true" do + expect(project.allowed_to_share_with_group?).to be_truthy + end + + it "returns false" do + project.namespace.update(share_with_group_lock: true) + expect(project.allowed_to_share_with_group?).to be_falsey + end + end + describe :ci_commit do let(:project) { create :project } let(:commit) { create :ci_commit, gl_project: project } diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb index 26e8fdae472..9332a5d7094 100644 --- a/spec/models/project_team_spec.rb +++ b/spec/models/project_team_spec.rb @@ -67,6 +67,50 @@ describe ProjectTeam do end end + describe :max_invited_level do + let(:group) { create(:group) } + let(:project) { create(:empty_project) } + + before do + project.project_group_links.create( + group: group, + group_access: Gitlab::Access::DEVELOPER + ) + + group.add_user(master, Gitlab::Access::MASTER) + group.add_user(reporter, Gitlab::Access::REPORTER) + end + + it { expect(project.team.max_invited_level(master.id)).to eq(Gitlab::Access::DEVELOPER) } + it { expect(project.team.max_invited_level(reporter.id)).to eq(Gitlab::Access::REPORTER) } + it { expect(project.team.max_invited_level(nonmember.id)).to be_nil } + end + + describe :max_member_access do + let(:group) { create(:group) } + let(:project) { create(:empty_project) } + + before do + project.project_group_links.create( + group: group, + group_access: Gitlab::Access::DEVELOPER + ) + + group.add_user(master, Gitlab::Access::MASTER) + group.add_user(reporter, Gitlab::Access::REPORTER) + end + + it { expect(project.team.max_member_access(master.id)).to eq(Gitlab::Access::DEVELOPER) } + it { expect(project.team.max_member_access(reporter.id)).to eq(Gitlab::Access::REPORTER) } + it { expect(project.team.max_member_access(nonmember.id)).to be_nil } + + it "does not have an access" do + project.namespace.update(share_with_group_lock: true) + expect(project.team.max_member_access(master.id)).to be_nil + expect(project.team.max_member_access(reporter.id)).to be_nil + end + end + describe "#human_max_access" do it "return master role" do user = create :user diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb index 692e5fda3ba..148aa292a2f 100644 --- a/spec/models/service_spec.rb +++ b/spec/models/service_spec.rb @@ -72,6 +72,10 @@ describe Service do end end + describe "Available services" do + it { expect(Service.available_services_names).to include("jenkins", "jira")} + end + describe "Template" do describe "for pushover service" do let(:service_template) do diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 7d716c23120..fe631fa2c71 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -177,6 +177,19 @@ describe User do end end + describe "non_ldap" do + it "retuns non-ldap user" do + User.delete_all + create :user + ldap_user = create :omniauth_user, provider: "ldapmain" + create :omniauth_user, provider: "gitlub" + + users = User.non_ldap + expect(users.count).to eq 2 + expect(users.detect { |user| user.username == ldap_user.username }).to be_nil + end + end + describe "Respond to" do it { is_expected.to respond_to(:is_admin?) } it { is_expected.to respond_to(:name) } @@ -658,6 +671,27 @@ describe User do end end + describe "#existing_member?" do + it "returns true for exisitng user" do + create :user, email: "bruno@example.com" + + expect(User.existing_member?("bruno@example.com")).to be_truthy + end + + it "returns false for unknown exisitng user" do + create :user, email: "bruno@example.com" + + expect(User.existing_member?("rendom@example.com")).to be_falsey + end + + it "returns true if additional email exists" do + user = create :user + user.emails.create(email: "bruno@example.com") + + expect(User.existing_member?("bruno@example.com")).to be_truthy + end + end + describe "#sort" do before do User.delete_all diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index 13cced81875..7f47077382a 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -14,6 +14,7 @@ describe API::API, api: true do before do group1.add_owner(user1) group2.add_owner(user2) + group1.ldap_group_links.create cn: 'ldap-group', group_access: Gitlab::Access::MASTER, provider: 'ldap' end describe "GET /groups" do @@ -27,10 +28,19 @@ describe API::API, api: true do context "when authenticated as user" do it "normal user: should return an array of groups of user1" do get api("/groups", user1) + expect(response.status).to eq(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['name']).to eq(group1.name) + + expect(json_response.first['ldap_cn']).to eq(group1.ldap_cn) + expect(json_response.first['ldap_access']).to eq(group1.ldap_access) + + ldap_group_link = json_response.first['ldap_group_links'].first + expect(ldap_group_link['cn']).to eq(group1.ldap_cn) + expect(ldap_group_link['group_access']).to eq(group1.ldap_access) + expect(ldap_group_link['provider']).to eq('ldap') end end @@ -124,6 +134,29 @@ describe API::API, api: true do post api("/groups", user3), { name: 'test' } expect(response.status).to eq(400) end + + it "creates an ldap_group_link if ldap_cn and ldap_access are supplied" do + group_attributes = attributes_for(:group, ldap_cn: 'ldap-group', ldap_access: Gitlab::Access::DEVELOPER) + expect { post api("/groups", admin), group_attributes }.to change{ LdapGroupLink.count }.by(1) + end + end + end + + describe "PUT /groups" do + context "when authenticated as user without group permissions" do + it "should not create group" do + put api("/groups/#{group2.id}", user1), attributes_for(:group) + expect(response.status).to eq(403) + end + end + + context "when authenticated as user with group permissions" do + it "should update group" do + group2.update(owner: user2) + put api("/groups/#{group2.id}", user2), { name: 'Renamed' } + expect(response.status).to eq(200) + expect(group2.reload.name).to eq('Renamed') + end end end diff --git a/spec/requests/api/ldap_group_links_spec.rb b/spec/requests/api/ldap_group_links_spec.rb new file mode 100644 index 00000000000..0bc710515b8 --- /dev/null +++ b/spec/requests/api/ldap_group_links_spec.rb @@ -0,0 +1,163 @@ +require 'spec_helper' + +describe API::API, api: true do + include ApiHelpers + + let(:owner) { create(:user) } + let(:user) { create(:user) } + let(:admin) { create(:admin) } + + let!(:group_with_ldap_links) do + group = create(:group) + group.ldap_group_links.create cn: 'ldap-group1', group_access: Gitlab::Access::MASTER, provider: 'ldap1' + group.ldap_group_links.create cn: 'ldap-group2', group_access: Gitlab::Access::MASTER, provider: 'ldap2' + group + end + + before do + group_with_ldap_links.add_owner owner + group_with_ldap_links.add_user user, group_access: Gitlab::Access::DEVELOPER + end + + describe "POST /groups/:id/ldap_group_links" do + context "when unauthenticated" do + it "should return authentication error" do + post api("/groups/#{group_with_ldap_links.id}/ldap_group_links") + expect(response.status).to eq 401 + end + end + + context "when a less priviledged user" do + it "should not allow less priviledged user to add LDAP group link" do + expect do + post api("/groups/#{group_with_ldap_links.id}/ldap_group_links", user), + cn: 'ldap-group4', group_access: GroupMember::GUEST + end.not_to change { group_with_ldap_links.ldap_group_links.count } + + expect(response.status).to eq(403) + end + end + + context "when owner of the group" do + it "should return ok and add ldap group link" do + expect do + post api("/groups/#{group_with_ldap_links.id}/ldap_group_links", owner), + cn: 'ldap-group3', group_access: GroupMember::GUEST, provider: 'ldap3' + end.to change { group_with_ldap_links.ldap_group_links.count }.by(1) + + expect(response.status).to eq(201) + expect(json_response['cn']).to eq('ldap-group3') + expect(json_response['group_access']).to eq(GroupMember::GUEST) + expect(json_response['provider']).to eq('ldap3') + end + + #TODO: Correct and activate this test once issue #329 is fixed + xit "should return ok and add ldap group link even if no provider specified" do + expect do + post api("/groups/#{group_with_ldap_links.id}/ldap_group_links", owner), + cn: 'ldap-group3', group_access: GroupMember::GUEST + end.to change { group_with_ldap_links.ldap_group_links.count }.by(1) + + expect(response.status).to eq(201) + expect(json_response['cn']).to eq('ldap-group3') + expect(json_response['group_access']).to eq(GroupMember::GUEST) + expect(json_response['provider']).to eq('ldapmain') + end + + it "should return error if LDAP group link already exists" do + post api("//groups/#{group_with_ldap_links.id}/ldap_group_links", owner), provider: 'ldap1', cn: 'ldap-group1', group_access: GroupMember::GUEST + expect(response.status).to eq(409) + end + + it "should return a 400 error when cn is not given" do + post api("//groups/#{group_with_ldap_links.id}/ldap_group_links", owner), group_access: GroupMember::GUEST + expect(response.status).to eq(400) + end + + it "should return a 400 error when group access is not given" do + post api("//groups/#{group_with_ldap_links.id}/ldap_group_links", owner), cn: 'ldap-group3' + expect(response.status).to eq(400) + end + + it "should return a 422 error when group access is not known" do + post api("//groups/#{group_with_ldap_links.id}/ldap_group_links", owner), cn: 'ldap-group3', group_access: 11, provider: 'ldap1' + expect(response.status).to eq(422) + end + end + end + + describe 'DELETE /groups/:id/ldap_group_links/:cn' do + context "when unauthenticated" do + it "should return authentication error" do + delete api("/groups/#{group_with_ldap_links.id}/ldap_group_links/ldap-group1") + expect(response.status).to eq 401 + end + end + + context "when a less priviledged user" do + it "should not remove the LDAP group link" do + expect do + delete api("/groups/#{group_with_ldap_links.id}/ldap_group_links/ldap-group1", user) + end.not_to change { group_with_ldap_links.ldap_group_links.count } + + expect(response.status).to eq(403) + end + end + + context "when owner of the group" do + it "should remove ldap group link" do + expect do + delete api("/groups/#{group_with_ldap_links.id}/ldap_group_links/ldap-group1", owner) + end.to change { group_with_ldap_links.ldap_group_links.count }.by(-1) + + expect(response.status).to eq(200) + end + + it "should return 404 if LDAP group cn not used for a LDAP group link" do + expect do + delete api("/groups/#{group_with_ldap_links.id}/ldap_group_links/ldap-group1356", owner) + end.not_to change { group_with_ldap_links.ldap_group_links.count } + + expect(response.status).to eq(404) + end + end + end + + describe 'DELETE /groups/:id/ldap_group_links/:provider/:cn' do + context "when unauthenticated" do + it "should return authentication error" do + delete api("/groups/#{group_with_ldap_links.id}/ldap_group_links/ldap2/ldap-group2") + expect(response.status).to eq 401 + end + end + + context "when a less priviledged user" do + it "should not remove the LDAP group link" do + expect do + delete api("/groups/#{group_with_ldap_links.id}/ldap_group_links/ldap2/ldap-group2", user) + end.not_to change { group_with_ldap_links.ldap_group_links.count } + + expect(response.status).to eq(403) + end + end + + context "when owner of the group" do + it "should return 404 if LDAP group cn not used for a LDAP group link for the specified provider" do + expect do + delete api("/groups/#{group_with_ldap_links.id}/ldap_group_links/ldap1/ldap-group2", owner) + end.not_to change { group_with_ldap_links.ldap_group_links.count } + + expect(response.status).to eq(404) + end + + it "should remove ldap group link" do + expect do + delete api("/groups/#{group_with_ldap_links.id}/ldap_group_links/ldap2/ldap-group2", owner) + end.to change { group_with_ldap_links.ldap_group_links.count }.by(-1) + + expect(response.status).to eq(200) + end + end + end + +end diff --git a/spec/requests/api/ldap_spec.rb b/spec/requests/api/ldap_spec.rb new file mode 100644 index 00000000000..8477a73ec34 --- /dev/null +++ b/spec/requests/api/ldap_spec.rb @@ -0,0 +1,53 @@ +require 'spec_helper' + +describe API::API do + include ApiHelpers + let(:user) { create(:user) } + + before do + groups = [ + OpenStruct.new(cn: 'developers'), + OpenStruct.new(cn: 'students') + ] + + allow_any_instance_of(Gitlab::LDAP::Adapter).to receive_messages(groups: groups) + end + + describe "GET /ldap/groups" do + context "when unauthenticated" do + it "should return authentication error" do + get api("/ldap/groups") + expect(response.status).to eq 401 + end + end + + context "when authenticated as user" do + it "should return an array of ldap groups" do + get api("/ldap/groups", user) + expect(response.status).to eq 200 + expect(json_response).to be_an Array + expect(json_response.length).to eq 2 + expect(json_response.first['cn']).to eq 'developers' + end + end + end + + describe "GET /ldap/ldapmain/groups" do + context "when unauthenticated" do + it "should return authentication error" do + get api("/ldap/ldapmain/groups") + expect(response.status).to eq 401 + end + end + + context "when authenticated as user" do + it "should return an array of ldap groups" do + get api("/ldap/ldapmain/groups", user) + expect(response.status).to eq 200 + expect(json_response).to be_an Array + expect(json_response.length).to eq 2 + expect(json_response.first['cn']).to eq 'developers' + end + end + end +end diff --git a/spec/requests/api/project_git_hook_spec.rb b/spec/requests/api/project_git_hook_spec.rb new file mode 100644 index 00000000000..de45deb01cc --- /dev/null +++ b/spec/requests/api/project_git_hook_spec.rb @@ -0,0 +1,141 @@ +require 'spec_helper' + +describe API::API, 'ProjectGitHook', api: true do + include ApiHelpers + let(:user) { create(:user) } + let(:user3) { create(:user) } + let!(:project) { create(:project, creator_id: user.id, namespace: user.namespace) } + + before do + project.team << [user, :master] + project.team << [user3, :developer] + end + + describe "GET /projects/:id/git_hook" do + before do + create(:git_hook, project: project) + end + + context "authorized user" do + it "should return project git hook" do + get api("/projects/#{project.id}/git_hook", user) + expect(response.status).to eq(200) + + expect(json_response).to be_an Hash + expect(json_response['project_id']).to eq(project.id) + end + end + + context "unauthorized user" do + it "should not access project git hooks" do + get api("/projects/#{project.id}/git_hook", user3) + expect(response.status).to eq(403) + end + end + end + + + describe "POST /projects/:id/git_hook" do + context "authorized user" do + it "should add git hook to project" do + post api("/projects/#{project.id}/git_hook", user), + deny_delete_tag: true + expect(response.status).to eq(201) + + expect(json_response).to be_an Hash + expect(json_response['project_id']).to eq(project.id) + expect(json_response['deny_delete_tag']).to eq(true) + end + end + + context "unauthorized user" do + it "should not add git hook to project" do + post api("/projects/#{project.id}/git_hook", user3), + deny_delete_tag: true + expect(response.status).to eq(403) + end + end + end + + describe "POST /projects/:id/git_hook" do + before do + create(:git_hook, project: project) + end + + context "with existing git hook" do + it "should not add git hook to project" do + post api("/projects/#{project.id}/git_hook", user), + deny_delete_tag: true + expect(response.status).to eq(422) + end + end + end + + describe "PUT /projects/:id/git_hook" do + before do + create(:git_hook, project: project) + end + + it "should update an existing project git hook" do + put api("/projects/#{project.id}/git_hook", user), + deny_delete_tag: false, commit_message_regex: 'Fixes \d+\..*' + expect(response.status).to eq(200) + + expect(json_response['deny_delete_tag']).to eq(false) + expect(json_response['commit_message_regex']).to eq('Fixes \d+\..*') + end + end + + describe "PUT /projects/:id/git_hook" do + it "should error on non existing project git hook" do + put api("/projects/#{project.id}/git_hook", user), + deny_delete_tag: false, commit_message_regex: 'Fixes \d+\..*' + expect(response.status).to eq(404) + end + + it "should not update git hook for unauthorized user" do + post api("/projects/#{project.id}/git_hook", user3), + deny_delete_tag: true + expect(response.status).to eq(403) + end + end + + describe "DELETE /projects/:id/git_hook" do + before do + create(:git_hook, project: project) + end + + context "authorized user" do + it "should delete git hook from project" do + delete api("/projects/#{project.id}/git_hook", user) + expect(response.status).to eq(200) + + expect(json_response).to be_an Hash + end + end + + context "unauthorized user" do + it "should return a 403 error" do + delete api("/projects/#{project.id}/git_hook", user3) + expect(response.status).to eq(403) + end + end + end + + describe "DELETE /projects/:id/git_hook" do + context "for non existing git hook" do + it "should delete git hook from project" do + delete api("/projects/#{project.id}/git_hook", user) + expect(response.status).to eq(404) + + expect(json_response).to be_an Hash + expect(json_response['message']).to eq("404 Not Found") + end + + it "should return a 403 error if not authorized" do + delete api("/projects/#{project.id}/git_hook", user3) + expect(response.status).to eq(403) + end + end + end +end diff --git a/spec/requests/api/project_members_spec.rb b/spec/requests/api/project_members_spec.rb index 6358f6a2a4a..1daa4115ad1 100644 --- a/spec/requests/api/project_members_spec.rb +++ b/spec/requests/api/project_members_spec.rb @@ -89,6 +89,18 @@ describe API::API, api: true do post api("/projects/#{project.id}/members", user), user_id: user2.id, access_level: 1234 expect(response.status).to eq(422) end + + context 'project in a group' do + before do + project2 = create(:project, group: create(:group, membership_lock: true)) + project2.group.add_owner(user) + post api("/projects/#{project2.id}/members", user), user_id: user2.id, access_level: ProjectMember::MASTER + end + + it 'should return a 405 method not allowed error when group membership lock is enabled' do + expect(response.status).to eq 405 + end + end end describe "PUT /projects/:id/members/:user_id" do @@ -131,7 +143,7 @@ describe API::API, api: true do delete api("/projects/#{project.id}/members/#{user3.id}", user) expect do delete api("/projects/#{project.id}/members/#{user3.id}", user) - end.to_not change { ProjectMember.count } + end.not_to change { ProjectMember.count } end it "should return 200 if team member already removed" do diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 9fc294118ae..61ff4960a1f 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -649,6 +649,42 @@ describe API::API, api: true do end end + describe "POST /projects/:id/share" do + let(:group) { create(:group) } + + it "should share project with group" do + expect do + post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: Gitlab::Access::DEVELOPER + end.to change { ProjectGroupLink.count }.by(1) + + expect(response.status).to eq 201 + expect(json_response['group_id']).to eq group.id + expect(json_response['group_access']).to eq Gitlab::Access::DEVELOPER + end + + it "should return a 400 error when group id is not given" do + post api("/projects/#{project.id}/share", user), group_access: Gitlab::Access::DEVELOPER + expect(response.status).to eq 400 + end + + it "should return a 400 error when access level is not given" do + post api("/projects/#{project.id}/share", user), group_id: group.id + expect(response.status).to eq 400 + end + + it "should return a 400 error when sharing is disabled" do + project.namespace.update(share_with_group_lock: true) + post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: Gitlab::Access::DEVELOPER + expect(response.status).to eq 400 + end + + it "should return a 409 error when wrong params passed" do + post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: 1234 + expect(response.status).to eq 409 + expect(json_response['message']).to eq 'Group access is not included in the list' + end + end + describe 'GET /projects/search/:query' do let!(:query) { 'query'} let!(:search) { create(:empty_project, name: query, creator_id: user.id, namespace: user.namespace) } diff --git a/spec/requests/api/system_hooks_spec.rb b/spec/requests/api/system_hooks_spec.rb index 3e676515488..94eebc48ec8 100644 --- a/spec/requests/api/system_hooks_spec.rb +++ b/spec/requests/api/system_hooks_spec.rb @@ -49,7 +49,7 @@ describe API::API, api: true do it "should not create new hook without url" do expect do post api("/hooks", admin) - end.to_not change { SystemHook.count } + end.not_to change { SystemHook.count } end end diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index a9ef2fe5885..3b2361d53fe 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -40,6 +40,18 @@ describe API::API, api: true do expect(json_response.first.keys).to include 'two_factor_enabled' end end + + context "when authenticated and ldap is enabled" do + it "should return non-ldap user" do + User.delete_all + create :omniauth_user, provider: "ldapserver1" + get api("/users", user), skip_ldap: "true" + expect(response.status).to eq 200 + expect(json_response).to be_an Array + username = user.username + expect(json_response.first["username"]).to eq username + end + end end describe "GET /users/:id" do diff --git a/spec/routing/admin_routing_spec.rb b/spec/routing/admin_routing_spec.rb index cd16a8e6322..1beddf4965b 100644 --- a/spec/routing/admin_routing_spec.rb +++ b/spec/routing/admin_routing_spec.rb @@ -118,3 +118,13 @@ describe Admin::DashboardController, "routing" do expect(get("/admin")).to route_to('admin/dashboard#index') end end + +describe Admin::EmailsController, "routing" do + it "to #show" do + expect(get("/admin/email")).to route_to('admin/emails#show') + end + + it "to #create" do + expect(post("/admin/email")).to route_to('admin/emails#create') + end +end diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb index 17015d29e51..00cd570fae3 100644 --- a/spec/services/git_push_service_spec.rb +++ b/spec/services/git_push_service_spec.rb @@ -265,6 +265,75 @@ describe GitPushService do expect(Issue.find(issue.id)).to be_opened end end + + # EE-only tests + context "for jira issue tracker" do + include JiraServiceHelper + + let(:jira_tracker) { project.create_jira_service if project.jira_service.nil? } + + before do + jira_service_settings + + WebMock.stub_request(:post, jira_api_transition_url) + WebMock.stub_request(:post, jira_api_comment_url) + WebMock.stub_request(:get, jira_api_comment_url).to_return(body: jira_issue_comments) + WebMock.stub_request(:get, jira_api_test_url) + + allow(closing_commit).to receive_messages({ + issue_closing_regex: Regexp.new(Gitlab.config.gitlab.issue_closing_pattern), + safe_message: message, + author_name: commit_author.name, + author_email: commit_author.email + }) + + allow(project.repository).to receive_messages(commits_between: [closing_commit]) + end + + after do + jira_tracker.destroy! + end + + context "mentioning an issue" do + let(:message) { "this is some work.\n\nrelated to JIRA-1" } + + it "should initiate one api call to jira server to mention the issue" do + service.execute(project, user, @oldrev, @newrev, @ref) + + expect(WebMock).to have_requested(:post, jira_api_comment_url).with( + body: /mentioned this issue in/ + ).once + end + end + + context "closing an issue" do + let(:message) { "this is some work.\n\ncloses JIRA-1" } + + it "should initiate one api call to jira server to close the issue" do + transition_body = { + transition: { + id: '2' + } + }.to_json + + service.execute(project, user, @oldrev, @newrev, @ref) + expect(WebMock).to have_requested(:post, jira_api_transition_url).with( + body: transition_body + ).once + end + + it "should initiate one api call to jira server to comment on the issue" do + comment_body = { + body: "Issue solved with [#{closing_commit.id}|http://localhost/#{project.path_with_namespace}/commit/#{closing_commit.id}]." + }.to_json + + service.execute(project, user, @oldrev, @newrev, @ref) + expect(WebMock).to have_requested(:post, jira_api_comment_url).with( + body: comment_body + ).once + end + end + end end describe "empty project" do diff --git a/spec/services/ldap_group_reset_service_spec.rb b/spec/services/ldap_group_reset_service_spec.rb new file mode 100644 index 00000000000..bfdf1b2ed13 --- /dev/null +++ b/spec/services/ldap_group_reset_service_spec.rb @@ -0,0 +1,42 @@ +require 'spec_helper' + +describe LdapGroupResetService do + # TODO: refactor to multi-ldap setup + let(:group) { create(:group) } + let(:user) { create(:user) } + let(:ldap_user) { create(:omniauth_user, extern_uid: 'john', provider: 'ldap', last_credential_check_at: Time.now) } + let(:ldap_user_2) { create(:omniauth_user, extern_uid: 'mike', provider: 'ldap', last_credential_check_at: Time.now) } + + before do + group.add_owner(user) + group.add_owner(ldap_user) + group.add_user(ldap_user_2, Gitlab::Access::REPORTER) + group.ldap_group_links.create cn: 'developers', group_access: Gitlab::Access::DEVELOPER + end + + describe '#execute' do + context 'initiated by ldap user' do + before { LdapGroupResetService.new.execute(group, ldap_user) } + + it { expect(member_access(ldap_user)).to eq Gitlab::Access::OWNER } + it { expect(member_access(ldap_user_2)).to eq Gitlab::Access::GUEST } + it { expect(member_access(user)).to eq Gitlab::Access::OWNER } + it { expect(ldap_user.reload.last_credential_check_at).to be_nil } + it { expect(ldap_user_2.reload.last_credential_check_at).to be_nil } + end + + context 'initiated by regular user' do + before { LdapGroupResetService.new.execute(group, user) } + + it { expect(member_access(ldap_user)).to eq Gitlab::Access::GUEST } + it { expect(member_access(ldap_user_2)).to eq Gitlab::Access::GUEST } + it { expect(member_access(user)).to eq Gitlab::Access::OWNER } + it { expect(ldap_user.reload.last_credential_check_at).to be_nil } + it { expect(ldap_user_2.reload.last_credential_check_at).to be_nil } + end + end + + def member_access(user) + group.members.find_by(user_id: user).access_level + end +end diff --git a/spec/services/merge_requests/ff_merge_service_spec.rb b/spec/services/merge_requests/ff_merge_service_spec.rb new file mode 100644 index 00000000000..f153edea228 --- /dev/null +++ b/spec/services/merge_requests/ff_merge_service_spec.rb @@ -0,0 +1,50 @@ +require 'spec_helper' + +describe MergeRequests::FfMergeService do + let(:user) { create(:user) } + let(:user2) { create(:user) } + let(:merge_request) do + create(:merge_request, + source_branch: 'flatten-dir', + target_branch: 'improve/awesome', + assignee: user2) + end + let(:project) { merge_request.project } + + before do + project.team << [user, :master] + project.team << [user2, :developer] + end + + describe :execute do + context 'valid params' do + let(:service) { MergeRequests::FfMergeService.new(project, user, {}) } + + before do + allow(service).to receive(:execute_hooks) + + service.execute(merge_request) + end + + it "should not create merge commit" do + source_branch_sha = merge_request.source_project.repository.commit(merge_request.source_branch).sha + target_branch_sha = merge_request.target_project.repository.commit(merge_request.target_branch).sha + expect(source_branch_sha).to eq(target_branch_sha) + end + + it { expect(merge_request).to be_valid } + it { expect(merge_request).to be_merged } + + it 'should send email to user2 about merge of new merge_request' do + email = ActionMailer::Base.deliveries.last + expect(email.to.first).to eq(user2.email) + expect(email.subject).to include(merge_request.title) + end + + it 'should create system note about merge_request merge' do + note = merge_request.notes.last + expect(note.note).to include 'Status changed to merged' + end + end + end +end diff --git a/spec/services/merge_requests/rebase_service_spec.rb b/spec/services/merge_requests/rebase_service_spec.rb new file mode 100644 index 00000000000..3b76504ceec --- /dev/null +++ b/spec/services/merge_requests/rebase_service_spec.rb @@ -0,0 +1,31 @@ +require 'spec_helper' + +describe MergeRequests::RebaseService do + let(:user) { create(:user) } + let(:merge_request) do + create(:merge_request, + source_branch: 'feature_conflict', + target_branch: 'master') + end + let(:project) { merge_request.project } + + before do + project.team << [user, :master] + end + + describe :execute do + context 'valid params' do + let(:service) { MergeRequests::RebaseService.new(project, user, {}) } + + before do + service.execute(merge_request) + end + + it "should rebase source branch" do + parent_sha = merge_request.source_project.repository.commit(merge_request.source_branch).parents.first.sha + target_branch_sha = merge_request.target_project.repository.commit(merge_request.target_branch).sha + expect(parent_sha).to eq(target_branch_sha) + end + end + end +end diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index 7ee4488521d..4a5fd83d583 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -11,7 +11,7 @@ describe MergeRequests::RefreshService do group = create(:group) group.add_owner(@user) - @project = create(:project, namespace: group) + @project = create(:project, namespace: group, approvals_before_merge: 1, reset_approvals_on_push: true) @fork_project = Projects::ForkService.new(@project, @user).execute @merge_request = create(:merge_request, source_project: @project, @@ -25,6 +25,9 @@ describe MergeRequests::RefreshService do target_branch: 'feature', target_project: @project) + @merge_request.approvals.create(user_id: user.id) + @fork_merge_request.approvals.create(user_id: user.id) + @commits = @merge_request.commits @oldrev = @commits.last.id @@ -46,8 +49,10 @@ describe MergeRequests::RefreshService do it { expect(@merge_request.notes).not_to be_empty } it { expect(@merge_request).to be_open } + it { expect(@merge_request.approvals).to be_empty } it { expect(@fork_merge_request).to be_open } it { expect(@fork_merge_request.notes).to be_empty } + it { expect(@fork_merge_request.approvals).to be_empty } end context 'push to origin repo target branch' do @@ -58,8 +63,10 @@ describe MergeRequests::RefreshService do it { expect(@merge_request.notes.last.note).to include('changed to merged') } it { expect(@merge_request).to be_merged } + it { expect(@merge_request.approvals).not_to be_empty } it { expect(@fork_merge_request).to be_merged } it { expect(@fork_merge_request.notes.last.note).to include('changed to merged') } + it { expect(@fork_merge_request.approvals).not_to be_empty } end context 'manual merge of source branch' do @@ -96,8 +103,10 @@ describe MergeRequests::RefreshService do it { expect(@merge_request.notes).to be_empty } it { expect(@merge_request).to be_open } + it { expect(@merge_request.approvals).not_to be_empty } it { expect(@fork_merge_request.notes.last.note).to include('Added 4 commits') } it { expect(@fork_merge_request).to be_open } + it { expect(@fork_merge_request.approvals).not_to be_empty } end context 'push to fork repo target branch' do @@ -108,8 +117,10 @@ describe MergeRequests::RefreshService do it { expect(@merge_request.notes).to be_empty } it { expect(@merge_request).to be_open } + it { expect(@merge_request.approvals).not_to be_empty } it { expect(@fork_merge_request.notes).to be_empty } it { expect(@fork_merge_request).to be_open } + it { expect(@fork_merge_request.approvals).not_to be_empty } end context 'push to origin repo target branch after fork project was removed' do @@ -121,8 +132,32 @@ describe MergeRequests::RefreshService do it { expect(@merge_request.notes.last.note).to include('changed to merged') } it { expect(@merge_request).to be_merged } + it { expect(@merge_request.approvals).not_to be_empty } it { expect(@fork_merge_request).to be_open } it { expect(@fork_merge_request.notes).to be_empty } + it { expect(@fork_merge_request.approvals).not_to be_empty } + end + + context 'resetting approvals if they are enabled' do + it "does not reset approvals if approvals_before_merge si disabled" do + @project.update(approvals_before_merge: 0) + refresh_service = service.new(@project, @user) + allow(refresh_service).to receive(:execute_hooks) + refresh_service.execute(@oldrev, @newrev, 'refs/heads/master') + reload_mrs + + expect(@merge_request.approvals).not_to be_empty + end + + it "does not reset approvals if reset_approvals_on_push si disabled" do + @project.update(reset_approvals_on_push: false) + refresh_service = service.new(@project, @user) + allow(refresh_service).to receive(:execute_hooks) + refresh_service.execute(@oldrev, @newrev, 'refs/heads/master') + reload_mrs + + expect(@merge_request.approvals).not_to be_empty + end end context 'push new branch that exists in a merge request' do diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb index e81c4edb7d8..0d8529fe53b 100644 --- a/spec/services/projects/create_service_spec.rb +++ b/spec/services/projects/create_service_spec.rb @@ -119,6 +119,19 @@ describe Projects::CreateService do end end + context "git hook sample" do + before do + @git_hook_sample = create :git_hook_sample + end + + it "creates git hook from sample" do + git_hook = create_project(@user, @opts).git_hook + [:force_push_regex, :deny_delete_tag, :delete_branch_regex, :commit_message_regex].each do |attr_name| + expect(git_hook.send(attr_name)).to eq @git_hook_sample.send(attr_name) + end + end + end + context 'repository creation' do it 'should synchronously create the repository' do expect_any_instance_of(Project).to receive(:create_repository) diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index a45130bd473..4b20cbeeb3b 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -399,4 +399,78 @@ describe SystemNoteService do end end end + + include JiraServiceHelper + + describe 'JIRA integration' do + let(:project) { create(:project) } + let(:author) { create(:user) } + let(:issue) { create(:issue, project: project) } + let(:mergereq) { create(:merge_request, :simple, target_project: project, source_project: project) } + let(:jira_issue) { JiraIssue.new("JIRA-1", project)} + let(:jira_tracker) { project.create_jira_service if project.jira_service.nil? } + let(:commit) { project.commit } + + context 'in JIRA issue tracker' do + before do + jira_service_settings + WebMock.stub_request(:post, jira_api_comment_url) + end + + after do + jira_tracker.destroy! + end + + describe "new reference" do + before do + WebMock.stub_request(:get, jira_api_comment_url).to_return(body: jira_issue_comments) + end + + subject { described_class.cross_reference(jira_issue, commit, author) } + + it { is_expected.to eq(jira_status_message) } + end + + describe "existing reference" do + before do + message = "[#{author.name}|http://localhost/u/#{author.username}] mentioned this issue in [a commit of #{project.path_with_namespace}|http://localhost/#{project.path_with_namespace}/commit/#{commit.id}]." + WebMock.stub_request(:get, jira_api_comment_url).to_return(body: "{\"comments\":[{\"body\":\"#{message}\"}]}") + end + + subject { described_class.cross_reference(jira_issue, commit, author) } + it { is_expected.not_to eq(jira_status_message) } + end + end + + context 'issue from an issue' do + context 'in JIRA issue tracker' do + before do + jira_service_settings + WebMock.stub_request(:post, jira_api_comment_url) + WebMock.stub_request(:get, jira_api_comment_url).to_return(body: jira_issue_comments) + end + + after do + jira_tracker.destroy! + end + + subject { described_class.cross_reference(jira_issue, issue, author) } + + it { is_expected.to eq(jira_status_message) } + end + end + end + + describe '.approve_mr' do + let(:noteable) { create(:merge_request, source_project: project) } + subject { described_class.approve_mr(noteable, author) } + + it_behaves_like 'a system note' + + context 'when merge request approved' do + it 'sets the note text' do + expect(subject.note).to eq "Approved this merge request" + end + end + end end diff --git a/spec/services/test_hook_service_spec.rb b/spec/services/test_hook_service_spec.rb index 226196eedae..526765b6b7c 100644 --- a/spec/services/test_hook_service_spec.rb +++ b/spec/services/test_hook_service_spec.rb @@ -1,14 +1,22 @@ require 'spec_helper' describe TestHookService do - let(:user) { create :user } - let(:project) { create :project } - let(:hook) { create :project_hook, project: project } + let(:user) { create :user } + let(:group) { create :group } + let(:project) { create :project, group: group } + let(:project_hook) { create :project_hook, project: project } + let(:group_hook) { create :group_hook, group: group } describe :execute do - it "should execute successfully" do - stub_request(:post, hook.url).to_return(status: 200) - expect(TestHookService.new.execute(hook, user)).to be_truthy + it "should successfully execute the project hook" do + stub_request(:post, project_hook.url).to_return(status: 200) + expect(TestHookService.new.execute(project_hook, user)).to be_truthy + end + + it "should successfully execute the group hook" do + project.reload + stub_request(:post, group_hook.url).to_return(status: 200) + expect(TestHookService.new.execute(group_hook, user)).to be_truthy end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 2be13bb3e6a..12a697a1cbd 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -41,6 +41,10 @@ RSpec.configure do |config| config.before(:suite) do TestEnv.init end + + config.before(:all) do + TestLicense.init + end end FactoryGirl::SyntaxRunner.class_eval do diff --git a/spec/support/jira_service_helper.rb b/spec/support/jira_service_helper.rb new file mode 100644 index 00000000000..a3f496359b1 --- /dev/null +++ b/spec/support/jira_service_helper.rb @@ -0,0 +1,67 @@ +module JiraServiceHelper + + def jira_service_settings + properties = { + "title"=>"JIRA tracker", + "project_url"=>"http://jira.example/issues/?jql=project=A", + "issues_url"=>"http://jira.example/browse/JIRA-1", + "new_issue_url"=>"http://jira.example/secure/CreateIssue.jspa", + "api_url"=>"http://jira.example/rest/api/2" + } + + jira_tracker.update_attributes(properties: properties, active: true) + end + + def jira_status_message + "JiraService SUCCESS 200: Successfully posted to #{jira_api_comment_url}." + end + + def jira_issue_comments + "{\"startAt\":0,\"maxResults\":11,\"total\":11, + \"comments\":[{\"self\":\"http://0.0.0.0:4567/rest/api/2/issue/10002/comment/10609\", + \"id\":\"10609\",\"author\":{\"self\":\"http://0.0.0.0:4567/rest/api/2/user?username=gitlab\", + \"name\":\"gitlab\",\"emailAddress\":\"gitlab@example.com\", + \"avatarUrls\":{\"16x16\":\"http://0.0.0.0:4567/secure/useravatar?size=xsmall&avatarId=10122\", + \"24x24\":\"http://0.0.0.0:4567/secure/useravatar?size=small&avatarId=10122\", + \"32x32\":\"http://0.0.0.0:4567/secure/useravatar?size=medium&avatarId=10122\", + \"48x48\":\"http://0.0.0.0:4567/secure/useravatar?avatarId=10122\"}, + \"displayName\":\"GitLab\",\"active\":true}, + \"body\":\"[Administrator|http://localhost:3000/u/root] mentioned JIRA-1 in Merge request of [gitlab-org/gitlab-test|http://localhost:3000/gitlab-org/gitlab-test/merge_requests/2].\", + \"updateAuthor\":{\"self\":\"http://0.0.0.0:4567/rest/api/2/user?username=gitlab\",\"name\":\"gitlab\",\"emailAddress\":\"gitlab@example.com\", + \"avatarUrls\":{\"16x16\":\"http://0.0.0.0:4567/secure/useravatar?size=xsmall&avatarId=10122\", + \"24x24\":\"http://0.0.0.0:4567/secure/useravatar?size=small&avatarId=10122\", + \"32x32\":\"http://0.0.0.0:4567/secure/useravatar?size=medium&avatarId=10122\", + \"48x48\":\"http://0.0.0.0:4567/secure/useravatar?avatarId=10122\"},\"displayName\":\"GitLab\",\"active\":true}, + \"created\":\"2015-02-12T22:47:07.826+0100\", + \"updated\":\"2015-02-12T22:47:07.826+0100\"}, + {\"self\":\"http://0.0.0.0:4567/rest/api/2/issue/10002/comment/10700\", + \"id\":\"10700\",\"author\":{\"self\":\"http://0.0.0.0:4567/rest/api/2/user?username=gitlab\", + \"name\":\"gitlab\",\"emailAddress\":\"gitlab@example.com\", + \"avatarUrls\":{\"16x16\":\"http://0.0.0.0:4567/secure/useravatar?size=xsmall&avatarId=10122\", + \"24x24\":\"http://0.0.0.0:4567/secure/useravatar?size=small&avatarId=10122\", + \"32x32\":\"http://0.0.0.0:4567/secure/useravatar?size=medium&avatarId=10122\", + \"48x48\":\"http://0.0.0.0:4567/secure/useravatar?avatarId=10122\"},\"displayName\":\"GitLab\",\"active\":true}, + \"body\":\"[Administrator|http://localhost:3000/u/root] mentioned this issue in [a commit of h5bp/html5-boilerplate|http://localhost:3000/h5bp/html5-boilerplate/commit/2439f77897122fbeee3bfd9bb692d3608848433e].\", + \"updateAuthor\":{\"self\":\"http://0.0.0.0:4567/rest/api/2/user?username=gitlab\",\"name\":\"gitlab\",\"emailAddress\":\"gitlab@example.com\", + \"avatarUrls\":{\"16x16\":\"http://0.0.0.0:4567/secure/useravatar?size=xsmall&avatarId=10122\", + \"24x24\":\"http://0.0.0.0:4567/secure/useravatar?size=small&avatarId=10122\", + \"32x32\":\"http://0.0.0.0:4567/secure/useravatar?size=medium&avatarId=10122\", + \"48x48\":\"http://0.0.0.0:4567/secure/useravatar?avatarId=10122\"},\"displayName\":\"GitLab\",\"active\":true}, + \"created\":\"2015-04-01T03:45:55.667+0200\", + \"updated\":\"2015-04-01T03:45:55.667+0200\" + } + ]}" + end + + def jira_api_comment_url + 'http://jira.example/rest/api/2/issue/JIRA-1/comment' + end + + def jira_api_transition_url + 'http://jira.example/rest/api/2/issue/JIRA-1/transitions' + end + + def jira_api_test_url + 'http://jira.example/rest/api/2/myself' + end +end diff --git a/spec/support/license.rb b/spec/support/license.rb new file mode 100644 index 00000000000..361e219e14a --- /dev/null +++ b/spec/support/license.rb @@ -0,0 +1,7 @@ +class TestLicense + def self.init + Gitlab::License.encryption_key = OpenSSL::PKey::RSA.generate(2048) + + FactoryGirl.create(:license) + end +end diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb index cd9fdc6f18e..6d658e2b90b 100644 --- a/spec/support/login_helpers.rb +++ b/spec/support/login_helpers.rb @@ -37,7 +37,7 @@ module LoginHelpers # Requires Javascript driver. def logout - find(:css, ".fa.fa-sign-out").click + find(:css, ".logout").click end # Logout without JavaScript driver diff --git a/spec/workers/admin_emails_worker_spec.rb b/spec/workers/admin_emails_worker_spec.rb new file mode 100644 index 00000000000..2eb7fa4ac04 --- /dev/null +++ b/spec/workers/admin_emails_worker_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe AdminEmailsWorker do + context "recipients" do + let(:recipient_id) { "group-#{group.id}" } + let(:group) { create :group } + + before do + 2.times do + group.add_user(create(:user), Gitlab::Access::DEVELOPER) + end + unsubscribed_user = create(:user, admin_email_unsubscribed_at: 5.days.ago) + group.add_user(unsubscribed_user, Gitlab::Access::DEVELOPER) + ActionMailer::Base.deliveries = [] + end + + it "sends email to subscribed users" do + AdminEmailsWorker.new.perform(recipient_id, 'subject', 'body') + expect(ActionMailer::Base.deliveries.count).to eql 2 + end + end +end |