summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMayra Cabrera <mcabrera@gitlab.com>2018-03-19 10:11:12 -0600
committerMayra Cabrera <mcabrera@gitlab.com>2018-03-27 18:57:10 -0600
commitaef29942e7386d932d2e4c19b717ccca6c9f4f97 (patch)
tree6378042a848f8876dc3f21b8e2ecf7d52f157056
parentfbb83069deaad3db1239af66e6c9dc913f29f8f8 (diff)
downloadgitlab-ce-31591-project-deploy-tokens-to-allow-permanent-access.tar.gz
Includes: - Model, factories, create service and controller actions - As usual, includes specs for everything - Builds UI (copy from PAT) - Add revoke action Closes #31591
-rw-r--r--app/assets/javascripts/pages/projects/settings/repository/show/index.js2
-rw-r--r--app/controllers/projects/deploy_tokens_controller.rb34
-rw-r--r--app/controllers/projects/settings/repository_controller.rb1
-rw-r--r--app/models/deploy_token.rb25
-rw-r--r--app/models/project.rb1
-rw-r--r--app/presenters/deploy_token_presenter.rb17
-rw-r--r--app/presenters/projects/settings/deploy_tokens_presenter.rb52
-rw-r--r--app/services/deploy_tokens/create_service.rb24
-rw-r--r--app/views/profiles/personal_access_tokens/index.html.haml1
-rw-r--r--app/views/projects/deploy_tokens/_form.html.haml21
-rw-r--r--app/views/projects/deploy_tokens/_index.html.haml17
-rw-r--r--app/views/projects/deploy_tokens/_scope_form.html.haml4
-rw-r--r--app/views/projects/deploy_tokens/_table.html.haml28
-rw-r--r--app/views/projects/deploy_tokens/_temporal_deploy_token.html.haml8
-rw-r--r--app/views/projects/settings/repository/show.html.haml1
-rw-r--r--config/gitlab_backup.yml756
-rw-r--r--config/routes/project.rb6
-rw-r--r--db/migrate/20180319190020_create_deploy_tokens.rb14
-rw-r--r--db/schema.rb17
-rw-r--r--spec/controllers/projects/deploy_tokens_controller_spec.rb67
-rw-r--r--spec/factories/deploy_tokens.rb23
-rw-r--r--spec/models/deploy_token_spec.rb39
-rw-r--r--spec/presenters/deploy_token_presenter.rb9
-rw-r--r--spec/presenters/projects/settings/deploy_tokens_presenter_spec.rb36
-rw-r--r--spec/services/deploy_tokens/create_service_spec.rb44
25 files changed, 1245 insertions, 2 deletions
diff --git a/app/assets/javascripts/pages/projects/settings/repository/show/index.js b/app/assets/javascripts/pages/projects/settings/repository/show/index.js
index 788d86d1192..5bcdfc26a4b 100644
--- a/app/assets/javascripts/pages/projects/settings/repository/show/index.js
+++ b/app/assets/javascripts/pages/projects/settings/repository/show/index.js
@@ -6,6 +6,7 @@ import initSettingsPanels from '~/settings_panels';
import initDeployKeys from '~/deploy_keys';
import ProtectedBranchCreate from '~/protected_branches/protected_branch_create';
import ProtectedBranchEditList from '~/protected_branches/protected_branch_edit_list';
+import DueDateSelectors from '~/due_date_select';
document.addEventListener('DOMContentLoaded', () => {
new ProtectedTagCreate();
@@ -14,4 +15,5 @@ document.addEventListener('DOMContentLoaded', () => {
initSettingsPanels();
new ProtectedBranchCreate(); // eslint-disable-line no-new
new ProtectedBranchEditList(); // eslint-disable-line no-new
+ new DueDateSelectors();
});
diff --git a/app/controllers/projects/deploy_tokens_controller.rb b/app/controllers/projects/deploy_tokens_controller.rb
new file mode 100644
index 00000000000..0628b7aecad
--- /dev/null
+++ b/app/controllers/projects/deploy_tokens_controller.rb
@@ -0,0 +1,34 @@
+class Projects::DeployTokensController < Projects::ApplicationController
+ include RepositorySettingsRedirect
+
+ before_action :authorize_admin_project!
+
+ def create
+ @token = DeployTokens::CreateService.new(@project, current_user, deploy_token_params).execute
+
+ if @token.valid?
+ flash[:notice] = 'Your new project deploy token has been created.'
+ else
+ flash[:alert] = @token.errors.full_messages.join(', ').html_safe
+ end
+
+ redirect_to_repository_settings(@project)
+ end
+
+ def revoke
+ @token = @project.deploy_tokens.find(params[:id])
+ @token.revoke!
+
+ redirect_to_repository_settings(@project)
+ end
+
+ private
+
+ def deploy_token_params
+ params.require(:deploy_token).permit(:name, :expires_at, scopes: [])
+ end
+
+ def authorize_admin_project!
+ return render_404 unless can?(current_user, :admin_project, @project)
+ end
+end
diff --git a/app/controllers/projects/settings/repository_controller.rb b/app/controllers/projects/settings/repository_controller.rb
index d06d18c498b..60908359e9d 100644
--- a/app/controllers/projects/settings/repository_controller.rb
+++ b/app/controllers/projects/settings/repository_controller.rb
@@ -5,6 +5,7 @@ module Projects
def show
@deploy_keys = DeployKeysPresenter.new(@project, current_user: current_user)
+ @deploy_tokens = DeployTokensPresenter.new(@project.deploy_tokens.active, current_user: current_user, project: project)
define_protected_refs
end
diff --git a/app/models/deploy_token.rb b/app/models/deploy_token.rb
new file mode 100644
index 00000000000..f46a4e6229b
--- /dev/null
+++ b/app/models/deploy_token.rb
@@ -0,0 +1,25 @@
+class DeployToken < ActiveRecord::Base
+ include Expirable
+ include TokenAuthenticatable
+ add_authentication_token_field :token
+
+ AVAILABLE_SCOPES = %w(read_repo read_registry).freeze
+
+ serialize :scopes, Array
+
+ validates :scopes, presence: true
+
+ belongs_to :project
+
+ before_save :ensure_token
+
+ scope :active, -> { where("revoked = false AND (expires_at >= NOW() OR expires_at IS NULL)") }
+
+ def revoke!
+ update!(revoked: true)
+ end
+
+ def self.redis_shared_state_key(user_id)
+ "gitlab:personal_access_token:#{user_id}"
+ end
+end
diff --git a/app/models/project.rb b/app/models/project.rb
index e5ede967668..ad336df5f3c 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -218,6 +218,7 @@ class Project < ActiveRecord::Base
has_many :environments
has_many :deployments
has_many :pipeline_schedules, class_name: 'Ci::PipelineSchedule'
+ has_many :deploy_tokens
has_many :active_runners, -> { active }, through: :runner_projects, source: :runner, class_name: 'Ci::Runner'
diff --git a/app/presenters/deploy_token_presenter.rb b/app/presenters/deploy_token_presenter.rb
new file mode 100644
index 00000000000..01949d4edc3
--- /dev/null
+++ b/app/presenters/deploy_token_presenter.rb
@@ -0,0 +1,17 @@
+class DeployTokenPresenter < Gitlab::View::Presenter::Delegated
+ presents :deploy_token
+
+ def new_deploy_token
+ Gitlab::Redis::SharedState.with do |redis|
+ token = redis.get(deploy_token_key)
+ redis.del(deploy_token_key)
+ token
+ end
+ end
+
+ private
+
+ def deploy_token_key
+ DeployToken.redis_shared_state_key(current_user.id)
+ end
+end
diff --git a/app/presenters/projects/settings/deploy_tokens_presenter.rb b/app/presenters/projects/settings/deploy_tokens_presenter.rb
new file mode 100644
index 00000000000..b210e8a601a
--- /dev/null
+++ b/app/presenters/projects/settings/deploy_tokens_presenter.rb
@@ -0,0 +1,52 @@
+module Projects
+ module Settings
+ class DeployTokensPresenter < Gitlab::View::Presenter::Simple
+ include Enumerable
+
+ presents :deploy_tokens
+
+ def available_scopes
+ DeployToken::AVAILABLE_SCOPES
+ end
+
+ def new_token
+ @new_token ||= project.deploy_tokens.build
+ end
+
+ def length
+ deploy_tokens.length
+ end
+
+ def scope_description(scope)
+ scope_descriptions[scope]
+ end
+
+ def each
+ deploy_tokens.each do |deploy_token|
+ yield DeployTokenPresenter.new(deploy_token, current_user: current_user)
+ end
+ end
+
+ def new_deploy_token
+ Gitlab::Redis::SharedState.with do |redis|
+ token = redis.get(deploy_token_key)
+ redis.del(deploy_token_key)
+ token
+ end
+ end
+
+ private
+
+ def scope_descriptions
+ {
+ 'read_repo' => 'Allows read-only access to the repository',
+ 'read_registry' => 'Allows read-only access to the registry images'
+ }
+ end
+
+ def deploy_token_key
+ DeployToken.redis_shared_state_key(current_user.id)
+ end
+ end
+ end
+end
diff --git a/app/services/deploy_tokens/create_service.rb b/app/services/deploy_tokens/create_service.rb
new file mode 100644
index 00000000000..679425fa278
--- /dev/null
+++ b/app/services/deploy_tokens/create_service.rb
@@ -0,0 +1,24 @@
+module DeployTokens
+ class CreateService < BaseService
+ REDIS_EXPIRY_TIME = 3.minutes
+
+ def execute
+ @deploy_token = @project.deploy_tokens.create(params)
+ store_in_redis if @deploy_token.persisted?
+
+ @deploy_token
+ end
+
+ private
+
+ def store_in_redis
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.set(deploy_token_key, @deploy_token.token, ex: REDIS_EXPIRY_TIME)
+ end
+ end
+
+ def deploy_token_key
+ DeployToken.redis_shared_state_key(current_user.id)
+ end
+ end
+end
diff --git a/app/views/profiles/personal_access_tokens/index.html.haml b/app/views/profiles/personal_access_tokens/index.html.haml
index 78848542810..7bf76a296d2 100644
--- a/app/views/profiles/personal_access_tokens/index.html.haml
+++ b/app/views/profiles/personal_access_tokens/index.html.haml
@@ -2,7 +2,6 @@
- page_title "Personal Access Tokens"
- @content_class = "limit-container-width" unless fluid_layout
-
.row.prepend-top-default
.col-lg-4.profile-settings-sidebar
%h4.prepend-top-0
diff --git a/app/views/projects/deploy_tokens/_form.html.haml b/app/views/projects/deploy_tokens/_form.html.haml
new file mode 100644
index 00000000000..6f1c892bce0
--- /dev/null
+++ b/app/views/projects/deploy_tokens/_form.html.haml
@@ -0,0 +1,21 @@
+%p.profile-settings-content
+ Pick a name for the application, and we'll give you a unique deploy token.
+
+= form_for token, url: project_deploy_tokens_path(project), method: :post do |f|
+ = form_errors(token)
+
+ .form-group
+ = f.label :name, class: 'label-light'
+ = f.text_field :name, class: "form-control", required: true
+
+ .form-group
+ = f.label :expires_at, class: 'label-light'
+ = f.text_field :expires_at, class: "datepicker form-control"
+
+ .form-group
+ = f.label :scopes, class: 'label-light'
+ - presenter.available_scopes.each do |scope|
+ = render 'projects/deploy_tokens/scope_form', token: token, scope: scope, presenter: presenter
+
+ .prepend-top-default
+ = f.submit "Create deploy token", class: "btn btn-create"
diff --git a/app/views/projects/deploy_tokens/_index.html.haml b/app/views/projects/deploy_tokens/_index.html.haml
new file mode 100644
index 00000000000..a2a4229d5cd
--- /dev/null
+++ b/app/views/projects/deploy_tokens/_index.html.haml
@@ -0,0 +1,17 @@
+- expanded = Rails.env.test?
+
+%section.settings.no-animate{ class: ('expanded' if expanded) }
+ .settings-header
+ %h4
+ Deploy Tokens
+ %button.btn.js-settings-toggle.qa-expand-deploy-keys
+ = expanded ? 'Collapse' : 'Expand'
+ %p
+ Deploy tokens allow read-only access to your repository and registry images.
+ .settings-content
+
+ %h5.prepend-top-0
+ Add a deploy token
+ = render 'projects/deploy_tokens/form', project: @project, token: @deploy_tokens.new_token, presenter: @deploy_tokens
+ %hr
+ = render 'projects/deploy_tokens/table', project: @project, active_tokens: @deploy_tokens
diff --git a/app/views/projects/deploy_tokens/_scope_form.html.haml b/app/views/projects/deploy_tokens/_scope_form.html.haml
new file mode 100644
index 00000000000..f67701c8ee1
--- /dev/null
+++ b/app/views/projects/deploy_tokens/_scope_form.html.haml
@@ -0,0 +1,4 @@
+%fieldset
+ = check_box_tag "deploy_token[scopes][]", scope, token.scopes.include?(scope), id: "deploy_token_scopes_#{scope}"
+ = label_tag ("deploy_token_scopes_#{scope}"), scope
+ %span= presenter.scope_description(scope)
diff --git a/app/views/projects/deploy_tokens/_table.html.haml b/app/views/projects/deploy_tokens/_table.html.haml
new file mode 100644
index 00000000000..e089fcc2412
--- /dev/null
+++ b/app/views/projects/deploy_tokens/_table.html.haml
@@ -0,0 +1,28 @@
+%h5 Active Deploy Tokens (#{active_tokens.length})
+
+- if active_tokens.present?
+ .table-responsive
+ %table.table.active-tokens
+ %thead
+ %tr
+ %th Name
+ %th Created
+ %th Expires
+ %th Scopes
+ %th
+ %tbody
+ - active_tokens.each do |token|
+ %tr
+ %td= token.name
+ %td= token.created_at.to_date.to_s(:medium)
+ %td
+ - if token.expires?
+ %span{ class: ('text-warning' if token.expires_soon?) }
+ In #{distance_of_time_in_words_to_now(token.expires_at)}
+ - else
+ %span.token-never-expires-label Never
+ %td= token.scopes.present? ? token.scopes.join(", ") : "<no scopes selected>"
+ %td= link_to "Revoke", revoke_project_deploy_token_path(project, token), method: :put, class: "btn btn-danger pull-right", data: { confirm: "Are you sure you want to revoke this Deploy Token? This action cannot be undone." }
+- else
+ .settings-message.text-center
+ This project has no active Deploy Tokens.
diff --git a/app/views/projects/deploy_tokens/_temporal_deploy_token.html.haml b/app/views/projects/deploy_tokens/_temporal_deploy_token.html.haml
new file mode 100644
index 00000000000..34b4da39dbd
--- /dev/null
+++ b/app/views/projects/deploy_tokens/_temporal_deploy_token.html.haml
@@ -0,0 +1,8 @@
+.created-project-deploy-token-container
+ %h5.prepend-top-0
+ Your New Project Deploy Token
+ .form-group
+ = text_field_tag 'created-project-deploy-token', token, readonly: true, class: "form-control js-select-on-focus", 'aria-describedby' => "created-project-deploy-token-help-block"
+ = clipboard_button(text: token, title: "Copy project deploy token to clipboard", placement: "left")
+ %span#created-project-deploy-token-help-block.help-block.text-danger Make sure you save it - you won't be able to access it again.
+%hr
diff --git a/app/views/projects/settings/repository/show.html.haml b/app/views/projects/settings/repository/show.html.haml
index 6bef4d19434..f57590a908f 100644
--- a/app/views/projects/settings/repository/show.html.haml
+++ b/app/views/projects/settings/repository/show.html.haml
@@ -9,3 +9,4 @@
= render "projects/protected_branches/index"
= render "projects/protected_tags/index"
= render @deploy_keys
+= render "projects/deploy_tokens/index"
diff --git a/config/gitlab_backup.yml b/config/gitlab_backup.yml
new file mode 100644
index 00000000000..8c10bf348db
--- /dev/null
+++ b/config/gitlab_backup.yml
@@ -0,0 +1,756 @@
+# # # # # # # # # # # # # # # # # #
+# GitLab application config file #
+# # # # # # # # # # # # # # # # # #
+#
+########################### NOTE #####################################
+# This file should not receive new settings. All configuration options #
+# * are being moved to ApplicationSetting model! #
+# If a setting requires an application restart say so in that screen. #
+# If you change this file in a Merge Request, please also create #
+# a MR on https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests #
+########################################################################
+#
+#
+# How to use:
+# 1. Copy file as gitlab.yml
+# 2. Update gitlab -> host with your fully qualified domain name
+# 3. Update gitlab -> email_from
+# 4. If you installed Git from source, change git -> bin_path to /usr/local/bin/git
+# IMPORTANT: If Git was installed in a different location use that instead.
+# You can check with `which git`. If a wrong path of Git is specified, it will
+# result in various issues such as failures of GitLab CI builds.
+# 5. Review this configuration file for other settings you may want to adjust
+
+production: &base
+ #
+ # 1. GitLab app settings
+ # ==========================
+
+ ## GitLab settings
+ gitlab:
+ ## Web server settings (note: host is the FQDN, do not include http://)
+ host: localhost
+ port: 3000
+ https: false # Set to true if using HTTPS, see installation.md#using-https for additional HTTPS configuration details
+
+ # Uncommment this line below if your ssh host is different from HTTP/HTTPS one
+ # (you'd obviously need to replace ssh.host_example.com with your own host).
+ # Otherwise, ssh host will be set to the `host:` value above
+ # ssh_host: ssh.host_example.com
+
+ # Relative URL support
+ # WARNING: We recommend using an FQDN to host GitLab in a root path instead
+ # of using a relative URL.
+ # Documentation: http://doc.gitlab.com/ce/install/relative_url.html
+ # Uncomment and customize the following line to run in a non-root path
+ #
+ # relative_url_root: /gitlab
+
+ # Trusted Proxies
+ # Customize if you have GitLab behind a reverse proxy which is running on a different machine.
+ # Add the IP address for your reverse proxy to the list, otherwise users will appear signed in from that address.
+ trusted_proxies:
+ # Examples:
+ #- 192.168.1.0/24
+ #- 192.168.2.1
+ #- 2001:0db8::/32
+
+ # Uncomment and customize if you can't use the default user to run GitLab (default: 'git')
+ user: mayra-cabrera
+
+ ## Date & Time settings
+ # Uncomment and customize if you want to change the default time zone of GitLab application.
+ # To see all available zones, run `bundle exec rake time:zones:all RAILS_ENV=production`
+ # time_zone: 'UTC'
+
+ ## Email settings
+ # Uncomment and set to false if you need to disable email sending from GitLab (default: true)
+ # email_enabled: true
+ # Email address used in the "From" field in mails sent by GitLab
+ email_from: example@example.com
+ email_display_name: GitLab
+ email_reply_to: noreply@example.com
+ email_subject_suffix: ''
+
+ # Email server smtp settings are in config/initializers/smtp_settings.rb.sample
+
+ # default_can_create_group: false # default: true
+ # username_changing_enabled: false # default: true - User can change her username/namespace
+ ## Default theme ID
+ ## 1 - Indigo
+ ## 2 - Dark
+ ## 3 - Light
+ ## 4 - Blue
+ ## 5 - Green
+ # default_theme: 1 # default: 1
+
+ ## Automatic issue closing
+ # If a commit message matches this regular expression, all issues referenced from the matched text will be closed.
+ # This happens when the commit is pushed or merged into the default branch of a project.
+ # When not specified the default issue_closing_pattern as specified below will be used.
+ # Tip: you can test your closing pattern at http://rubular.com.
+ # issue_closing_pattern: '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing)|[Ii]mplement(?:s|ed|ing)?)(:?) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?)|([A-Z][A-Z0-9_]+-\d+))+)'
+
+ ## Default project features settings
+ default_projects_features:
+ issues: true
+ merge_requests: true
+ wiki: true
+ snippets: true
+ builds: true
+ container_registry: true
+
+ ## Webhook settings
+ # Number of seconds to wait for HTTP response after sending webhook HTTP POST request (default: 10)
+ # webhook_timeout: 10
+
+ ## Repository downloads directory
+ # When a user clicks e.g. 'Download zip' on a project, a temporary zip file is created in the following directory.
+ # The default is 'shared/cache/archive/' relative to the root of the Rails app.
+ # repository_downloads_path: shared/cache/archive/
+
+ ## Reply by email
+ # Allow users to comment on issues and merge requests by replying to notification emails.
+ # For documentation on how to set this up, see http://doc.gitlab.com/ce/administration/reply_by_email.html
+ incoming_email:
+ enabled: false
+
+ # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to.
+ # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`).
+ address: "gitlab-incoming+%{key}@gmail.com"
+
+ # Email account username
+ # With third party providers, this is usually the full email address.
+ # With self-hosted email servers, this is usually the user part of the email address.
+ user: "gitlab-incoming@gmail.com"
+ # Email account password
+ password: "[REDACTED]"
+
+ # IMAP server host
+ host: "imap.gmail.com"
+ # IMAP server port
+ port: 993
+ # Whether the IMAP server uses SSL
+ ssl: true
+ # Whether the IMAP server uses StartTLS
+ start_tls: false
+
+ # The mailbox where incoming mail will end up. Usually "inbox".
+ mailbox: "inbox"
+ # The IDLE command timeout.
+ idle_timeout: 60
+
+ ## Build Artifacts
+ artifacts:
+ enabled: true
+ # The location where build artifacts are stored (default: shared/artifacts).
+ # path: shared/artifacts
+
+ ## Git LFS
+ lfs:
+ enabled: true
+ # The location where LFS objects are stored (default: shared/lfs-objects).
+ # storage_path: shared/lfs-objects
+
+ ## GitLab Pages
+ pages:
+ enabled: false
+ # The location where pages are stored (default: shared/pages).
+ # path: shared/pages
+
+ # The domain under which the pages are served:
+ # http://group.example.com/project
+ # or project path can be a group page: group.example.com
+ host: example.com
+ port: 80 # Set to 443 if you serve the pages with HTTPS
+ https: false # Set to true if you serve the pages with HTTPS
+ artifacts_server: true
+ # external_http: ["1.1.1.1:80", "[2001::1]:80"] # If defined, enables custom domain support in GitLab Pages
+ # external_https: ["1.1.1.1:443", "[2001::1]:443"] # If defined, enables custom domain and certificate support in GitLab Pages
+
+ ## Mattermost
+ ## For enabling Add to Mattermost button
+ mattermost:
+ enabled: false
+ host: 'https://mattermost.example.com'
+
+ ## Gravatar
+ ## For Libravatar see: http://doc.gitlab.com/ce/customization/libravatar.html
+ gravatar:
+ # gravatar urls: possible placeholders: %{hash} %{size} %{email} %{username}
+ # plain_url: "http://..." # default: http://www.gravatar.com/avatar/%{hash}?s=%{size}&d=identicon
+ # ssl_url: "https://..." # default: https://secure.gravatar.com/avatar/%{hash}?s=%{size}&d=identicon
+
+ ## Auxiliary jobs
+ # Periodically executed jobs, to self-heal Gitlab, do external synchronizations, etc.
+ # Please read here for more information: https://github.com/ondrejbartas/sidekiq-cron#adding-cron-job
+ cron_jobs:
+ # Flag stuck CI jobs as failed
+ stuck_ci_jobs_worker:
+ cron: "0 * * * *"
+ # Execute scheduled triggers
+ pipeline_schedule_worker:
+ cron: "19 * * * *"
+ # Remove expired build artifacts
+ expire_build_artifacts_worker:
+ cron: "50 * * * *"
+ # Periodically run 'git fsck' on all repositories. If started more than
+ # once per hour you will have concurrent 'git fsck' jobs.
+ repository_check_worker:
+ cron: "20 * * * *"
+ # Send admin emails once a week
+ admin_email_worker:
+ cron: "0 0 * * 0"
+
+ # Remove outdated repository archives
+ repository_archive_cache_worker:
+ cron: "0 * * * *"
+
+ registry:
+ enabled: false
+ host: 127.0.0.1
+ port: 5000
+ api_url: http://127.0.0.1:5000/
+ key: ../localhost.key
+ path: ../registry/storage/
+ issuer: gitlab-issuer
+ # enabled: true
+ # host: registry.example.com
+ # port: 5005
+ # api_url: http://localhost:5000/ # internal address to the registry, will be used by GitLab to directly communicate with API
+ # key: config/registry.key
+ # path: shared/registry
+ # issuer: gitlab-issuer
+
+ #
+ # 2. GitLab CI settings
+ # ==========================
+
+ gitlab_ci:
+ # Default project notifications settings:
+ #
+ # Send emails only on broken builds (default: true)
+ # all_broken_builds: true
+ #
+ # Add pusher to recipients list (default: false)
+ # add_pusher: true
+
+ # The location where build traces are stored (default: builds/). Relative paths are relative to Rails.root
+ # builds_path: builds/
+
+ #
+ # 3. Auth settings
+ # ==========================
+
+ ## LDAP settings
+ # You can test connections and inspect a sample of the LDAP users with login
+ # access by running:
+ # bundle exec rake gitlab:ldap:check RAILS_ENV=production
+ ldap:
+ enabled: false
+ servers:
+ ##########################################################################
+ #
+ # Since GitLab 7.4, LDAP servers get ID's (below the ID is 'main'). GitLab
+ # Enterprise Edition now supports connecting to multiple LDAP servers.
+ #
+ # If you are updating from the old (pre-7.4) syntax, you MUST give your
+ # old server the ID 'main'.
+ #
+ ##########################################################################
+ main: # 'main' is the GitLab 'provider ID' of this LDAP server
+ ## label
+ #
+ # A human-friendly name for your LDAP server. It is OK to change the label later,
+ # for instance if you find out it is too large to fit on the web page.
+ #
+ # Example: 'Paris' or 'Acme, Ltd.'
+ label: 'LDAP'
+
+ # Example: 'ldap.mydomain.com'
+ host: '_your_ldap_server'
+ # This port is an example, it is sometimes different but it is always an integer and not a string
+ port: 389 # usually 636 for SSL
+ uid: 'sAMAccountName' # This should be the attribute, not the value that maps to uid.
+
+ # Examples: 'america\\momo' or 'CN=Gitlab Git,CN=Users,DC=mydomain,DC=com'
+ bind_dn: '_the_full_dn_of_the_user_you_will_bind_with'
+ password: '_the_password_of_the_bind_user'
+
+ # Encryption method. The "method" key is deprecated in favor of
+ # "encryption".
+ #
+ # Examples: "start_tls" or "simple_tls" or "plain"
+ #
+ # Deprecated values: "tls" was replaced with "start_tls" and "ssl" was
+ # replaced with "simple_tls".
+ #
+ encryption: 'plain'
+
+ # Enables SSL certificate verification if encryption method is
+ # "start_tls" or "simple_tls". Defaults to true.
+ verify_certificates: true
+
+ # Specifies the path to a file containing a PEM-format CA certificate,
+ # e.g. if you need to use an internal CA.
+ #
+ # Example: '/etc/ca.pem'
+ #
+ ca_file: ''
+
+ # Specifies the SSL version for OpenSSL to use, if the OpenSSL default
+ # is not appropriate.
+ #
+ # Example: 'TLSv1_1'
+ #
+ ssl_version: ''
+
+ # Set a timeout, in seconds, for LDAP queries. This helps avoid blocking
+ # a request if the LDAP server becomes unresponsive.
+ # A value of 0 means there is no timeout.
+ timeout: 10
+
+ # 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.
+ active_directory: true
+
+ # If allow_username_or_email_login is enabled, GitLab will ignore everything
+ # after the first '@' in the LDAP username submitted by the user on login.
+ #
+ # Example:
+ # - the user enters 'jane.doe@example.com' and 'p@ssw0rd' as LDAP credentials;
+ # - GitLab queries the LDAP server with 'jane.doe' and 'p@ssw0rd'.
+ #
+ # If you are using "uid: 'userPrincipalName'" on ActiveDirectory you need to
+ # disable this setting, because the userPrincipalName contains an '@'.
+ allow_username_or_email_login: false
+
+ # To maintain tight control over the number of active users on your GitLab installation,
+ # enable this setting to keep new users blocked until they have been cleared by the admin
+ # (default: false).
+ block_auto_created_users: false
+
+ # Base where we can search for users
+ #
+ # Ex. 'ou=People,dc=gitlab,dc=example' or 'DC=mydomain,DC=com'
+ #
+ base: ''
+
+ # Filter LDAP users
+ #
+ # Format: RFC 4515 https://tools.ietf.org/search/rfc4515
+ # Ex. (employeeType=developer)
+ #
+ # Note: GitLab does not support omniauth-ldap's custom filter syntax.
+ #
+ # Example for getting only specific users:
+ # '(&(objectclass=user)(|(samaccountname=momo)(samaccountname=toto)))'
+ #
+ user_filter: ''
+
+ # 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']).
+ # Note that the user's LDAP login will always be the attribute specified as `uid` above.
+ attributes:
+ # The username will be used in paths for the user's own projects
+ # (like `gitlab.example.com/username/project`) and when mentioning
+ # them in issues, merge request and comments (like `@username`).
+ # If the attribute specified for `username` contains an email address,
+ # the GitLab username will be the part of the email address before the '@'.
+ username: ['uid', 'userid', 'sAMAccountName']
+ email: ['mail', 'email', 'userPrincipalName']
+
+ # If no full name could be found at the attribute specified for `name`,
+ # the full name is determined using the attributes specified for
+ # `first_name` and `last_name`.
+ name: 'cn'
+ first_name: 'givenName'
+ last_name: 'sn'
+
+ # GitLab EE only: add more LDAP servers
+ # Choose an ID made of a-z and 0-9 . This ID will be stored in the database
+ # so that GitLab can remember which LDAP server a user belongs to.
+ # uswest2:
+ # label:
+ # host:
+ # ....
+
+
+ ## OmniAuth settings
+ omniauth:
+ # Allow login via Twitter, Google, etc. using OmniAuth providers
+ enabled: false
+
+ # Uncomment this to automatically sign in with a specific omniauth provider's without
+ # showing GitLab's sign-in page (default: show the GitLab sign-in page)
+ # auto_sign_in_with_provider: saml
+
+ # Sync user's profile from the specified Omniauth providers every time the user logs in (default: empty).
+ # Define the allowed providers using an array, e.g. ["cas3", "saml", "twitter"],
+ # or as true/false to allow all providers or none.
+ # When authenticating using LDAP, the user's email is always synced.
+ # sync_profile_from_provider: []
+
+ # Select which info to sync from the providers above. (default: email).
+ # Define the synced profile info using an array. Available options are "name", "email" and "location"
+ # e.g. ["name", "email", "location"] or as true to sync all available.
+ # This consequently will make the selected attributes read-only.
+ # sync_profile_attributes: true
+
+ # CAUTION!
+ # This allows users to login without having a user account first. Define the allowed providers
+ # using an array, e.g. ["saml", "twitter"], or as true/false to allow all providers or none.
+ # User accounts will be created automatically when authentication was successful.
+ allow_single_sign_on: ["saml"]
+
+ # Locks down those users until they have been cleared by the admin (default: true).
+ block_auto_created_users: true
+ # Look up new users in LDAP servers. If a match is found (same uid), automatically
+ # link the omniauth identity with the LDAP account. (default: false)
+ auto_link_ldap_user: false
+
+ # Allow users with existing accounts to login and auto link their account via SAML
+ # login, without having to do a manual login first and manually add SAML
+ # (default: false)
+ auto_link_saml_user: false
+
+ # Set different Omniauth providers as external so that all users creating accounts
+ # via these providers will not be able to have access to internal projects. You
+ # will need to use the full name of the provider, like `google_oauth2` for Google.
+ # Refer to the examples below for the full names of the supported providers.
+ # (default: [])
+ external_providers: []
+
+ ## Auth providers
+ # Uncomment the following lines and fill in the data of the auth provider you want to use
+ # If your favorite auth provider is not listed you can use others:
+ # see https://github.com/gitlabhq/gitlab-public-wiki/wiki/Custom-omniauth-provider-configurations
+ # The 'app_id' and 'app_secret' parameters are always passed as the first two
+ # arguments, followed by optional 'args' which can be either a hash or an array.
+ # Documentation for this is available at http://doc.gitlab.com/ce/integration/omniauth.html
+ providers:
+ # See omniauth-cas3 for more configuration details
+ # - { name: 'cas3',
+ # label: 'cas3',
+ # args: {
+ # url: 'https://sso.example.com',
+ # disable_ssl_verification: false,
+ # login_url: '/cas/login',
+ # service_validate_url: '/cas/p3/serviceValidate',
+ # logout_url: '/cas/logout'} }
+ # - { name: 'authentiq',
+ # # for client credentials (client ID and secret), go to https://www.authentiq.com/developers
+ # app_id: 'YOUR_CLIENT_ID',
+ # app_secret: 'YOUR_CLIENT_SECRET',
+ # args: {
+ # scope: 'aq:name email~rs address aq:push'
+ # # callback_url parameter is optional except when 'gitlab.host' in this file is set to 'localhost'
+ # # callback_url: 'YOUR_CALLBACK_URL'
+ # }
+ # }
+ # - { name: 'github',
+ # app_id: 'YOUR_APP_ID',
+ # app_secret: 'YOUR_APP_SECRET',
+ # url: "https://github.com/",
+ # verify_ssl: true,
+ # args: { scope: 'user:email' } }
+ # - { name: 'bitbucket',
+ # app_id: 'YOUR_APP_ID',
+ # app_secret: 'YOUR_APP_SECRET' }
+ # - { name: 'gitlab',
+ # app_id: 'YOUR_APP_ID',
+ # app_secret: 'YOUR_APP_SECRET',
+ # args: { scope: 'api' } }
+ - { name: 'google_oauth2',
+ app_id: '181722811143-nkfe4nmc75i1vm5qtjmtr128ji1e065g.apps.googleusercontent.com',
+ app_secret: 'OdIwJ94_DTWuQm09O5zTX5mQ',
+ args: { access_type: 'offline', approval_prompt: '' } }
+ # - { name: 'facebook',
+ # app_id: 'YOUR_APP_ID',
+ # app_secret: 'YOUR_APP_SECRET' }
+ # - { name: 'twitter',
+ # app_id: 'YOUR_APP_ID',
+ # app_secret: 'YOUR_APP_SECRET' }
+ #
+ # - { name: 'saml',
+ # label: 'Our SAML Provider',
+ # groups_attribute: 'Groups',
+ # external_groups: ['Contractors', 'Freelancers'],
+ # args: {
+ # assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
+ # idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
+ # idp_sso_target_url: 'https://login.example.com/idp',
+ # issuer: 'https://gitlab.example.com',
+ # name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
+ # } }
+ #
+ # - { name: 'crowd',
+ # args: {
+ # crowd_server_url: 'CROWD SERVER URL',
+ # application_name: 'YOUR_APP_NAME',
+ # application_password: 'YOUR_APP_PASSWORD' } }
+ #
+ # - { name: 'auth0',
+ # args: {
+ # client_id: 'YOUR_AUTH0_CLIENT_ID',
+ # client_secret: 'YOUR_AUTH0_CLIENT_SECRET',
+ # namespace: 'YOUR_AUTH0_DOMAIN' } }
+
+ # SSO maximum session duration in seconds. Defaults to CAS default of 8 hours.
+ # cas3:
+ # session_duration: 28800
+
+ # Shared file storage settings
+ shared:
+ # path: /mnt/gitlab # Default: shared
+
+ # Gitaly settings
+ gitaly:
+ # Path to the directory containing Gitaly client executables.
+ client_path: /Users/mayra-cabrera/Documents/code/gitlab-development-kit/gitaly/bin
+ # Default Gitaly authentication token. Can be overriden per storage. Can
+ # be left blank when Gitaly is running locally on a Unix socket, which
+ # is the normal way to deploy Gitaly.
+ token:
+
+ #
+ # 4. Advanced settings
+ # ==========================
+
+ ## Repositories settings
+ repositories:
+ # Paths where repositories can be stored. Give the canonicalized absolute pathname.
+ # IMPORTANT: None of the path components may be symlink, because
+ # gitlab-shell invokes Dir.pwd inside the repository path and that results
+ # real path not the symlink.
+ storages: # You must have at least a `default` storage path.
+ default:
+ path: /Users/mayra-cabrera/Documents/code/gitlab-development-kit/repositories/
+ gitaly_address: unix:/Users/mayra-cabrera/Documents/code/gitlab-development-kit/gitaly.socket
+ # gitaly_token: 'special token' # Optional: override global gitaly.token for this storage.
+
+ ## Backup settings
+ backup:
+ path: "tmp/backups" # Relative paths are relative to Rails.root (default: tmp/backups/)
+ # archive_permissions: 0640 # Permissions for the resulting backup.tar file (default: 0600)
+ # keep_time: 604800 # default: 0 (forever) (in seconds)
+ # pg_schema: public # default: nil, it means that all schemas will be backed up
+ # upload:
+ # # Fog storage connection settings, see http://fog.io/storage/ .
+ # connection:
+ # provider: AWS
+ # region: eu-west-1
+ # aws_access_key_id: AKIAKIAKI
+ # aws_secret_access_key: 'secret123'
+ # # The remote 'directory' to store your backups. For S3, this would be the bucket name.
+ # remote_directory: 'my.s3.bucket'
+ # # Use multipart uploads when file size reaches 100MB, see
+ # # http://docs.aws.amazon.com/AmazonS3/latest/dev/uploadobjusingmpu.html
+ # multipart_chunk_size: 104857600
+ # # Turns on AWS Server-Side Encryption with Amazon S3-Managed Keys for backups, this is optional
+ # # encryption: 'AES256'
+ # # Specifies Amazon S3 storage class to use for backups, this is optional
+ # # storage_class: 'STANDARD'
+
+ ## GitLab Shell settings
+ gitlab_shell:
+ ssh_port: 2222
+ ssh_host: localhost
+ path: /Users/mayra-cabrera/Documents/code/gitlab-development-kit/gitlab-shell/
+ hooks_path: /Users/mayra-cabrera/Documents/code/gitlab-development-kit/gitlab-shell/hooks/
+
+ # File that contains the secret key for verifying access for gitlab-shell.
+ # Default is '.gitlab_shell_secret' relative to Rails.root (i.e. root of the GitLab app).
+ # secret_file: /Users/mayra-cabrera/Documents/code/gitlab-development-kit/gitlab/.gitlab_shell_secret
+
+ # Git over HTTP
+ upload_pack: true
+ receive_pack: true
+
+ # Git import/fetch timeout, in seconds. Defaults to 3 hours.
+ # git_timeout: 10800
+
+ # If you use non-standard ssh port you need to specify it
+ # ssh_port: 22
+
+ workhorse:
+ # File that contains the secret key for verifying access for gitlab-workhorse.
+ # Default is '.gitlab_workhorse_secret' relative to Rails.root (i.e. root of the GitLab app).
+ # secret_file: /Users/mayra-cabrera/Documents/code/gitlab-development-kit/gitlab/.gitlab_workhorse_secret
+
+ ## Git settings
+ # CAUTION!
+ # Use the default values unless you really know what you are doing
+ git:
+ bin_path: /usr/local/bin/git
+
+ ## Webpack settings
+ # If enabled, this will tell rails to serve frontend assets from the webpack-dev-server running
+ # on a given port instead of serving directly from /assets/webpack. This is only indended for use
+ # in development.
+ webpack:
+ dev_server:
+ enabled: true
+ host: localhost
+ port: 3808
+ # dev_server:
+ # enabled: true
+ # host: localhost
+ # port: 3808
+
+ ## Monitoring
+ # Built in monitoring settings
+ monitoring:
+ # Time between sampling of unicorn socket metrics, in seconds
+ # unicorn_sampler_interval: 10
+ # IP whitelist to access monitoring endpoints
+ ip_whitelist:
+ - 127.0.0.0/8
+
+ # Sidekiq exporter is webserver built in to Sidekiq to expose Prometheus metrics
+ sidekiq_exporter:
+ # enabled: true
+ # address: localhost
+ # port: 3807
+
+ #
+ # 5. Extra customization
+ # ==========================
+
+ extra:
+ ## Google analytics. Uncomment if you want it
+ # google_analytics_id: '_your_tracking_id'
+
+ ## Piwik analytics.
+ # piwik_url: '_your_piwik_url'
+ # piwik_site_id: '_your_piwik_site_id'
+
+ rack_attack:
+ git_basic_auth:
+ # Rack Attack IP banning enabled
+ # enabled: true
+ #
+ # Whitelist requests from 127.0.0.1 for web proxies (NGINX/Apache) with incorrect headers
+ # ip_whitelist: ["127.0.0.1"]
+ #
+ # Limit the number of Git HTTP authentication attempts per IP
+ # maxretry: 10
+ #
+ # Reset the auth attempt counter per IP after 60 seconds
+ # findtime: 60
+ #
+ # Ban an IP for one hour (3600s) after too many auth attempts
+ # bantime: 3600
+
+development:
+ <<: *base
+
+test:
+ <<: *base
+ gravatar:
+ enabled: true
+ lfs:
+ enabled: false
+ gitlab:
+ host: localhost
+ port: 80
+
+ # When you run tests we clone and setup gitlab-shell
+ # In order to setup it correctly you need to specify
+ # your system username you use to run GitLab
+ # user: YOUR_USERNAME
+ pages:
+ path: tmp/tests/pages
+ artifacts:
+ path: tmp/tests/artifacts
+ repositories:
+ storages:
+ default:
+ path: tmp/tests/repositories/
+ gitaly_address: unix:tmp/tests/gitaly/gitaly.socket
+ broken:
+ path: tmp/tests/non-existent-repositories
+ gitaly_address: unix:tmp/tests/gitaly/gitaly.socket
+
+ gitaly:
+ client_path: tmp/tests/gitaly
+ token: secret
+ backup:
+ path: tmp/tests/backups
+ gitlab_shell:
+ path: tmp/tests/gitlab-shell/
+ hooks_path: tmp/tests/gitlab-shell/hooks/
+ issues_tracker:
+ redmine:
+ title: "Redmine"
+ project_url: "http://redmine/projects/:issues_tracker_id"
+ issues_url: "http://redmine/:project_id/:issues_tracker_id/:id"
+ new_issue_url: "http://redmine/projects/:issues_tracker_id/issues/new"
+ jira:
+ title: "JIRA"
+ url: https://sample_company.atlassian.net
+ project_key: PROJECT
+
+ omniauth:
+ enabled: true
+ allow_single_sign_on: true
+ external_providers: []
+
+ providers:
+ - { name: 'cas3',
+ label: 'cas3',
+ args: { url: 'https://sso.example.com',
+ disable_ssl_verification: false,
+ login_url: '/cas/login',
+ service_validate_url: '/cas/p3/serviceValidate',
+ logout_url: '/cas/logout'} }
+ - { name: 'github',
+ app_id: 'YOUR_APP_ID',
+ app_secret: 'YOUR_APP_SECRET',
+ url: "https://github.com/",
+ verify_ssl: false,
+ args: { scope: 'user:email' } }
+ - { name: 'bitbucket',
+ app_id: 'YOUR_APP_ID',
+ app_secret: 'YOUR_APP_SECRET' }
+ - { name: 'gitlab',
+ app_id: 'YOUR_APP_ID',
+ app_secret: 'YOUR_APP_SECRET',
+ args: { scope: 'api' } }
+ - { name: 'google_oauth2',
+ app_id: 'YOUR_APP_ID',
+ app_secret: 'YOUR_APP_SECRET',
+ args: { access_type: 'offline', approval_prompt: '' } }
+ - { name: 'facebook',
+ app_id: 'YOUR_APP_ID',
+ app_secret: 'YOUR_APP_SECRET' }
+ - { name: 'twitter',
+ app_id: 'YOUR_APP_ID',
+ app_secret: 'YOUR_APP_SECRET' }
+ - { name: 'auth0',
+ args: {
+ client_id: 'YOUR_AUTH0_CLIENT_ID',
+ client_secret: 'YOUR_AUTH0_CLIENT_SECRET',
+ namespace: 'YOUR_AUTH0_DOMAIN' } }
+ - { name: 'authentiq',
+ app_id: 'YOUR_CLIENT_ID',
+ app_secret: 'YOUR_CLIENT_SECRET',
+ args: { scope: 'aq:name email~rs address aq:push' } }
+ ldap:
+ enabled: false
+ servers:
+ main:
+ label: ldap
+ host: 127.0.0.1
+ port: 3890
+ uid: 'uid'
+ encryption: 'plain' # "start_tls" or "simple_tls" or "plain"
+ base: 'dc=example,dc=com'
+ user_filter: ''
+ group_base: 'ou=groups,dc=example,dc=com'
+ admin_group: ''
+
+staging:
+ <<: *base
diff --git a/config/routes/project.rb b/config/routes/project.rb
index c803737d40b..a385212a48c 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -88,6 +88,12 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
end
+ resources :deploy_tokens, constraints: { id: /\d+/ }, only: :create do
+ member do
+ put :revoke
+ end
+ end
+
resources :forks, only: [:index, :new, :create]
resource :import, only: [:new, :create, :show]
diff --git a/db/migrate/20180319190020_create_deploy_tokens.rb b/db/migrate/20180319190020_create_deploy_tokens.rb
new file mode 100644
index 00000000000..ffcea3676e5
--- /dev/null
+++ b/db/migrate/20180319190020_create_deploy_tokens.rb
@@ -0,0 +1,14 @@
+class CreateDeployTokens < ActiveRecord::Migration
+ def change
+ create_table :deploy_tokens do |t|
+ t.references :project, index: true, foreign_key: true, null: false
+ t.string :name, null: false
+ t.string :token, index: { unique: true }, null: false
+ t.string :scopes
+ t.boolean :revoked, default: false
+ t.datetime :expires_at
+
+ t.timestamps null: false
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 3ff1a8754e2..c8ad32ce6e3 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20180309160427) do
+ActiveRecord::Schema.define(version: 20180319190020) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -668,6 +668,20 @@ ActiveRecord::Schema.define(version: 20180309160427) do
add_index "deploy_keys_projects", ["project_id"], name: "index_deploy_keys_projects_on_project_id", using: :btree
+ create_table "deploy_tokens", force: :cascade do |t|
+ t.integer "project_id", null: false
+ t.string "name", null: false
+ t.string "token", null: false
+ t.string "scopes"
+ t.boolean "revoked", default: false
+ t.datetime "expires_at"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ end
+
+ add_index "deploy_tokens", ["project_id"], name: "index_deploy_tokens_on_project_id", using: :btree
+ add_index "deploy_tokens", ["token"], name: "index_deploy_tokens_on_token", unique: true, using: :btree
+
create_table "deployments", force: :cascade do |t|
t.integer "iid", null: false
t.integer "project_id", null: false
@@ -2048,6 +2062,7 @@ ActiveRecord::Schema.define(version: 20180309160427) do
add_foreign_key "clusters_applications_runners", "clusters", on_delete: :cascade
add_foreign_key "container_repositories", "projects"
add_foreign_key "deploy_keys_projects", "projects", name: "fk_58a901ca7e", on_delete: :cascade
+ add_foreign_key "deploy_tokens", "projects"
add_foreign_key "deployments", "projects", name: "fk_b9a3851b82", on_delete: :cascade
add_foreign_key "environments", "projects", name: "fk_d1c8c1da6a", on_delete: :cascade
add_foreign_key "events", "projects", on_delete: :cascade
diff --git a/spec/controllers/projects/deploy_tokens_controller_spec.rb b/spec/controllers/projects/deploy_tokens_controller_spec.rb
new file mode 100644
index 00000000000..56ab4df7b79
--- /dev/null
+++ b/spec/controllers/projects/deploy_tokens_controller_spec.rb
@@ -0,0 +1,67 @@
+require 'spec_helper'
+
+describe Projects::DeployTokensController do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+ let!(:member) { project.add_master(user) }
+
+ before do
+ sign_in(user)
+ end
+
+ describe 'POST #create' do
+ let(:deploy_token_params) { attributes_for(:deploy_token) }
+ subject do
+ post :create,
+ namespace_id: project.namespace,
+ project_id: project,
+ deploy_token: deploy_token_params
+ end
+
+ context 'with valid params' do
+ it 'should create a new DeployToken' do
+ expect { subject }.to change(DeployToken, :count).by(1)
+ end
+
+ it 'should include a flash notice' do
+ subject
+ expect(flash[:notice]).to eq('Your new project deploy token has been created.')
+ end
+
+ it 'should redirect to repository settings' do
+ subject
+
+ expect(response).to redirect_to project_settings_repository_path(project)
+ end
+ end
+
+ context 'with invalid params' do
+ let(:deploy_token_params) { attributes_for(:deploy_token, scopes: []) }
+
+ it 'should not create a new DeployToken' do
+ expect { subject }.not_to change(DeployToken, :count)
+ end
+
+ it 'should include a flash alert with the error message' do
+ subject
+ expect(flash[:alert]).to eq("Scopes can't be blank")
+ end
+
+ it 'should redirect to repository settings' do
+ subject
+
+ expect(response).to redirect_to project_settings_repository_path(project)
+ end
+ end
+
+ context 'when user does not have enough permissions' do
+ let!(:member) { project.add_developer(user) }
+
+ it 'responds with status 404' do
+ subject
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+ end
+end
diff --git a/spec/factories/deploy_tokens.rb b/spec/factories/deploy_tokens.rb
new file mode 100644
index 00000000000..209a2d4370d
--- /dev/null
+++ b/spec/factories/deploy_tokens.rb
@@ -0,0 +1,23 @@
+FactoryBot.define do
+ factory :deploy_token do
+ project
+ token { SecureRandom.hex(50) }
+ sequence(:name) { |n| "PDT #{n}" }
+ revoked false
+ expires_at { 5.days.from_now }
+ scopes ['read_repo', 'read_registry']
+
+ trait :revoked do
+ revoked true
+ end
+
+ trait :read_repo do
+ scopes ['read_repo']
+ end
+
+ trait :read_registry do
+ scopes ['read_registry']
+ end
+ end
+end
+
diff --git a/spec/models/deploy_token_spec.rb b/spec/models/deploy_token_spec.rb
new file mode 100644
index 00000000000..82f97a7f694
--- /dev/null
+++ b/spec/models/deploy_token_spec.rb
@@ -0,0 +1,39 @@
+require 'spec_helper'
+
+describe DeployToken do
+ it { should belong_to :project }
+
+ describe 'validations' do
+ let(:project_deploy_token) { build(:deploy_token) }
+
+ context 'with no scopes defined' do
+ it 'should not be valid' do
+ project_deploy_token.scopes = []
+
+ expect(project_deploy_token).not_to be_valid
+ expect(project_deploy_token.errors[:scopes].first).to eq("can't be blank")
+ end
+ end
+ end
+
+ describe '#ensure_token' do
+ let(:project_deploy_token) { build(:deploy_token) }
+
+ it 'should ensure a token' do
+ project_deploy_token.token = nil
+ project_deploy_token.save
+
+ expect(project_deploy_token.token).not_to be_empty
+ end
+ end
+
+ describe '#revoke!' do
+ subject { create(:deploy_token) }
+
+ it 'should update revoke attribute' do
+ subject.revoke!
+ expect(subject.revoked?).to be_truthy
+ end
+ end
+end
+
diff --git a/spec/presenters/deploy_token_presenter.rb b/spec/presenters/deploy_token_presenter.rb
new file mode 100644
index 00000000000..c09f580be3e
--- /dev/null
+++ b/spec/presenters/deploy_token_presenter.rb
@@ -0,0 +1,9 @@
+require 'spec_helper'
+
+describe DeployTokenPresenter do
+ let(:project) { create(:project) }
+ let(:deploy_token) { create(:deploy_token, project: project) }
+
+ subject(:presenter) { described_class.new(deploy_token) }
+
+end
diff --git a/spec/presenters/projects/settings/deploy_tokens_presenter_spec.rb b/spec/presenters/projects/settings/deploy_tokens_presenter_spec.rb
new file mode 100644
index 00000000000..5a1d473b464
--- /dev/null
+++ b/spec/presenters/projects/settings/deploy_tokens_presenter_spec.rb
@@ -0,0 +1,36 @@
+require 'spec_helper'
+
+describe Projects::DeployTokensPresenter do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+ let(:deploy_tokens) { create_list(:deploy_token, 3, project: project) }
+
+ subject(:presenter) { described_class.new(deploy_tokens, current_user: user) }
+
+ describe '#available_scopes' do
+ it 'returns the all the deploy token scopes' do
+ expect(presenter.available_scopes).to match_array(%w(read_repo read_registry))
+ end
+ end
+
+ describe '#scope_description' do
+ let(:deploy_token) { create(:deploy_token, project: project, scopes: [:read_registry]) }
+
+ it 'returns the description for a given scope' do
+ description = 'Allows read-only access to the registry images'
+ expect(presenter.scope_description('read_registry')).to eq(description)
+ end
+ end
+
+ describe '#new_token' do
+ it 'returns a build instance of DeployToken' do
+ expect(presenter.new_token).to be_an_instance_of(DeployToken)
+ end
+ end
+
+ describe '#length' do
+ it 'returns the size of deploy tokens presented' do
+ expect(presenter.length).to eq(3)
+ end
+ end
+end
diff --git a/spec/services/deploy_tokens/create_service_spec.rb b/spec/services/deploy_tokens/create_service_spec.rb
new file mode 100644
index 00000000000..b352675c48f
--- /dev/null
+++ b/spec/services/deploy_tokens/create_service_spec.rb
@@ -0,0 +1,44 @@
+require 'spec_helper'
+
+describe DeployTokens::CreateService, :clean_gitlab_redis_shared_state do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+ let(:deploy_token_params) { attributes_for(:deploy_token) }
+
+ describe '#execute' do
+ subject { described_class.new(project, user, deploy_token_params) }
+
+ context 'when the deploy token is valid' do
+ it 'should create a new DeployToken' do
+ expect { subject.execute }.to change { DeployToken.count }.by(1)
+ end
+
+ it 'should assign the DeployToken to the project' do
+ deploy_token = subject.execute
+ expect(subject.project).to eq(project)
+ end
+
+ it 'should store the token on redis' do
+ redis_key = DeployToken.redis_shared_state_key(user.id)
+ deploy_token = subject.execute
+
+ expect(Gitlab::Redis::SharedState.with { |redis| redis.get(redis_key) }).not_to be_nil
+ end
+ end
+
+ context 'when the deploy token is invalid' do
+ let(:deploy_token_params) { attributes_for(:deploy_token, scopes: []) }
+
+ it 'it should not create a new DeployToken' do
+ expect { subject.execute }.not_to change { DeployToken.count }
+ end
+
+ it 'should not store the token on redis' do
+ redis_key = DeployToken.redis_shared_state_key(user.id)
+ deploy_token = subject.execute
+
+ expect(Gitlab::Redis::SharedState.with { |redis| redis.get(redis_key) }).to be_nil
+ end
+ end
+ end
+end