password-store

Simple password manager using gpg and ordinary unix directories
git clone https://git.zx2c4.com/password-store
Log | Files | Refs | README | LICENSE

roboform2pass.rb (3337B)


      1 #!/usr/bin/env ruby
      2 
      3 # Copyright 2015 Sergey Makridenkov <sergey@makridenkov.com>.
      4 # Released under MIT License.
      5 #
      6 # Usage:
      7 #    1. Save roboform print lists (like File > Print lists > Logins) to ~/roboform_print_lists.
      8 #    2. Run import: ./roboform2pass.rb ~/roboform_print_lists
      9 
     10 
     11 require 'nokogiri'
     12 
     13 class Login
     14   def initialize
     15     self.fields = {}
     16     self.raw = []
     17   end
     18 
     19   attr_accessor :key
     20   attr_accessor :password
     21   attr_accessor :url
     22   attr_accessor :fields
     23   attr_accessor :raw
     24 
     25   def ask_required_info
     26     ask!(:key) if blank?(key)
     27     find_password!
     28     if blank?(password)
     29       ask!(:password)
     30       puts ''
     31     end
     32   end
     33 
     34   def save
     35     if valid?
     36       IO.popen("pass insert -m -f '#{path}' > /dev/null", "w") do |pass_io|
     37         pass_io.puts password
     38         pass_io.puts "Url: #{url}" if present?(url)
     39         pass_io.puts "Fields: #{fields}" if fields.any?
     40         pass_io.puts "Notes: #{raw}" if raw.any?
     41       end
     42 
     43       $? == 0
     44     end
     45   end
     46 
     47   private
     48 
     49   def path
     50     key = self.key.downcase.gsub(/[^\w\.\/]/, '_').gsub(/_{2,}/, '_')
     51     "roboform/#{key}"
     52   end
     53 
     54   def valid?
     55     present?(key) && present?(password)
     56   end
     57 
     58   def find_password!
     59     fields.each do |key, val|
     60       key = key.downcase
     61       if key.include?('password') || key.include?('pwd') || key.include?('pass')
     62         self.password = val
     63       end
     64     end
     65   end
     66 
     67   def ask!(field)
     68     puts Colorize.red("#{field.capitalize} is empty for login:")
     69     print_self
     70     print Colorize.green "Please type #{field}: "
     71     self.send("#{field}=", gets.chomp)
     72   end
     73 
     74   def print_self
     75     puts Colorize.yellow "\tKey:\t#{key}" if present?(key)
     76     puts Colorize.yellow "\tPassword:\t#{password}" if present?(password)
     77     puts Colorize.yellow "\tUrl:\t#{url}" if present?(url)
     78     puts Colorize.yellow "\tFields:\t#{fields}" if fields.any?
     79     puts Colorize.yellow "\tRaw:\t#{raw}" if raw.any?
     80   end
     81 
     82   def blank?(str)
     83     !str || str.strip.empty?
     84   end
     85 
     86   def present?(str)
     87     !blank?(str)
     88   end
     89 end
     90 
     91 class Colorize
     92   class << self
     93     def red(mes)
     94       colorize(31, mes)
     95     end
     96 
     97     def green(mes)
     98       colorize(32, mes)
     99     end
    100 
    101     def yellow(mes)
    102       colorize(33, mes)
    103     end
    104 
    105     def pink(mes)
    106       colorize(35, mes)
    107     end
    108 
    109     private
    110 
    111     def colorize(color_code, mes)
    112       "\e[#{color_code}m#{mes}\e[0m"
    113     end
    114   end
    115 end
    116 
    117 
    118 print_list_dir = ARGV.pop
    119 unless print_list_dir
    120   raise "No dir/to/roboform/print_lists"
    121 end
    122 print_list_dir = File.expand_path(print_list_dir)
    123 
    124 # parse logins
    125 logins_path = Dir.glob("#{print_list_dir}/RoboForm Logins*.html").first
    126 unless logins_path
    127   raise 'Login HTML (RoboForm Logins*.html) not found'
    128 end
    129 
    130 html_logins = Nokogiri::HTML(File.open(logins_path))
    131 
    132 saved_logins = 0
    133 
    134 html_logins.css('table').each do |table|
    135   login = Login.new
    136 
    137   table.css('tr').each do |tr|
    138     caption = tr.at_css('.caption')
    139     subcaption = tr.at_css('.subcaption')
    140     key = tr.at_css('.field')
    141 
    142     if caption
    143       login.key = caption.text()
    144     elsif subcaption
    145       login.url = subcaption.text()
    146     elsif key
    147       login.fields[key.text()] = tr.at_css('.wordbreakfield').text()
    148     else
    149       login.raw << tr.at_css('.wordbreakfield').text()
    150     end
    151   end
    152 
    153   if login.fields.any?
    154     login.ask_required_info
    155     if login.save
    156       saved_logins += 1
    157     end
    158   end
    159 end
    160 
    161 puts "Imported passwords: #{saved_logins}"
    162