Commit a1fc67a5 authored by Mayra Cabrera's avatar Mayra Cabrera
Browse files

Create barebones for Deploytoken

Includes:
- Model, factories, create service and controller actions
- As usual, includes specs for everything
- Builds UI (copy from PAT)
- Add revoke action

Closes #31591
parent 7d92c235f73b
......@@ -6,6 +6,7 @@
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 @@
initSettingsPanels();
new ProtectedBranchCreate(); // eslint-disable-line no-new
new ProtectedBranchEditList(); // eslint-disable-line no-new
new DueDateSelectors();
});
......@@ -284,3 +284,22 @@
.deprecated-service {
cursor: default;
}
.personal-access-tokens-never-expires-label {
color: $note-disabled-comment-color;
}
.created-deploy-token-container {
.deploy-token-field {
width: 90%;
display: inline;
}
.btn-clipboard {
margin-left: 5px;
}
.help-block {
margin-top: 4px;
}
}
class Projects::DeployTokensController < Projects::ApplicationController
before_action :authorize_admin_project!
def create
@token = DeployTokens::CreateService.new(@project, current_user, deploy_token_params).execute
token_params = {}
if @token.valid?
flash[:notice] = 'Your new project deploy token has been created.'
else
token_params = @token.attributes.slice("name", "scopes", "expires_at")
flash[:alert] = @token.errors.full_messages.join(', ').html_safe
end
redirect_to project_settings_repository_path(project, deploy_token: token_params)
end
def revoke
@token = @project.deploy_tokens.find(params[:id])
@token.revoke!
redirect_to project_settings_repository_path(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
......@@ -5,4 +5,5 @@
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)
......@@ -8,4 +9,5 @@
define_deploy_token
define_protected_refs
end
......@@ -51,6 +53,14 @@
gon.push(protectable_branches_for_dropdown)
gon.push(access_levels_options)
end
def define_deploy_token
@deploy_token = @project.deploy_tokens.build(deploy_token_attributes)
end
def deploy_token_attributes
params.fetch(:deploy_token, {}).permit(:name, :expires_at, scopes: [])
end
end
end
end
class DeployToken < ActiveRecord::Base
include Expirable
include TokenAuthenticatable
add_authentication_token_field :token
AVAILABLE_SCOPES = %w(read_repo read_registry).freeze
serialize :scopes, Array # rubocop:disable Cop/ActiveRecordSerialize
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
......@@ -222,6 +222,7 @@
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'
......
module Projects
module Settings
class DeployTokensPresenter < Gitlab::View::Presenter::Simple
include Enumerable
presents :deploy_tokens
def available_scopes
DeployToken::AVAILABLE_SCOPES
end
def length
deploy_tokens.length
end
def scope_description(scope)
scope_descriptions[scope]
end
def each
deploy_tokens.each do |deploy_token|
yield deploy_token
end
end
def new_deploy_token
@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
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
......@@ -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
......
%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"
- 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
- if @deploy_tokens.new_deploy_token
= render 'projects/deploy_tokens/new_deploy_token', new_token: @deploy_tokens.new_deploy_token
%h5.prepend-top-0
Add a deploy token
= render 'projects/deploy_tokens/form', project: @project, token: @deploy_token, presenter: @deploy_tokens
%hr
= render 'projects/deploy_tokens/table', project: @project, active_tokens: @deploy_tokens
.created-deploy-token-container
%h5.prepend-top-0
Your New Deploy Token
.form-group
= text_field_tag 'deploy-token', new_token, readonly: true, class: "deploy-token-field form-control js-select-on-focus", 'aria-describedby' => "deploy-token-help-block"
= clipboard_button(text: new_token, title: "Copy deploy token to clipboard", placement: "left")
%span.deploy-token.help-block.text-danger Make sure you save it - you won't be able to access it again.
%hr
.modal{ id: "revoke-modal-#{token.id}" }
.modal-dialog
.modal-content
.modal-header
%h4.modal-title.pull-left
Revoke
%b #{token.name}?
%button.close.pull-right{ "aria-label" => "Close", data: { dismiss: "modal"} }
%span{ "aria-hidden" => "true" } ×
.modal-body
%p
Are you sure you want to revoke this Deploy Token? This action cannot be undone
.modal-footer
%a{ href: '#', data: { dismiss: 'modal' }, class: 'btn btn-default' } Cancel
= link_to "Revoke #{token.name}", revoke_project_deploy_token_path(project, token), method: :put, class: 'btn btn-danger'
%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)
%h5 Active Deploy Tokens (#{active_tokens.length})
- if active_tokens.present?
.table-responsive.deploy-tokens
%table.table
%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", "#", class: "btn btn-danger pull-right", data: { toggle: "modal", target: "#revoke-modal-#{token.id}"}
= render 'projects/deploy_tokens/revoke_modal', token: token, project: project
- else
.settings-message.text-center
This project has no active Deploy Tokens.
......@@ -9,3 +9,4 @@
= render "projects/protected_branches/index"
= render "projects/protected_tags/index"
= render @deploy_keys
= render "projects/deploy_tokens/index"
---
title: Creates Deploy Tokens to allow permanent access to repo and registry
merge_request: 17894
author:
type: added
......@@ -88,6 +88,12 @@
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]
......
class CreateDeployTokens < ActiveRecord::Migration
DOWNTIME = false
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
......@@ -683,6 +683,20 @@
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
......@@ -2072,6 +2086,7 @@
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
......
Supports Markdown
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