Skip to content

Instantly share code, notes, and snippets.

@RISCfuture
Last active October 2, 2021 00:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save RISCfuture/2e8a250b884619c44c6d686498773b9a to your computer and use it in GitHub Desktop.
Save RISCfuture/2e8a250b884619c44c6d686498773b9a to your computer and use it in GitHub Desktop.
Scan and fix 1Password passwords
### 1password-scan-fix.rb ###
#
# This script locates 1Password passwords linked to URLs that either a)
# redirect to a newer URL or b) fail to load. For each of these URLs, you will
# be prompted as to whether you would like to modify or delete the URL.
#
# This script requires the
# [1Password CLI](https://1password.com/downloads/command-line/)
# (`brew install 1password-cli`). You will need to sign in to 1Password CLI
# by following the instructions on that web page first.
#
# You will also need the `json` and `activesupport` gems installed.
require 'json'
require 'uri'
require 'net/http'
require 'active_support'
require 'open3'
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.61 Safari/537.36'
verbose = ARGV.include?('--verbose')
ARGV.delete '--verbose'
class URI::HTTP
def host_portion
to_s.sub(path, '')
end
end
def sign_in
result = `op signin`
if (matches = result.match(/export (.+?)="(.+)"/))
ENV[matches[1]] = matches[2]
else
sign_in
end
end
def op(*args)
stdout, stderr, status = Open3.capture3('op', *args)
if stderr.include?('You are not currently signed in') || stderr.include?('session expired')
sign_in
op(*args)
elsif stderr.present? || !status.success?
$stdout.puts(stdout)
$stderr.puts(stderr)
$stderr.puts "op had non-zero exit code #{status.to_s}; exiting"
exit(status.to_i)
else
return stdout
end
end
def each_item
items = JSON.parse(op('list', 'items')).sort_by { |i| i['overview']['title'].downcase }
items.each do |item|
yield item
end
end
def item_urls(item)
item.dig('overview', 'URLs')&.map { |u| u['u'] }
end
def each_http_uri(item)
# item.dig('overview', 'URLs')&.each_with_index do |url, index|
# uri = URI.parse(url['u']) rescue next
# next unless uri.scheme.start_with?('http')
# yield uri, "overview.URLs.#{index}.u"
# end
if (url = item.dig('overview', 'url'))
uri = URI.parse(url) rescue nil
return unless uri
return unless uri.scheme.start_with?('http')
yield uri, 'url'
end
end
def test_http(uri)
Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |client|
req = Net::HTTP::Get.new(uri.path.presence || '/', 'User-Agent' => USER_AGENT)
res = client.request(req)
yield res
end
end
def redirect_location(uri, res)
loc = res.header['Location']
loc = loc.first if loc.kind_of?(Array)
if loc.start_with?('/')
loc = "#{uri.host_portion}#{loc}"
end
return loc
end
def handle_redirect(item, uri, field, loc)
puts "#{item['overview']['title']} (#{item['uuid']}) redirected"
puts " from #{uri}"
puts " to #{loc}"
puts "Would you like to update the URL? [(y)es/(n)o/[enter custom url]]"
response = gets.chomp
case response
when 'y', 'yes'
op 'edit', 'item', item['uuid'], "#{field}=#{loc}"
when 'n', 'no'
# do nothing
else
op 'edit', 'item', item['uuid'], "#{field}=#{response}"
end
end
def handle_error(item, uri, field, error)
puts "#{item['overview']['title']} (#{item['uuid']}) could not be loaded"
puts " #{uri}"
puts " -> #{error}"
puts "Would you like to archive this entry? [(y)es/(n)o]"
response = gets.chomp
case response
when 'y', 'yes'
op 'delete', 'item', item['uuid'], '--archive'
when 'n', 'no'
# do nothing
else
handle_error(item, uri, field, error)
end
end
each_item do |item|
puts "#{item['uuid']}: #{item['overview']['title']}" if verbose
each_http_uri(item) do |uri, field|
puts " #{uri}" if verbose
test_http(uri) do |res|
if res.kind_of?(Net::HTTPSuccess)
# do nothing
elsif res.kind_of?(Net::HTTPRedirection)
loc = redirect_location(uri, res)
puts " -> #{loc}" if verbose
handle_redirect(item, uri, field, loc)
else
puts " ! #{res.class}" if verbose
handle_error(item, uri, field, res.class)
end
end
rescue => err
puts " !! #{err.class} #{err}" if verbose
handle_error(item, uri, field, err)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment