BigW Consortium Gitlab

gpg.rb 2.83 KB
Newer Older
1 2 3 4
module Gitlab
  module Gpg
    extend self

5 6
    MUTEX = Mutex.new

7 8 9 10 11 12 13
    module CurrentKeyChain
      extend self

      def add(key)
        GPGME::Key.import(key)
      end

Alexis Reigel committed
14
      def fingerprints_from_key(key)
15 16 17 18 19 20 21 22
        import = GPGME::Key.import(key)

        return [] if import.imported == 0

        import.imports.map(&:fingerprint)
      end
    end

Alexis Reigel committed
23
    def fingerprints_from_key(key)
24
      using_tmp_keychain do
Alexis Reigel committed
25 26 27
        CurrentKeyChain.fingerprints_from_key(key)
      end
    end
28

Alexis Reigel committed
29 30 31
    def primary_keyids_from_key(key)
      using_tmp_keychain do
        fingerprints = CurrentKeyChain.fingerprints_from_key(key)
32 33 34 35 36

        GPGME::Key.find(:public, fingerprints).map { |raw_key| raw_key.primary_subkey.keyid }
      end
    end

37 38
    def subkeys_from_key(key)
      using_tmp_keychain do
39 40
        fingerprints = CurrentKeyChain.fingerprints_from_key(key)
        raw_keys     = GPGME::Key.find(:public, fingerprints)
41

42
        raw_keys.each_with_object({}) do |raw_key, grouped_subkeys|
43 44
          primary_subkey_id = raw_key.primary_subkey.keyid

45 46
          grouped_subkeys[primary_subkey_id] = raw_key.subkeys[1..-1].map do |s|
            { keyid: s.keyid, fingerprint: s.fingerprint }
47 48 49 50 51
          end
        end
      end
    end

52
    def user_infos_from_key(key)
53
      using_tmp_keychain do
Alexis Reigel committed
54
        fingerprints = CurrentKeyChain.fingerprints_from_key(key)
55

56
        GPGME::Key.find(:public, fingerprints).flat_map do |raw_key|
57
          raw_key.uids.map { |uid| { name: uid.name, email: uid.email.downcase } }
58
        end
59 60 61
      end
    end

62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
    # Allows thread safe switching of temporary keychain files
    #
    # 1. The current thread may use nesting of temporary keychain
    # 2. Another thread needs to wait for the lock to be released
    def using_tmp_keychain(&block)
      if MUTEX.locked? && MUTEX.owned?
        optimistic_using_tmp_keychain(&block)
      else
        MUTEX.synchronize do
          optimistic_using_tmp_keychain(&block)
        end
      end
    end

    # 1. Returns the custom home directory if one has been set by calling
    #    `GPGME::Engine.home_dir=`
    # 2. Returns the default home directory otherwise
    def current_home_dir
      GPGME::Engine.info.first.home_dir || GPGME::Engine.dirinfo('homedir')
    end

    private

    def optimistic_using_tmp_keychain
86
      previous_dir = current_home_dir
87 88 89
      tmp_dir = Dir.mktmpdir
      GPGME::Engine.home_dir = tmp_dir
      yield
90
    ensure
91 92 93 94 95 96 97
      # Ignore any errors when removing the tmp directory, as we may run into a
      # race condition:
      # The `gpg-agent` agent process may clean up some files as well while
      # `FileUtils.remove_entry` is iterating the directory and removing all
      # its contained files and directories recursively, which could raise an
      # error.
      FileUtils.remove_entry(tmp_dir, true)
98
      GPGME::Engine.home_dir = previous_dir
99 100 101
    end
  end
end