123456789_123456789_123456789_123456789_123456789_

Class: Gem::CredentialStore::LinuxBackend

Relationships & Source Files
Inherits: Object
Defined in: lib/rubygems/credential_store/native/linux.rb

Overview

Stores credentials in the Secret Service API (GNOME Keyring, KWallet, ...) via the secret-tool command line tool from libsecret.

Constant Summary

Class Attribute Summary

Class Method Summary

Class Attribute Details

.available?Boolean (readonly)

[ GitHub ]

  
# File 'lib/rubygems/credential_store/native/linux.rb', line 14

def self.available?
  return @available if defined?(@available)

  @available = ENV["PATH"].to_s.split(File::PATH_SEPARATOR).any? do |dir|
    File.executable?(File.join(dir, "secret-tool"))
  end
end

Class Method Details

.cleared?(service, account = nil) ⇒ Boolean (private)

libsecret clears only unlocked items and reports no error for the ones it skipped, so a locked keyring answers a clear with success while keeping every secret. search exits zero either way, so its output is the answer.

[ GitHub ]

  
# File 'lib/rubygems/credential_store/native/linux.rb', line 87

def self.cleared?(service,  = nil)
  _out, err, status = Open3.capture3(
    "secret-tool", "search", "--all", "service", service
  )
  return false unless status.success?

  remaining = err.scan(ACCOUNT_ATTRIBUTE).flatten
   ? !remaining.include?() : remaining.empty?
end

.delete(service, account)

[ GitHub ]

  
# File 'lib/rubygems/credential_store/native/linux.rb', line 65

def self.delete(service, )
  _out, err, status = Open3.capture3(
    "secret-tool", "clear", "service", service, "account", 
  )
  return cleared?(service, ) if status.success?

  # secret-tool clear exits 1 with no stderr when nothing matched.
  status.exitstatus == 1 && err.to_s.strip.empty?
end

.delete_all(service)

[ GitHub ]

  
# File 'lib/rubygems/credential_store/native/linux.rb', line 75

def self.delete_all(service)
  _out, err, status = Open3.capture3(
    "secret-tool", "clear", "service", service
  )
  return cleared?(service) if status.success?

  status.exitstatus == 1 && err.to_s.strip.empty?
end

.get(service, account)

[ GitHub ]

  
# File 'lib/rubygems/credential_store/native/linux.rb', line 29

def self.get(service, )
  out, err, status = Open3.capture3(
    "secret-tool", "lookup", "service", service, "account", 
  )
  # secret-tool exits 1 with nothing on stderr when the entry is simply
  # absent. Anything else is a real failure.
  unless status.success?
    return nil if status.exitstatus == 1 && err.to_s.strip.empty?

    raise "secret-tool exited with #{status.exitstatus}: #{err.strip}"
  end

  secret = out.chomp
  secret.empty? ? nil : secret
end

.list(service)

secret-tool writes attributes to stderr and secrets to stdout, so accounts are read from stderr. Discarding stdout also keeps a secret containing a newline from being mistaken for an attribute line.

[ GitHub ]

  
# File 'lib/rubygems/credential_store/native/linux.rb', line 56

def self.list(service)
  _out, err, status = Open3.capture3(
    "secret-tool", "search", "--all", "service", service
  )
  return [] unless status.success?

  err.scan(ACCOUNT_ATTRIBUTE).flatten.uniq
end

.reset!

Clears the memoized Gem::CredentialStore#available? result. Intended for tests only.

[ GitHub ]

  
# File 'lib/rubygems/credential_store/native/linux.rb', line 25

def self.reset!
  remove_instance_variable(:@available) if defined?(@available)
end

.set(service, account, secret)

[ GitHub ]

  
# File 'lib/rubygems/credential_store/native/linux.rb', line 45

def self.set(service, , secret)
  _out, status = Open3.capture2(
    "secret-tool", "store", "--label=RubyGems", "service", service, "account", ,
    stdin_data: secret
  )
  status.success?
end