From cc2211295ee7a45906878fac728ab2bc1b26d591 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Mon, 17 Dec 2018 12:29:20 +0000 Subject: Merge branch '55433-un-revert-https-gitlab-com-gitlab-org-gitlab-ce-commit-00acef434031b5dc0bf39576a9e83802c7806842-revert' into 'master' Resolve "Un-revert https://gitlab.com/gitlab-org/gitlab-ce/commit/00acef434031b5dc0bf39576a9e83802c7806842 revert" Closes #55433 See merge request gitlab-org/gitlab-ce!23861 (cherry picked from commit e8374cb6f493880042bf21d70c275bfdeed65fbe) 8ce86bf9 Revert "Revert "LfsToken uses JSONWebToken::HMACToken by default"" --- lib/gitlab/auth.rb | 4 +- lib/gitlab/lfs_token.rb | 121 ++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 104 insertions(+), 21 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index 6eb5f9e2300..7aa02009aa0 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -199,7 +199,7 @@ module Gitlab end # rubocop: enable CodeReuse/ActiveRecord - def lfs_token_check(login, password, project) + def lfs_token_check(login, encoded_token, project) deploy_key_matches = login.match(/\Alfs\+deploy-key-(\d+)\z/) actor = @@ -222,7 +222,7 @@ module Gitlab read_authentication_abilities end - if Devise.secure_compare(token_handler.token, password) + if token_handler.token_valid?(encoded_token) Gitlab::Auth::Result.new(actor, nil, token_handler.type, authentication_abilities) end end diff --git a/lib/gitlab/lfs_token.rb b/lib/gitlab/lfs_token.rb index 05d3096a208..c09d3ebc7be 100644 --- a/lib/gitlab/lfs_token.rb +++ b/lib/gitlab/lfs_token.rb @@ -2,10 +2,21 @@ module Gitlab class LfsToken - attr_accessor :actor + module LfsTokenHelper + def user? + actor.is_a?(User) + end + + def actor_name + user? ? actor.username : "lfs+deploy-key-#{actor.id}" + end + end + + include LfsTokenHelper - TOKEN_LENGTH = 50 - EXPIRY_TIME = 1800 + DEFAULT_EXPIRE_TIME = 1800 + + attr_accessor :actor def initialize(actor) @actor = @@ -19,36 +30,108 @@ module Gitlab end end - def token - Gitlab::Redis::SharedState.with do |redis| - token = redis.get(redis_shared_state_key) - token ||= Devise.friendly_token(TOKEN_LENGTH) - redis.set(redis_shared_state_key, token, ex: EXPIRY_TIME) + def token(expire_time: DEFAULT_EXPIRE_TIME) + HMACToken.new(actor).token(expire_time) + end - token - end + def token_valid?(token_to_check) + HMACToken.new(actor).token_valid?(token_to_check) || + LegacyRedisDeviseToken.new(actor).token_valid?(token_to_check) end def deploy_key_pushable?(project) actor.is_a?(DeployKey) && actor.can_push_to?(project) end - def user? - actor.is_a?(User) - end - def type user? ? :lfs_token : :lfs_deploy_token end - def actor_name - actor.is_a?(User) ? actor.username : "lfs+deploy-key-#{actor.id}" + private # rubocop:disable Lint/UselessAccessModifier + + class HMACToken + include LfsTokenHelper + + def initialize(actor) + @actor = actor + end + + def token(expire_time) + hmac_token = JSONWebToken::HMACToken.new(secret) + hmac_token.expire_time = Time.now + expire_time + hmac_token[:data] = { actor: actor_name } + hmac_token.encoded + end + + def token_valid?(token_to_check) + decoded_token = JSONWebToken::HMACToken.decode(token_to_check, secret).first + decoded_token.dig('data', 'actor') == actor_name + rescue JWT::DecodeError + false + end + + private + + attr_reader :actor + + def secret + salt + key + end + + def salt + case actor + when DeployKey, Key + actor.fingerprint.delete(':').first(16) + when User + # Take the last 16 characters as they're more unique than the first 16 + actor.id.to_s + actor.encrypted_password.last(16) + end + end + + def key + # Take 16 characters of attr_encrypted_db_key_base, as that's what the + # cipher needs exactly + Settings.attr_encrypted_db_key_base.first(16) + end end - private + # TODO: LegacyRedisDeviseToken and references need to be removed after + # next released milestone + # + class LegacyRedisDeviseToken + TOKEN_LENGTH = 50 + DEFAULT_EXPIRY_TIME = 1800 * 1000 # 30 mins + + def initialize(actor) + @actor = actor + end + + def token_valid?(token_to_check) + Devise.secure_compare(stored_token, token_to_check) + end + + def stored_token + Gitlab::Redis::SharedState.with { |redis| redis.get(state_key) } + end + + # This method exists purely to facilitate legacy testing to ensure the + # same redis key is used. + # + def store_new_token(expiry_time_in_ms = DEFAULT_EXPIRY_TIME) + Gitlab::Redis::SharedState.with do |redis| + new_token = Devise.friendly_token(TOKEN_LENGTH) + redis.set(state_key, new_token, px: expiry_time_in_ms) + new_token + end + end + + private - def redis_shared_state_key - "gitlab:lfs_token:#{actor.class.name.underscore}_#{actor.id}" if actor + attr_reader :actor + + def state_key + "gitlab:lfs_token:#{actor.class.name.underscore}_#{actor.id}" + end end end end -- cgit v1.2.3