Read about our upcoming Code of Conduct on this issue

This instance was upgraded to Heptapod 0.28.0 today

Commit f3adca94 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 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();
});
......@@ -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,7 +5,9 @@ class RepositoryController < Projects::ApplicationController
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_deploy_token
define_protected_refs
end
......@@ -51,6 +53,14 @@ def load_gon_index
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 @@ 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'
......
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
......
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