Created
August 13, 2022 12:47
-
-
Save sspreitzer/2642c85f12396a5b6a623e95866d1a85 to your computer and use it in GitHub Desktop.
OnePassword entry deduplication in ruby
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'open3' | |
require 'json' | |
require 'uri' | |
# MIT license, (c) Sascha Spreitzer, 2022 | |
# !!! Create an export before using this tool !!! | |
# !!! You may loose all your passwords !!! | |
# !!! Use at your own risk !!! | |
# This script deduplicates OnePassword entries, which might have come | |
# as a result of imports, etc.. | |
# To this day onepassword.com has not come up with a solution to that | |
# problem. This script is an attempt to workaround the problem. | |
# ruby op-dedup.rb | |
# Takes a minute to build a cache | |
# Deletes duplicates (by url hostname and username and password) | |
# | |
# Delete cache securely with: find .cache -type f -exec shred -u {} \; | |
def cmd(command) | |
cmd = command.split.first | |
opts = command.split[1..] | |
stdout, stderr, status = ::Open3.capture3(cmd, *opts) | |
raise "stdout: #{stdout}\nstderr: #{stderr}\nrc: #{status.exitstatus}" unless status.exitstatus == 0 | |
return stdout | |
end | |
class OnePasswordDedup | |
attr_reader :items, :dups | |
def initialize() | |
@items = [] | |
@dups = {} | |
begin | |
cmd("op whoami") | |
rescue | |
raise "You need to be logged in to onepassword cli: eval $(op signin)" | |
end | |
create_cache | |
end | |
def scanDups | |
@dups = {} | |
@items.each do |item| | |
next unless item.member?('id') | |
next if @dups.member?(item['id']) | |
next unless item.member?('urls') | |
next unless item.member?('fields') | |
next unless (item['fields'].select { |e| e['id'] == 'username'}).size >= 1 | |
next unless (item['fields'].select { |e| e['id'] == 'password'}).size >= 1 | |
id = item['id'] | |
host = URI(item['urls'][0]['href']).host | |
username = (item['fields'].select { |e| e['id'] == 'username'})[0]['value'] | |
password = (item['fields'].select { |e| e['id'] == 'password'})[0]['value'] | |
@items.each do |item_| | |
next unless item_.member?('id') | |
next if item_['id'] == id | |
next unless item_.member?('urls') | |
next unless item_.member?('fields') | |
next unless (item_['fields'].select { |e| e['id'] == 'username'}).size >= 1 | |
next unless (item_['fields'].select { |e| e['id'] == 'password'}).size >= 1 | |
id_ = item_['id'] | |
host_ = URI(item_['urls'][0]['href']).host | |
username_ = (item_['fields'].select { |e| e['id'] == 'username'})[0]['value'] | |
password_ = (item_['fields'].select { |e| e['id'] == 'password'})[0]['value'] | |
if host == host_ and username == username_ and password == password_ | |
if @dups.member?(id) | |
@dups[id] << id_ | |
else | |
@dups[id] = [id_] | |
end | |
end | |
end | |
end | |
return @dups | |
end | |
def deleteDups(archive=true) | |
if archive | |
command_archive = " --archive" | |
end | |
self.scanDups.each_pair do |id, dups| | |
dups.each do |dup| | |
puts "Deleting #{dup}" | |
cmd("op item delete #{dup}#{command_archive}") | |
end | |
end | |
end | |
private | |
def create_cache | |
Dir.mkdir('.cache') unless Dir.exists?('.cache') | |
item_list = JSON.parse(cmd("op item list --format=json")) | |
item_list.each do |item| | |
docache = true | |
id = item['id'] | |
if File.exists?(File.join(".cache", "#{id}.json")) | |
if File.ctime(File.join(".cache", "#{id}.json")) >= Time.now-60*60 | |
docache = false | |
end | |
end | |
if docache | |
item_ = JSON.parse(cmd("op item get #{id} --format=json")) | |
File.open(File.join(".cache", "#{id}.json"), 'w') do |file| | |
file.write(item_.to_json) | |
end | |
end | |
@items << JSON.parse(File.open(File.join(".cache", "#{id}.json")).read) | |
end | |
end | |
end | |
op = OnePasswordDedup.new | |
op.deleteDups |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment