diff options
author | Mayra Cabrera <mcabrera@gitlab.com> | 2018-03-19 10:11:12 -0600 |
---|---|---|
committer | Mayra Cabrera <mcabrera@gitlab.com> | 2018-03-27 18:57:10 -0600 |
commit | aef29942e7386d932d2e4c19b717ccca6c9f4f97 (patch) | |
tree | 6378042a848f8876dc3f21b8e2ecf7d52f157056 | |
parent | fbb83069deaad3db1239af66e6c9dc913f29f8f8 (diff) | |
download | gitlab-ce-31591-project-deploy-tokens-to-allow-permanent-access.tar.gz |
Create barebones for Deploytoken31591-project-deploy-tokens-to-allow-permanent-access
Includes:
- Model, factories, create service and controller actions
- As usual, includes specs for everything
- Builds UI (copy from PAT)
- Add revoke action
Closes #31591
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 |