1# frozen_string_literal: true 2 3class DeployToken < ApplicationRecord 4 include Expirable 5 include TokenAuthenticatable 6 include PolicyActor 7 include Gitlab::Utils::StrongMemoize 8 add_authentication_token_field :token, encrypted: :optional 9 10 AVAILABLE_SCOPES = %i(read_repository read_registry write_registry 11 read_package_registry write_package_registry).freeze 12 GITLAB_DEPLOY_TOKEN_NAME = 'gitlab-deploy-token' 13 REQUIRED_DEPENDENCY_PROXY_SCOPES = %i[read_registry write_registry].freeze 14 15 default_value_for(:expires_at) { Forever.date } 16 17 has_many :project_deploy_tokens, inverse_of: :deploy_token 18 has_many :projects, through: :project_deploy_tokens 19 20 has_many :group_deploy_tokens, inverse_of: :deploy_token 21 has_many :groups, through: :group_deploy_tokens 22 23 validate :no_groups, unless: :group_type? 24 validate :no_projects, unless: :project_type? 25 validate :ensure_at_least_one_scope 26 validates :username, 27 length: { maximum: 255 }, 28 allow_nil: true, 29 format: { 30 with: /\A[a-zA-Z0-9\.\+_-]+\z/, 31 message: "can contain only letters, digits, '_', '-', '+', and '.'" 32 } 33 34 validates :deploy_token_type, presence: true 35 enum deploy_token_type: { 36 group_type: 1, 37 project_type: 2 38 } 39 40 before_save :ensure_token 41 42 accepts_nested_attributes_for :project_deploy_tokens 43 44 scope :active, -> { where("revoked = false AND expires_at >= NOW()") } 45 46 def self.gitlab_deploy_token 47 active.find_by(name: GITLAB_DEPLOY_TOKEN_NAME) 48 end 49 50 def valid_for_dependency_proxy? 51 group_type? && 52 active? && 53 REQUIRED_DEPENDENCY_PROXY_SCOPES.all? { |scope| scope.in?(scopes) } 54 end 55 56 def revoke! 57 update!(revoked: true) 58 end 59 60 def active? 61 !revoked && !expired? 62 end 63 64 def deactivated? 65 !active? 66 end 67 68 def scopes 69 AVAILABLE_SCOPES.select { |token_scope| read_attribute(token_scope) } 70 end 71 72 def username 73 super || default_username 74 end 75 76 def has_access_to?(requested_project) 77 return false unless active? 78 return false unless holder 79 80 holder.has_access_to?(requested_project) 81 end 82 83 def has_access_to_group?(requested_group) 84 return false unless active? 85 return false unless group_type? 86 return false unless holder 87 88 holder.has_access_to_group?(requested_group) 89 end 90 91 # This is temporal. Currently we limit DeployToken 92 # to a single project or group, later we're going to 93 # extend that to be for multiple projects and namespaces. 94 def project 95 strong_memoize(:project) do 96 projects.first 97 end 98 end 99 100 def group 101 strong_memoize(:group) do 102 groups.first 103 end 104 end 105 106 def accessible_projects 107 if project_type? 108 projects 109 elsif group_type? 110 group.all_projects 111 end 112 end 113 114 def holder 115 strong_memoize(:holder) do 116 if project_type? 117 project_deploy_tokens.first 118 elsif group_type? 119 group_deploy_tokens.first 120 end 121 end 122 end 123 124 def expires_at 125 expires_at = read_attribute(:expires_at) 126 expires_at != Forever.date ? expires_at : nil 127 end 128 129 def expires_at=(value) 130 write_attribute(:expires_at, value.presence || Forever.date) 131 end 132 133 private 134 135 def expired? 136 return false unless expires_at 137 138 expires_at < Date.today 139 end 140 141 def ensure_at_least_one_scope 142 errors.add(:base, _("Scopes can't be blank")) unless scopes.any? 143 end 144 145 def default_username 146 "gitlab+deploy-token-#{id}" if persisted? 147 end 148 149 def no_groups 150 errors.add(:deploy_token, 'cannot have groups assigned') if group_deploy_tokens.any? 151 end 152 153 def no_projects 154 errors.add(:deploy_token, 'cannot have projects assigned') if project_deploy_tokens.any? 155 end 156end 157