Commit d745ff04 authored by Krasimir Angelov's avatar Krasimir Angelov Committed by Mayra Cabrera

Add username to deploy tokens

This new attribute is optional and used when set instead of the default
format `gitlab+deploy-token-#{id}`.

Empty usernames will be saved as null in the database.

Related to https://gitlab.com/gitlab-org/gitlab-ce/issues/50228.
parent 4b050fc2
...@@ -99,7 +99,7 @@ module Projects ...@@ -99,7 +99,7 @@ module Projects
end end
def deploy_token_params def deploy_token_params
params.require(:deploy_token).permit(:name, :expires_at, :read_repository, :read_registry) params.require(:deploy_token).permit(:name, :expires_at, :read_repository, :read_registry, :username)
end end
end end
end end
......
...@@ -16,6 +16,14 @@ class DeployToken < ApplicationRecord ...@@ -16,6 +16,14 @@ class DeployToken < ApplicationRecord
has_many :projects, through: :project_deploy_tokens has_many :projects, through: :project_deploy_tokens
validate :ensure_at_least_one_scope validate :ensure_at_least_one_scope
validates :username,
length: { maximum: 255 },
allow_nil: true,
format: {
with: /\A[a-zA-Z0-9\.\+_-]+\z/,
message: "can contain only letters, digits, '_', '-', '+', and '.'"
}
before_save :ensure_token before_save :ensure_token
accepts_nested_attributes_for :project_deploy_tokens accepts_nested_attributes_for :project_deploy_tokens
...@@ -39,7 +47,7 @@ class DeployToken < ApplicationRecord ...@@ -39,7 +47,7 @@ class DeployToken < ApplicationRecord
end end
def username def username
"gitlab+deploy-token-#{id}" super || default_username
end end
def has_access_to?(requested_project) def has_access_to?(requested_project)
...@@ -75,4 +83,8 @@ class DeployToken < ApplicationRecord ...@@ -75,4 +83,8 @@ class DeployToken < ApplicationRecord
def ensure_at_least_one_scope def ensure_at_least_one_scope
errors.add(:base, "Scopes can't be blank") unless read_repository || read_registry errors.add(:base, "Scopes can't be blank") unless read_repository || read_registry
end end
def default_username
"gitlab+deploy-token-#{id}" if persisted?
end
end end
...@@ -3,7 +3,9 @@ ...@@ -3,7 +3,9 @@
module DeployTokens module DeployTokens
class CreateService < BaseService class CreateService < BaseService
def execute def execute
@project.deploy_tokens.create(params) @project.deploy_tokens.create(params) do |deploy_token|
deploy_token.username = params[:username].presence
end
end end
end end
end end
...@@ -12,6 +12,11 @@ ...@@ -12,6 +12,11 @@
= f.label :expires_at, class: 'label-bold' = f.label :expires_at, class: 'label-bold'
= f.text_field :expires_at, class: 'datepicker form-control qa-deploy-token-expires-at', value: f.object.expires_at = f.text_field :expires_at, class: 'datepicker form-control qa-deploy-token-expires-at', value: f.object.expires_at
.form-group
= f.label :username, class: 'label-bold'
= f.text_field :username, class: 'form-control qa-deploy-token-username'
.text-secondary= s_('DeployTokens|Default format is "gitlab+deploy-token-{n}". Enter custom username if you want to change it.')
.form-group .form-group
= f.label :scopes, class: 'label-bold' = f.label :scopes, class: 'label-bold'
%fieldset.form-group.form-check %fieldset.form-group.form-check
......
---
title: Allow custom username for deploy tokens
merge_request: 29639
author:
type: added
# frozen_string_literal: true
class AddUsernameToDeployTokens < ActiveRecord::Migration[5.1]
DOWNTIME = false
def change
add_column :deploy_tokens, :username, :string
end
end
...@@ -1047,6 +1047,7 @@ ActiveRecord::Schema.define(version: 20190628145246) do ...@@ -1047,6 +1047,7 @@ ActiveRecord::Schema.define(version: 20190628145246) do
t.datetime_with_timezone "created_at", null: false t.datetime_with_timezone "created_at", null: false
t.string "name", null: false t.string "name", null: false
t.string "token", null: false t.string "token", null: false
t.string "username"
t.index ["token", "expires_at", "id"], name: "index_deploy_tokens_on_token_and_expires_at_and_id", where: "(revoked IS FALSE)", using: :btree t.index ["token", "expires_at", "id"], name: "index_deploy_tokens_on_token_and_expires_at_and_id", where: "(revoked IS FALSE)", using: :btree
t.index ["token"], name: "index_deploy_tokens_on_token", unique: true, using: :btree t.index ["token"], name: "index_deploy_tokens_on_token", unique: true, using: :btree
end end
......
...@@ -3557,6 +3557,9 @@ msgstr "" ...@@ -3557,6 +3557,9 @@ msgstr ""
msgid "DeployTokens|Created" msgid "DeployTokens|Created"
msgstr "" msgstr ""
msgid "DeployTokens|Default format is \"gitlab+deploy-token-{n}\". Enter custom username if you want to change it."
msgstr ""
msgid "DeployTokens|Deploy Tokens" msgid "DeployTokens|Deploy Tokens"
msgstr "" msgstr ""
......
...@@ -32,4 +32,24 @@ describe Projects::Settings::RepositoryController do ...@@ -32,4 +32,24 @@ describe Projects::Settings::RepositoryController do
expect(RepositoryCleanupWorker).to have_received(:perform_async).once expect(RepositoryCleanupWorker).to have_received(:perform_async).once
end end
end end
describe 'POST create_deploy_token' do
let(:deploy_token_params) do
{
name: 'deployer_token',
expires_at: 1.month.from_now.to_date.to_s,
username: 'deployer',
read_repository: '1'
}
end
subject(:create_deploy_token) { post :create_deploy_token, params: { namespace_id: project.namespace, project_id: project, deploy_token: deploy_token_params } }
it 'creates deploy token' do
expect { create_deploy_token }.to change { DeployToken.active.count }.by(1)
expect(response).to have_gitlab_http_status(200)
expect(response).to render_template(:show)
end
end
end end
...@@ -112,11 +112,17 @@ describe 'Projects > Settings > Repository settings' do ...@@ -112,11 +112,17 @@ describe 'Projects > Settings > Repository settings' do
it 'add a new deploy token' do it 'add a new deploy token' do
fill_in 'deploy_token_name', with: 'new_deploy_key' fill_in 'deploy_token_name', with: 'new_deploy_key'
fill_in 'deploy_token_expires_at', with: (Date.today + 1.month).to_s fill_in 'deploy_token_expires_at', with: (Date.today + 1.month).to_s
fill_in 'deploy_token_username', with: 'deployer'
check 'deploy_token_read_repository' check 'deploy_token_read_repository'
check 'deploy_token_read_registry' check 'deploy_token_read_registry'
click_button 'Create deploy token' click_button 'Create deploy token'
expect(page).to have_content('Your new project deploy token has been created') expect(page).to have_content('Your new project deploy token has been created')
within('.created-deploy-token-container') do
expect(page).to have_selector("input[name='deploy-token-user'][value='deployer']")
expect(page).to have_selector("input[name='deploy-token'][readonly='readonly']")
end
end end
end end
......
...@@ -309,6 +309,15 @@ describe Gitlab::Auth do ...@@ -309,6 +309,15 @@ describe Gitlab::Auth do
.to eq(auth_success) .to eq(auth_success)
end end
it 'succeeds when custom login and token are valid' do
deploy_token = create(:deploy_token, username: 'deployer', read_registry: false, projects: [project])
auth_success = Gitlab::Auth::Result.new(deploy_token, project, :deploy_token, [:download_code])
expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: 'deployer')
expect(gl_auth.find_for_git_client('deployer', deploy_token.token, project: project, ip: 'ip'))
.to eq(auth_success)
end
it 'fails when login is not valid' do it 'fails when login is not valid' do
expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: 'random_login') expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: 'random_login')
expect(gl_auth.find_for_git_client('random_login', deploy_token.token, project: project, ip: 'ip')) expect(gl_auth.find_for_git_client('random_login', deploy_token.token, project: project, ip: 'ip'))
......
...@@ -8,6 +8,15 @@ describe DeployToken do ...@@ -8,6 +8,15 @@ describe DeployToken do
it { is_expected.to have_many :project_deploy_tokens } it { is_expected.to have_many :project_deploy_tokens }
it { is_expected.to have_many(:projects).through(:project_deploy_tokens) } it { is_expected.to have_many(:projects).through(:project_deploy_tokens) }
describe 'validations' do
let(:username_format_message) { "can contain only letters, digits, '_', '-', '+', and '.'" }
it { is_expected.to validate_length_of(:username).is_at_most(255) }
it { is_expected.to allow_value('GitLab+deploy_token-3.14').for(:username) }
it { is_expected.not_to allow_value('<script>').for(:username).with_message(username_format_message) }
it { is_expected.not_to allow_value('').for(:username).with_message(username_format_message) }
end
describe '#ensure_token' do describe '#ensure_token' do
it 'ensures a token' do it 'ensures a token' do
deploy_token.token = nil deploy_token.token = nil
...@@ -87,8 +96,30 @@ describe DeployToken do ...@@ -87,8 +96,30 @@ describe DeployToken do
end end
describe '#username' do describe '#username' do
it 'returns a harcoded username' do context 'persisted records' do
expect(deploy_token.username).to eq("gitlab+deploy-token-#{deploy_token.id}") it 'returns a default username if none is set' do
expect(deploy_token.username).to eq("gitlab+deploy-token-#{deploy_token.id}")
end
it 'returns the username provided if one is set' do
deploy_token = create(:deploy_token, username: 'deployer')
expect(deploy_token.username).to eq('deployer')
end
end
context 'new records' do
it 'returns nil if no username is set' do
deploy_token = build(:deploy_token)
expect(deploy_token.username).to be_nil
end
it 'returns the username provided if one is set' do
deploy_token = build(:deploy_token, username: 'deployer')
expect(deploy_token.username).to eq('deployer')
end
end end
end end
......
...@@ -32,6 +32,22 @@ describe DeployTokens::CreateService do ...@@ -32,6 +32,22 @@ describe DeployTokens::CreateService do
end end
end end
context 'when username is empty string' do
let(:deploy_token_params) { attributes_for(:deploy_token, username: '') }
it 'converts it to nil' do
expect(subject.read_attribute(:username)).to be_nil
end
end
context 'when username is provided' do
let(:deploy_token_params) { attributes_for(:deploy_token, username: 'deployer') }
it 'keeps the provided username' do
expect(subject.read_attribute(:username)).to eq('deployer')
end
end
context 'when the deploy token is invalid' do context 'when the deploy token is invalid' do
let(:deploy_token_params) { attributes_for(:deploy_token, read_repository: false, read_registry: false) } let(:deploy_token_params) { attributes_for(:deploy_token, read_repository: false, read_registry: false) }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment