one_password 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. data/.gitignore +17 -0
  2. data/.rspec +2 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +29 -0
  6. data/Rakefile +21 -0
  7. data/lib/one_password.rb +19 -0
  8. data/lib/one_password/encryption.rb +74 -0
  9. data/lib/one_password/encryption_key.rb +34 -0
  10. data/lib/one_password/errors.rb +13 -0
  11. data/lib/one_password/item.rb +156 -0
  12. data/lib/one_password/keychain.rb +47 -0
  13. data/lib/one_password/profile.rb +94 -0
  14. data/lib/one_password/version.rb +3 -0
  15. data/one_password.gemspec +27 -0
  16. data/spec/fixtures/1Password.agilekeychain/1Password.html +3870 -0
  17. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/l/0EDE2B13D7AC4E2C9105842682ACB187 +0 -0
  18. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/l/0EDE2B13D7AC4E2C9105842682ACB187.def +1 -0
  19. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/l/13C8E12AC8E54B1F873BAB0824E521BC +0 -0
  20. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/l/13C8E12AC8E54B1F873BAB0824E521BC.def +1 -0
  21. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/l/2A632FDD32F5445E91EB5636C7580447 +0 -0
  22. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/l/2A632FDD32F5445E91EB5636C7580447.def +1 -0
  23. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/l/358B7411EB8B45CD9CE592ED16F3E9DE +0 -0
  24. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/l/358B7411EB8B45CD9CE592ED16F3E9DE.def +1 -0
  25. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/l/468B1E24F93B413DAD57ABE6F1C01DF6 +0 -0
  26. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/l/468B1E24F93B413DAD57ABE6F1C01DF6.def +1 -0
  27. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/l/5ADFF73C09004C448D45565BC4750DE2 +0 -0
  28. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/l/5ADFF73C09004C448D45565BC4750DE2.def +1 -0
  29. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/l/72366D161D9E43D98E58EB801DAD1EF8 +0 -0
  30. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/l/72366D161D9E43D98E58EB801DAD1EF8.def +1 -0
  31. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/l/D8F79F17D6384808848B213EB4946ECA +0 -0
  32. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/l/D8F79F17D6384808848B213EB4946ECA.def +1 -0
  33. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/l/EC0A40400ABB4B16926B7417E95C9669 +0 -0
  34. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/l/EC0A40400ABB4B16926B7417E95C9669.def +1 -0
  35. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/l/F3707FA58EA7480884BC6A662658E039 +0 -0
  36. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/l/F3707FA58EA7480884BC6A662658E039.def +1 -0
  37. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/l/F5F099B210F248348E22934DDC3338B2 +0 -0
  38. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/l/F5F099B210F248348E22934DDC3338B2.def +1 -0
  39. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/l/F78CEC04078743B6975511A6FDDBED7E +0 -0
  40. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/l/F78CEC04078743B6975511A6FDDBED7E.def +1 -0
  41. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/m/0EDE2B13D7AC4E2C9105842682ACB187 +0 -0
  42. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/m/0EDE2B13D7AC4E2C9105842682ACB187.def +1 -0
  43. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/m/13C8E12AC8E54B1F873BAB0824E521BC +0 -0
  44. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/m/13C8E12AC8E54B1F873BAB0824E521BC.def +1 -0
  45. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/m/2A632FDD32F5445E91EB5636C7580447 +0 -0
  46. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/m/2A632FDD32F5445E91EB5636C7580447.def +1 -0
  47. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/m/358B7411EB8B45CD9CE592ED16F3E9DE +0 -0
  48. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/m/358B7411EB8B45CD9CE592ED16F3E9DE.def +1 -0
  49. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/m/468B1E24F93B413DAD57ABE6F1C01DF6 +0 -0
  50. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/m/468B1E24F93B413DAD57ABE6F1C01DF6.def +1 -0
  51. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/m/5ADFF73C09004C448D45565BC4750DE2 +0 -0
  52. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/m/5ADFF73C09004C448D45565BC4750DE2.def +1 -0
  53. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/m/72366D161D9E43D98E58EB801DAD1EF8 +0 -0
  54. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/m/72366D161D9E43D98E58EB801DAD1EF8.def +1 -0
  55. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/m/D8F79F17D6384808848B213EB4946ECA +0 -0
  56. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/m/D8F79F17D6384808848B213EB4946ECA.def +1 -0
  57. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/m/EC0A40400ABB4B16926B7417E95C9669 +0 -0
  58. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/m/EC0A40400ABB4B16926B7417E95C9669.def +1 -0
  59. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/m/F3707FA58EA7480884BC6A662658E039 +0 -0
  60. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/m/F3707FA58EA7480884BC6A662658E039.def +1 -0
  61. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/m/F5F099B210F248348E22934DDC3338B2 +0 -0
  62. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/m/F5F099B210F248348E22934DDC3338B2.def +1 -0
  63. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/m/F78CEC04078743B6975511A6FDDBED7E +0 -0
  64. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/m/F78CEC04078743B6975511A6FDDBED7E.def +1 -0
  65. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/s/0EDE2B13D7AC4E2C9105842682ACB187 +0 -0
  66. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/s/0EDE2B13D7AC4E2C9105842682ACB187.def +1 -0
  67. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/s/13C8E12AC8E54B1F873BAB0824E521BC +0 -0
  68. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/s/13C8E12AC8E54B1F873BAB0824E521BC.def +1 -0
  69. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/s/2A632FDD32F5445E91EB5636C7580447 +0 -0
  70. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/s/2A632FDD32F5445E91EB5636C7580447.def +1 -0
  71. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/s/358B7411EB8B45CD9CE592ED16F3E9DE +0 -0
  72. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/s/358B7411EB8B45CD9CE592ED16F3E9DE.def +1 -0
  73. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/s/468B1E24F93B413DAD57ABE6F1C01DF6 +0 -0
  74. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/s/468B1E24F93B413DAD57ABE6F1C01DF6.def +1 -0
  75. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/s/5ADFF73C09004C448D45565BC4750DE2 +0 -0
  76. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/s/5ADFF73C09004C448D45565BC4750DE2.def +1 -0
  77. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/s/72366D161D9E43D98E58EB801DAD1EF8 +0 -0
  78. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/s/72366D161D9E43D98E58EB801DAD1EF8.def +1 -0
  79. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/s/D8F79F17D6384808848B213EB4946ECA +0 -0
  80. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/s/D8F79F17D6384808848B213EB4946ECA.def +1 -0
  81. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/s/EC0A40400ABB4B16926B7417E95C9669 +0 -0
  82. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/s/EC0A40400ABB4B16926B7417E95C9669.def +1 -0
  83. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/s/F3707FA58EA7480884BC6A662658E039 +0 -0
  84. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/s/F3707FA58EA7480884BC6A662658E039.def +1 -0
  85. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/s/F5F099B210F248348E22934DDC3338B2 +0 -0
  86. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/s/F5F099B210F248348E22934DDC3338B2.def +1 -0
  87. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/s/F78CEC04078743B6975511A6FDDBED7E +0 -0
  88. data/spec/fixtures/1Password.agilekeychain/a/default/thumb/s/F78CEC04078743B6975511A6FDDBED7E.def +1 -0
  89. data/spec/fixtures/1Password.agilekeychain/config/buildnum +1 -0
  90. data/spec/fixtures/1Password.agilekeychain/config/use-thumbnails +1 -0
  91. data/spec/fixtures/1Password.agilekeychain/data/default/.1password.keys +0 -0
  92. data/spec/fixtures/1Password.agilekeychain/data/default/.password.hint +1 -0
  93. data/spec/fixtures/1Password.agilekeychain/data/default/0EDE2B13D7AC4E2C9105842682ACB187.1password +1 -0
  94. data/spec/fixtures/1Password.agilekeychain/data/default/13C8E12AC8E54B1F873BAB0824E521BC.1password +1 -0
  95. data/spec/fixtures/1Password.agilekeychain/data/default/1password.keys +0 -0
  96. data/spec/fixtures/1Password.agilekeychain/data/default/27DCFA2810B24083A3ECC7CEABC7C0A9.1password +1 -0
  97. data/spec/fixtures/1Password.agilekeychain/data/default/2A632FDD32F5445E91EB5636C7580447.1password +1 -0
  98. data/spec/fixtures/1Password.agilekeychain/data/default/358B7411EB8B45CD9CE592ED16F3E9DE.1password +1 -0
  99. data/spec/fixtures/1Password.agilekeychain/data/default/3A47A0E3FEE948ADA9028FF0DA053CDB.1password +1 -0
  100. data/spec/fixtures/1Password.agilekeychain/data/default/468B1E24F93B413DAD57ABE6F1C01DF6.1password +1 -0
  101. data/spec/fixtures/1Password.agilekeychain/data/default/4E36C011EE8348B1B24418218B04018C.1password +1 -0
  102. data/spec/fixtures/1Password.agilekeychain/data/default/5ADFF73C09004C448D45565BC4750DE2.1password +1 -0
  103. data/spec/fixtures/1Password.agilekeychain/data/default/72366D161D9E43D98E58EB801DAD1EF8.1password +1 -0
  104. data/spec/fixtures/1Password.agilekeychain/data/default/D06307ADA44C4031BA2FF4B174DE79CB.1password +1 -0
  105. data/spec/fixtures/1Password.agilekeychain/data/default/D1820AA8CB534AC6A4B5A2C0263FD3B2.1password +1 -0
  106. data/spec/fixtures/1Password.agilekeychain/data/default/D8F79F17D6384808848B213EB4946ECA.1password +1 -0
  107. data/spec/fixtures/1Password.agilekeychain/data/default/E482B70C038D4DD78A0940728FA737BF.1password +1 -0
  108. data/spec/fixtures/1Password.agilekeychain/data/default/EC0A40400ABB4B16926B7417E95C9669.1password +1 -0
  109. data/spec/fixtures/1Password.agilekeychain/data/default/F3707FA58EA7480884BC6A662658E039.1password +1 -0
  110. data/spec/fixtures/1Password.agilekeychain/data/default/F5F099B210F248348E22934DDC3338B2.1password +1 -0
  111. data/spec/fixtures/1Password.agilekeychain/data/default/F7883ADDE5944B349ABB5CBEC20F39BE.1password +1 -0
  112. data/spec/fixtures/1Password.agilekeychain/data/default/F78CEC04078743B6975511A6FDDBED7E.1password +1 -0
  113. data/spec/fixtures/1Password.agilekeychain/data/default/contents.js +1 -0
  114. data/spec/fixtures/1Password.agilekeychain/data/default/encryptionKeys.js +1 -0
  115. data/spec/fixtures/1Password.tsv +20 -0
  116. data/spec/fixtures/config.ru +1 -0
  117. data/spec/one_password/keychain_spec.rb +78 -0
  118. data/spec/spec_helper.rb +15 -0
  119. metadata +370 -0
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in one_password.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Alexander Semyonov
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # OnePassword
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'one_password'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install one_password
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,21 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ begin
4
+ require 'yard'
5
+ YARD::Rake::YardocTask.new(:doc)
6
+ rescue LoadError
7
+ task :doc do
8
+ abort 'YARD is not available. In order to run yardoc, you must run: `bundle install``'
9
+ end
10
+ end
11
+
12
+ begin
13
+ require 'rspec/core/rake_task'
14
+ RSpec::Core::RakeTask.new(:spec)
15
+ rescue LoadError
16
+ task :spec do
17
+ abort 'RSpec is not available. In order to run specs, you must run: `bundle install`'
18
+ end
19
+ end
20
+
21
+ task default: :spec
@@ -0,0 +1,19 @@
1
+ require 'one_password/version'
2
+
3
+ module OnePassword
4
+ WEBFORMS = 'webforms.WebForm'
5
+ FOLDERS = 'system.folder.Regular'
6
+ NOTES = 'securenotes.SecureNote'
7
+ IDENTITIES = 'identities.Identity'
8
+ PASSWORDS = 'passwords.Password'
9
+ WALLET = 'wallet'
10
+ SOFTWARE_LICENSES = 'wallet.computer.License'
11
+ TRASHED = 'trashed'
12
+ ACCOUNT = 'account'
13
+ ACCOUNT_ONLINESERVICE = 'wallet.onlineservices.'
14
+ ACCOUNT_COMPUTER = 'wallet.computer.'
15
+ CATEGORIES = [WEBFORMS, NOTES, WALLET, PASSWORDS, IDENTITIES, SOFTWARE_LICENSES, :folders,
16
+ ACCOUNT, TRASHED,].map { |type| type.to_sym }
17
+ end
18
+
19
+ require 'one_password/keychain'
@@ -0,0 +1,74 @@
1
+ require 'base64'
2
+ require 'openssl'
3
+ require 'pbkdf2'
4
+ require 'cgi'
5
+
6
+ module OnePassword
7
+ class Encryption
8
+ ZERO_IV = "\x00" * 8
9
+ NR = 10
10
+ NK = 4
11
+
12
+ def self.decrypt_using_pbkdf2(data, password, iterations)
13
+ encrypted = Base64.decode64(data)
14
+ salt = ZERO_IV
15
+ if salted?(encrypted)
16
+ salt = encrypted[8, 8]
17
+ encrypted = encrypted[16..-1]
18
+ end
19
+
20
+ derived_key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(password, salt, iterations, 32)
21
+
22
+ key = derived_key.slice(0..15)
23
+ iv = derived_key.slice(16..-1)
24
+
25
+ decrypt_using_key_and_ivec(encrypted, key, iv)
26
+ end
27
+
28
+ def self.decrypt_using_key(encrypted, encryption_key)
29
+ encrypted = Base64.decode64(encrypted)
30
+
31
+ if Encryption.salted?(encrypted)
32
+ salt = encrypted[8, 8]
33
+ encrypted = encrypted[16..-1]
34
+
35
+ key, iv = Encryption.open_ssl_key(encryption_key, salt)
36
+ else
37
+ key = OpenSSL::Digest::MD5.digest(encryption_key)
38
+ iv = Encryption::ZERO_IV
39
+ end
40
+
41
+ plain_text = Encryption.decrypt_using_key_and_ivec(encrypted, key, iv)
42
+
43
+
44
+ CGI::unescape(CGI::escape(plain_text))
45
+
46
+ end
47
+
48
+ def self.decrypt_using_key_and_ivec(encrypted, key, iv)
49
+ aes = OpenSSL::Cipher.new('AES-128-CBC')
50
+ aes.decrypt
51
+ aes.key = key
52
+ aes.iv = iv
53
+ aes.update(encrypted) << aes.final
54
+ end
55
+
56
+ def self.salted?(string)
57
+ (string =~ /\ASalted__/)
58
+ end
59
+
60
+ def self.open_ssl_key(password, salt)
61
+ rounds = NR >= 12 ? 3 : 2
62
+ data00 = password + salt
63
+ md5_hash = [OpenSSL::Digest::MD5.digest(data00)]
64
+ result = md5_hash[0]
65
+ 1.upto(rounds - 1) do |i|
66
+ md5_hash[i] = OpenSSL::Digest::MD5.digest(md5_hash[i - 1] + data00)
67
+ result += md5_hash[i]
68
+ end
69
+ key = result.slice(0..(4 * NK - 1))
70
+ iv = result.slice((4 * NK)..(4 * NK + 15))
71
+ [key, iv]
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,34 @@
1
+ require 'one_password/encryption'
2
+
3
+ module OnePassword
4
+ class EncryptionKey
5
+ #noinspection RubyResolve
6
+ attr_accessor :profile, :data, :validation, :level, :iterations, :identifier
7
+
8
+ def initialize(profile, data)
9
+ @profile = profile
10
+ data.each do |name, value|
11
+ send("#{name}=", value)
12
+ end
13
+ end
14
+
15
+ #noinspection RubyResolve
16
+ def iterations=(iterations)
17
+ @iterations = iterations.to_i
18
+ @iterations = 1000 if @iterations < 1000
19
+ @iterations
20
+ end
21
+
22
+ def decrypt(password=self.profile.password)
23
+ @decrypted_key = Encryption.decrypt_using_pbkdf2(data, password, iterations)
24
+ end
25
+
26
+ def decrypted_key
27
+ @decrypted_key || decrypt
28
+ end
29
+
30
+ def valid?
31
+ Encryption.decrypt_using_key(validation, decrypted_key) == decrypted_key
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,13 @@
1
+ module OnePassword
2
+ class Error < StandardError
3
+ end
4
+
5
+ class UndefinedProfile < Error
6
+ def initialize(profile_name)
7
+ super "Undefined profile #{profile_name.inspect}"
8
+ end
9
+ end
10
+
11
+ class NoPassword < Error
12
+ end
13
+ end
@@ -0,0 +1,156 @@
1
+ require 'one_password/encryption'
2
+ require 'active_support/core_ext'
3
+
4
+ module OnePassword
5
+ class Item
6
+ INDEX_UUID = 0
7
+ INDEX_TYPE = 1
8
+ INDEX_NAME = 2
9
+ INDEX_URL = 3
10
+ INDEX_DATE = 4
11
+ INDEX_FOLDER = 5
12
+ INDEX_PASSWORD_STRENGTH = 6
13
+ INDEX_TRASHED = 7
14
+
15
+ # @return [String]
16
+ attr_accessor :uuid, :type, :title, :domain, :updated_at, :trashed, :security_level, :open_contents, :encrypted#,
17
+ #:location_key, :open_contents, :key_id, :location, :encrypted,
18
+ #:created_at, :trashed, :type_name #,
19
+ #:reminderq, :port, :notes_plain, :cash_limit, :html_name, :fields,
20
+ #:expiry_mm, :expiry_yy, :member_name, :zip, :database_type, :html_action, :phone_toll_free,
21
+ #:lastname, :password, :html_method, :html_id,
22
+ #:renewal_date_yy, :renewal_date_mm, :renewal_date_dd, :cardholder, :credit_limit,
23
+ #:birthdate_yy, :birthdate_mm, :birthdate_dd, :username, :company, :bank, :email, :ccnum,
24
+ #:idisk_storage, :pin, :firstname, :path, :hostname, :website, :country, :interest, :database, :sex,
25
+ #:server, :cvv, :address1, :address2, :cellphone_local, :city, :aim, :jobtitle, :state, :occupation,
26
+ #:department, :busphone_local
27
+ # @return [Time]
28
+
29
+ # @return [OnePassword::Profile]
30
+ attr_reader :profile
31
+
32
+ # @param [Profile] profile
33
+ # @param [Array] data
34
+ def initialize(profile, data)
35
+ @profile = profile
36
+ self.attributes = {
37
+ uuid: data[INDEX_UUID],
38
+ type: data[INDEX_TYPE],
39
+ title: data[INDEX_NAME],
40
+ domain: data[INDEX_URL],
41
+ updated_at: data[INDEX_DATE],
42
+ trashed: data[INDEX_TRASHED]
43
+ }
44
+ end
45
+
46
+ def updated_at=(seconds)
47
+ @updated_at = Time.at(seconds)
48
+ end
49
+
50
+ def created_at=(seconds)
51
+ @created_at = Time.at(seconds)
52
+ end
53
+
54
+ def system?
55
+ @type =~ /^system/
56
+ end
57
+
58
+ def wallet?
59
+ @type =~ /^wallet\.(membership|financial|government)}/
60
+ end
61
+
62
+ def trashed?
63
+ @trashed == 'Y'
64
+ end
65
+
66
+ def category
67
+ @category ||= case type
68
+ when SOFTWARE_LICENSES, WEBFORMS, NOTES, IDENTITIES, PASSWORDS
69
+ type.to_sym
70
+ else
71
+ if type == FOLDERS
72
+ :folders
73
+ elsif wallet?
74
+ WALLET
75
+ else
76
+ ACCOUNT.to_sym
77
+ end
78
+ end
79
+ end
80
+
81
+ def matches?(text)
82
+ title.downcase.index(text) || domain.downcase.index(text)
83
+ end
84
+
85
+ def file_name
86
+ profile.directory.join("#{uuid}.1password")
87
+ end
88
+
89
+ def security_level
90
+ open_contents.try(:[], 'securityLevel') || 'SL5'
91
+ end
92
+
93
+ def encryption_key
94
+ profile.encryption_keys[security_level].decrypted_key
95
+ end
96
+
97
+ def load_encrypted_data
98
+ self.attributes = JSON.parse(File.read(file_name))
99
+ end
100
+
101
+ def encrypted
102
+ load_encrypted_data unless @encrypted
103
+ @encrypted
104
+ end
105
+
106
+ def decrypt_data
107
+ unless @decrypted
108
+ @decrypted = true
109
+ plain_text = Encryption.decrypt_using_key(encrypted, encryption_key)
110
+ attrs = JSON.parse(plain_text)
111
+ self.attributes = attrs
112
+ end
113
+ end
114
+
115
+ def login_username
116
+ find_field_with_designation('username')
117
+ end
118
+
119
+ def login_password
120
+ attributes['password'].presence || find_field_with_designation('password')
121
+ end
122
+
123
+ def find_field_with_designation(designation)
124
+ fields = attributes['fields']
125
+ field = fields.find do |field|
126
+ field['designation'] == designation
127
+ end if fields
128
+
129
+ field['value'] if field
130
+ end
131
+
132
+ def attributes
133
+ @attributes ||= {}
134
+ fields = instance_variables.inject({}) do |result, ivar|
135
+ result[ivar] = instance_variable_get(ivar) unless ivar == :@profile
136
+ result
137
+ end
138
+ @attributes.merge(fields)
139
+ end
140
+
141
+ def attributes=(attrs)
142
+ attrs.each do |name, value|
143
+ if value.present?
144
+ attribute = name.to_s.underscore
145
+ writer = "#{attribute}="
146
+ if respond_to?(writer)
147
+ send(writer, value)
148
+ else
149
+ @attributes ||= {}
150
+ @attributes[attribute] = value
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,47 @@
1
+ # coding: utf-8
2
+
3
+ require 'json'
4
+ require 'one_password/profile'
5
+ require 'one_password/errors'
6
+
7
+ module OnePassword
8
+ class Keychain
9
+ def initialize(directory = '~/Dropbox/1Password.agilekeychain')
10
+ @directory = Pathname(File.expand_path(directory))
11
+ @master_password = nil
12
+ profiles
13
+ end
14
+
15
+ # @return [Profile]
16
+ def current_profile
17
+ @current_profile ||= profiles['default']
18
+ end
19
+
20
+ # @param [String, Profile] profile
21
+ def current_profile=(profile)
22
+ unless profile.is_a?(Profile)
23
+ raise UndefinedProfile.new(profile) unless profiles.key?(profile)
24
+ profile = profiles[profile]
25
+ end
26
+ @profile = profile
27
+ end
28
+
29
+ def password=(password)
30
+ current_profile.password = password
31
+ end
32
+
33
+ protected
34
+
35
+ def data_directory
36
+ @directory.join('data')
37
+ end
38
+
39
+ def profiles
40
+ @profiles ||= Dir["#{data_directory}/*"].inject({}) do |result, dir|
41
+ profile = Profile.new(self, dir)
42
+ result[profile.name] = profile
43
+ result
44
+ end
45
+ end
46
+ end
47
+ end