one_password 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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