keepassx 0.1.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.codeclimate.yml +30 -0
- data/.gitignore +9 -0
- data/.rubocop.yml +64 -0
- data/.travis.yml +12 -3
- data/Gemfile +4 -2
- data/Guardfile +16 -0
- data/LICENSE +19 -0
- data/README.md +33 -0
- data/Rakefile +3 -2
- data/keepassx.gemspec +20 -10
- data/lib/keepassx.rb +42 -3
- data/lib/keepassx/aes_crypt.rb +16 -6
- data/lib/keepassx/database.rb +218 -27
- data/lib/keepassx/database/dumper.rb +87 -0
- data/lib/keepassx/database/finder.rb +102 -0
- data/lib/keepassx/database/loader.rb +217 -0
- data/lib/keepassx/entry.rb +70 -38
- data/lib/keepassx/field/base.rb +191 -0
- data/lib/keepassx/field/entry.rb +32 -0
- data/lib/keepassx/field/group.rb +27 -0
- data/lib/keepassx/fieldable.rb +161 -0
- data/lib/keepassx/group.rb +93 -20
- data/lib/keepassx/hashable_payload.rb +6 -0
- data/lib/keepassx/header.rb +102 -27
- data/lib/keepassx/version.rb +5 -0
- data/spec/factories.rb +23 -0
- data/spec/fixtures/database_empty.kdb +0 -0
- data/spec/fixtures/database_test.kdb +0 -0
- data/spec/fixtures/database_test_dumped.yml +76 -0
- data/spec/fixtures/database_with_key.kdb +0 -0
- data/spec/fixtures/database_with_key.key +1 -0
- data/spec/fixtures/database_with_key2.key +1 -0
- data/spec/fixtures/test_data_array.yml +113 -0
- data/spec/fixtures/test_data_array_dumped.yml +124 -0
- data/spec/keepassx/database_spec.rb +491 -29
- data/spec/keepassx/entry_spec.rb +95 -0
- data/spec/keepassx/group_spec.rb +92 -0
- data/spec/keepassx_spec.rb +17 -0
- data/spec/spec_helper.rb +59 -3
- metadata +143 -69
- data/.rvmrc +0 -1
- data/Gemfile.lock +0 -28
- data/lib/keepassx/entry_field.rb +0 -49
- data/lib/keepassx/group_field.rb +0 -44
- data/spec/test_database.kdb +0 -0
data/lib/keepassx/header.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# The keepass file header.
|
2
4
|
#
|
3
5
|
# From the KeePass doc:
|
@@ -32,38 +34,62 @@
|
|
32
34
|
# - aContentsHash: "plain contents" refers to the database file, minus the
|
33
35
|
# database header, decrypted by FinalKey.
|
34
36
|
# * PlainContents = Decrypt_with_FinalKey(DatabaseFile - DatabaseHeader)
|
37
|
+
|
35
38
|
module Keepassx
|
36
39
|
class Header
|
37
40
|
|
38
41
|
ENCRYPTION_FLAGS = [
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
]
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
42
|
+
[1, 'SHA2'],
|
43
|
+
[2, 'Rijndael'],
|
44
|
+
[2, 'AES'],
|
45
|
+
[4, 'ArcFour'],
|
46
|
+
[8, 'TwoFish'],
|
47
|
+
].freeze
|
48
|
+
|
49
|
+
SIGNATURES = [0x9AA2D903, 0xB54BFB65].freeze
|
50
|
+
|
51
|
+
attr_reader :encryption_iv
|
52
|
+
attr_accessor :groups_count
|
53
|
+
attr_accessor :entries_count
|
54
|
+
attr_accessor :content_hash
|
55
|
+
|
56
|
+
|
57
|
+
# rubocop:disable Metrics/MethodLength
|
58
|
+
def initialize(header_bytes = nil)
|
59
|
+
if header_bytes.nil?
|
60
|
+
@signature1 = SIGNATURES[0]
|
61
|
+
@signature2 = SIGNATURES[1]
|
62
|
+
@flags = 3 # SHA2 hashing, AES encryption
|
63
|
+
@version = 0x30002
|
64
|
+
@master_seed = SecureRandom.random_bytes(16)
|
65
|
+
@encryption_iv = SecureRandom.random_bytes(16)
|
66
|
+
@groups_count = 0
|
67
|
+
@entries_count = 0
|
68
|
+
@master_seed2 = SecureRandom.random_bytes(32)
|
69
|
+
@rounds = 50_000
|
70
|
+
else
|
71
|
+
header_bytes = StringIO.new(header_bytes)
|
72
|
+
@signature1 = header_bytes.read(4).unpack('L*').first
|
73
|
+
@signature2 = header_bytes.read(4).unpack('L*').first
|
74
|
+
@flags = header_bytes.read(4).unpack('L*').first
|
75
|
+
@version = header_bytes.read(4).unpack('L*').first
|
76
|
+
@master_seed = header_bytes.read(16)
|
77
|
+
@encryption_iv = header_bytes.read(16)
|
78
|
+
@groups_count = header_bytes.read(4).unpack('L*').first
|
79
|
+
@entries_count = header_bytes.read(4).unpack('L*').first
|
80
|
+
@content_hash = header_bytes.read(32)
|
81
|
+
@master_seed2 = header_bytes.read(32)
|
82
|
+
@rounds = header_bytes.read(4).unpack('L*').first
|
83
|
+
end
|
61
84
|
end
|
85
|
+
# rubocop:enable Metrics/MethodLength
|
86
|
+
|
62
87
|
|
63
88
|
def valid?
|
64
|
-
@signature1 ==
|
89
|
+
@signature1 == SIGNATURES[0] && @signature2 == SIGNATURES[1]
|
65
90
|
end
|
66
91
|
|
92
|
+
|
67
93
|
def encryption_type
|
68
94
|
ENCRYPTION_FLAGS.each do |(flag_mask, encryption_type)|
|
69
95
|
return encryption_type if @flags & flag_mask
|
@@ -71,17 +97,66 @@ module Keepassx
|
|
71
97
|
'Unknown'
|
72
98
|
end
|
73
99
|
|
74
|
-
|
100
|
+
|
101
|
+
# rubocop:disable Metrics/MethodLength
|
102
|
+
def final_key(master_key, keyfile_data = nil)
|
75
103
|
key = Digest::SHA2.new.update(master_key).digest
|
76
|
-
aes = FastAES.new(@master_seed2)
|
77
104
|
|
78
|
-
|
79
|
-
|
105
|
+
if keyfile_data
|
106
|
+
keyfile_hash = extract_keyfile_hash(keyfile_data)
|
107
|
+
key = master_key == '' ? keyfile_hash : Digest::SHA2.new.update(key + keyfile_hash).digest
|
108
|
+
end
|
109
|
+
|
110
|
+
aes = OpenSSL::Cipher.new('AES-256-ECB')
|
111
|
+
aes.encrypt
|
112
|
+
aes.key = @master_seed2
|
113
|
+
aes.padding = 0
|
114
|
+
|
115
|
+
@rounds.times do
|
116
|
+
key = aes.update(key) + aes.final
|
80
117
|
end
|
81
118
|
|
82
119
|
key = Digest::SHA2.new.update(key).digest
|
83
120
|
key = Digest::SHA2.new.update(@master_seed + key).digest
|
84
121
|
key
|
85
122
|
end
|
123
|
+
# rubocop:enable Metrics/MethodLength
|
124
|
+
|
125
|
+
|
126
|
+
# Return encoded header
|
127
|
+
#
|
128
|
+
# @return [String] Encoded header representation.
|
129
|
+
def encode
|
130
|
+
[@signature1].pack('L*') <<
|
131
|
+
[@signature2].pack('L*') <<
|
132
|
+
[@flags].pack('L*') <<
|
133
|
+
[@version].pack('L*') <<
|
134
|
+
@master_seed <<
|
135
|
+
@encryption_iv <<
|
136
|
+
[@groups_count].pack('L*') <<
|
137
|
+
[@entries_count].pack('L*') <<
|
138
|
+
@content_hash <<
|
139
|
+
@master_seed2 <<
|
140
|
+
[@rounds].pack('L*')
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
|
147
|
+
def extract_keyfile_hash(keyfile_data)
|
148
|
+
# Hex encoded key
|
149
|
+
if keyfile_data.size == 64
|
150
|
+
[keyfile_data].pack('H*')
|
151
|
+
|
152
|
+
# Raw key
|
153
|
+
elsif keyfile_data.size == 32
|
154
|
+
keyfile_data
|
155
|
+
|
156
|
+
else
|
157
|
+
Digest::SHA2.new.update(keyfile_data).digest
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
86
161
|
end
|
87
162
|
end
|
data/spec/factories.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
FactoryBot.define do
|
2
|
+
|
3
|
+
factory :group, class: Keepassx::Group do
|
4
|
+
id { 1 }
|
5
|
+
name { 'test_group' }
|
6
|
+
icon { 20 }
|
7
|
+
|
8
|
+
initialize_with { new(attributes) }
|
9
|
+
end
|
10
|
+
|
11
|
+
factory :entry, class: Keepassx::Entry do
|
12
|
+
name { 'test_entry' }
|
13
|
+
group { build(:group) }
|
14
|
+
username { 'test' }
|
15
|
+
password { 'test' }
|
16
|
+
icon { 20 }
|
17
|
+
url { 'https://example.com' }
|
18
|
+
notes { 'Test comment' }
|
19
|
+
|
20
|
+
initialize_with { new(attributes) }
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
Binary file
|
Binary file
|
@@ -0,0 +1,76 @@
|
|
1
|
+
---
|
2
|
+
- id: 503880962
|
3
|
+
name: Internet
|
4
|
+
icon: 1
|
5
|
+
level: 0
|
6
|
+
flags: 0
|
7
|
+
entries:
|
8
|
+
- id: 678f624ea0204444f775fb252d754ff3
|
9
|
+
group_id: 503880962
|
10
|
+
icon: 1
|
11
|
+
name: test entry
|
12
|
+
url: http://example.com/testurl
|
13
|
+
username: testuser
|
14
|
+
password: testpassword
|
15
|
+
notes: test comment
|
16
|
+
- id: 8bfa7a503bb6046b1ab6331f3de3bdeb
|
17
|
+
group_id: 503880962
|
18
|
+
icon: 1
|
19
|
+
name: entry2
|
20
|
+
url: http://example.com
|
21
|
+
username: user
|
22
|
+
password: pass2
|
23
|
+
notes: comment
|
24
|
+
- id: '00000000000000000000000000000000'
|
25
|
+
group_id: 503880962
|
26
|
+
icon: 0
|
27
|
+
name: Meta-Info
|
28
|
+
url: "$"
|
29
|
+
username: SYSTEM
|
30
|
+
password: ''
|
31
|
+
notes: KPX_CUSTOM_ICONS_4
|
32
|
+
- id: '00000000000000000000000000000000'
|
33
|
+
group_id: 503880962
|
34
|
+
icon: 0
|
35
|
+
name: Meta-Info
|
36
|
+
url: "$"
|
37
|
+
username: SYSTEM
|
38
|
+
password: ''
|
39
|
+
notes: KPX_GROUP_TREE_STATE
|
40
|
+
groups:
|
41
|
+
- id: 3210836170
|
42
|
+
name: Web
|
43
|
+
icon: 61
|
44
|
+
level: 1
|
45
|
+
flags: 0
|
46
|
+
entries:
|
47
|
+
- id: fb3bd253ca650a0faaf8b1c94ffdd648
|
48
|
+
group_id: 3210836170
|
49
|
+
icon: 20
|
50
|
+
name: test entry 2
|
51
|
+
url: ''
|
52
|
+
username: ''
|
53
|
+
password: ''
|
54
|
+
notes: ''
|
55
|
+
groups:
|
56
|
+
- id: 1532025522
|
57
|
+
name: Wikipedia
|
58
|
+
icon: 54
|
59
|
+
level: 2
|
60
|
+
flags: 0
|
61
|
+
entries: []
|
62
|
+
groups: []
|
63
|
+
- id: 2863278214
|
64
|
+
name: eMail
|
65
|
+
icon: 19
|
66
|
+
level: 0
|
67
|
+
flags: 0
|
68
|
+
entries: []
|
69
|
+
groups: []
|
70
|
+
- id: 1190700005
|
71
|
+
name: Backup
|
72
|
+
icon: 4
|
73
|
+
level: 0
|
74
|
+
flags: 0
|
75
|
+
entries: []
|
76
|
+
groups: []
|
Binary file
|
@@ -0,0 +1 @@
|
|
1
|
+
D27454A8D561887ADA616E972FA4A00BCF2F869FB49E03EF06CB27DB2060737D
|
@@ -0,0 +1 @@
|
|
1
|
+
2BAD1145C4EA3E0A1DE25B3302DE8FC03634916137A3BB03D064647094DB30A1
|
@@ -0,0 +1,113 @@
|
|
1
|
+
---
|
2
|
+
- :name: Internet
|
3
|
+
:icon: 1
|
4
|
+
:creation_time: 2016-09-03 21:09:06.000000000 +02:00
|
5
|
+
:last_mod_time: 2016-09-03 21:09:06.000000000 +02:00
|
6
|
+
:last_acc_time: 2016-09-03 21:09:06.000000000 +02:00
|
7
|
+
:groups:
|
8
|
+
- :name: Web
|
9
|
+
:icon: 61
|
10
|
+
:creation_time: 2016-09-03 21:09:06.000000000 +02:00
|
11
|
+
:last_mod_time: 2016-09-03 21:09:06.000000000 +02:00
|
12
|
+
:last_acc_time: 2016-09-03 21:09:06.000000000 +02:00
|
13
|
+
:groups:
|
14
|
+
- :name: Amazon
|
15
|
+
:icon: 30
|
16
|
+
:creation_time: 2016-09-03 21:09:06.000000000 +02:00
|
17
|
+
:last_mod_time: 2016-09-03 21:09:06.000000000 +02:00
|
18
|
+
:last_acc_time: 2016-09-03 21:09:06.000000000 +02:00
|
19
|
+
- :name: Wikipedia
|
20
|
+
:icon: 54
|
21
|
+
:creation_time: 2016-09-03 21:09:06.000000000 +02:00
|
22
|
+
:last_mod_time: 2016-09-03 21:09:06.000000000 +02:00
|
23
|
+
:last_acc_time: 2016-09-03 21:09:06.000000000 +02:00
|
24
|
+
:entries:
|
25
|
+
- :name: Wikipedia URL 1
|
26
|
+
:username: testwiki1
|
27
|
+
:url: http://wiki.com/testurl1
|
28
|
+
:password: testwiki1
|
29
|
+
:notes: test wiki comment1
|
30
|
+
:group: Wikipedia
|
31
|
+
:id: f720371d-15f1-4b9b-918e-68bb83e4407e
|
32
|
+
:last_mod_time: 2008-09-07 23:40:43
|
33
|
+
:last_acc_time: 2008-09-07 23:40:43
|
34
|
+
:creation_time: 2008-09-07 23:40:43
|
35
|
+
- :name: Wikipedia URL 2
|
36
|
+
:username: testwiki2
|
37
|
+
:url: http://wiki.com/testurl2
|
38
|
+
:password: testwiki2
|
39
|
+
:notes: test wiki comment2
|
40
|
+
:group: Wikipedia
|
41
|
+
:id: e720371d-15f1-4c9b-918e-68bb83e4407e
|
42
|
+
:last_mod_time: 2008-09-07 23:43:43
|
43
|
+
:last_acc_time: 2008-09-07 23:20:43
|
44
|
+
:creation_time: 2008-09-07 23:50:43
|
45
|
+
:entries:
|
46
|
+
- :name: Internet URL 1
|
47
|
+
:username: testuser1
|
48
|
+
:url: http://example.com/testurl1
|
49
|
+
:password: testuser1
|
50
|
+
:notes: test comment1
|
51
|
+
:id: 2d3fae36-e89f-48bd-8900-389cc01ef248
|
52
|
+
:group: Internet
|
53
|
+
:last_mod_time: 2008-09-03 23:40:43
|
54
|
+
:last_acc_time: 2008-09-03 23:40:43
|
55
|
+
:creation_time: 2008-09-03 23:40:43
|
56
|
+
- :name: Internet URL 2
|
57
|
+
:username: testuser2
|
58
|
+
:url: http://example.com/testurl2
|
59
|
+
:password: testuser2
|
60
|
+
:notes: text comment2
|
61
|
+
:id: 866216ea-3a57-43df-8847-becfc3593939
|
62
|
+
:group: Internet
|
63
|
+
:last_mod_time: 2008-09-07 22:40:43
|
64
|
+
:last_acc_time: 2008-09-07 22:40:43
|
65
|
+
:creation_time: 2008-09-07 22:40:43
|
66
|
+
- :name: eMail
|
67
|
+
:icon: 19
|
68
|
+
:creation_time: 2016-09-03 21:09:06.000000000 +02:00
|
69
|
+
:last_mod_time: 2016-09-03 21:09:06.000000000 +02:00
|
70
|
+
:last_acc_time: 2016-09-03 21:09:06.000000000 +02:00
|
71
|
+
:groups:
|
72
|
+
- :name: Personnal
|
73
|
+
:icon: 61
|
74
|
+
:creation_time: 2016-09-03 21:09:06.000000000 +02:00
|
75
|
+
:last_mod_time: 2016-09-03 21:09:06.000000000 +02:00
|
76
|
+
:last_acc_time: 2016-09-03 21:09:06.000000000 +02:00
|
77
|
+
:groups:
|
78
|
+
- :name: Microsoft
|
79
|
+
:icon: 80
|
80
|
+
:creation_time: 2016-09-03 21:09:06.000000000 +02:00
|
81
|
+
:last_mod_time: 2016-09-03 21:09:06.000000000 +02:00
|
82
|
+
:last_acc_time: 2016-09-03 21:09:06.000000000 +02:00
|
83
|
+
- :name: Apple
|
84
|
+
:icon: 81
|
85
|
+
:creation_time: 2016-09-03 21:09:06.000000000 +02:00
|
86
|
+
:last_mod_time: 2016-09-03 21:09:06.000000000 +02:00
|
87
|
+
:last_acc_time: 2016-09-03 21:09:06.000000000 +02:00
|
88
|
+
- :name: Google
|
89
|
+
:icon: 82
|
90
|
+
:creation_time: 2016-09-03 21:09:06.000000000 +02:00
|
91
|
+
:last_mod_time: 2016-09-03 21:09:06.000000000 +02:00
|
92
|
+
:last_acc_time: 2016-09-03 21:09:06.000000000 +02:00
|
93
|
+
- :name: Profesionnal
|
94
|
+
:icon: 61
|
95
|
+
:creation_time: 2016-09-03 21:09:06.000000000 +02:00
|
96
|
+
:last_mod_time: 2016-09-03 21:09:06.000000000 +02:00
|
97
|
+
:last_acc_time: 2016-09-03 21:09:06.000000000 +02:00
|
98
|
+
:groups:
|
99
|
+
- :name: Microsoft
|
100
|
+
:icon: 70
|
101
|
+
:creation_time: 2016-09-03 21:09:06.000000000 +02:00
|
102
|
+
:last_mod_time: 2016-09-03 21:09:06.000000000 +02:00
|
103
|
+
:last_acc_time: 2016-09-03 21:09:06.000000000 +02:00
|
104
|
+
- :name: Apple
|
105
|
+
:icon: 71
|
106
|
+
:creation_time: 2016-09-03 21:09:06.000000000 +02:00
|
107
|
+
:last_mod_time: 2016-09-03 21:09:06.000000000 +02:00
|
108
|
+
:last_acc_time: 2016-09-03 21:09:06.000000000 +02:00
|
109
|
+
- :name: Google
|
110
|
+
:icon: 72
|
111
|
+
:creation_time: 2016-09-03 21:09:06.000000000 +02:00
|
112
|
+
:last_mod_time: 2016-09-03 21:09:06.000000000 +02:00
|
113
|
+
:last_acc_time: 2016-09-03 21:09:06.000000000 +02:00
|
@@ -0,0 +1,124 @@
|
|
1
|
+
---
|
2
|
+
- id: 1
|
3
|
+
name: Internet
|
4
|
+
icon: 1
|
5
|
+
level: 0
|
6
|
+
flags: 0
|
7
|
+
entries:
|
8
|
+
- id: 2d3fae36de89fd48bdd8900d389cc01ef248
|
9
|
+
group_id: 1
|
10
|
+
icon: 1
|
11
|
+
name: Internet URL 1
|
12
|
+
url: http://example.com/testurl1
|
13
|
+
username: testuser1
|
14
|
+
password: testuser1
|
15
|
+
notes: test comment1
|
16
|
+
- id: 866216ead3a57d43dfd8847dbecfc3593939
|
17
|
+
group_id: 1
|
18
|
+
icon: 1
|
19
|
+
name: Internet URL 2
|
20
|
+
url: http://example.com/testurl2
|
21
|
+
username: testuser2
|
22
|
+
password: testuser2
|
23
|
+
notes: text comment2
|
24
|
+
groups:
|
25
|
+
- id: 2
|
26
|
+
name: Web
|
27
|
+
icon: 61
|
28
|
+
level: 1
|
29
|
+
flags: 0
|
30
|
+
entries: []
|
31
|
+
groups:
|
32
|
+
- id: 3
|
33
|
+
name: Amazon
|
34
|
+
icon: 30
|
35
|
+
level: 2
|
36
|
+
flags: 0
|
37
|
+
entries: []
|
38
|
+
groups: []
|
39
|
+
- id: 4
|
40
|
+
name: Wikipedia
|
41
|
+
icon: 54
|
42
|
+
level: 2
|
43
|
+
flags: 0
|
44
|
+
entries:
|
45
|
+
- id: f720371dd15f1d4b9bd918ed68bb83e4407e
|
46
|
+
group_id: 4
|
47
|
+
icon: 1
|
48
|
+
name: Wikipedia URL 1
|
49
|
+
url: http://wiki.com/testurl1
|
50
|
+
username: testwiki1
|
51
|
+
password: testwiki1
|
52
|
+
notes: test wiki comment1
|
53
|
+
- id: e720371dd15f1d4c9bd918ed68bb83e4407e
|
54
|
+
group_id: 4
|
55
|
+
icon: 1
|
56
|
+
name: Wikipedia URL 2
|
57
|
+
url: http://wiki.com/testurl2
|
58
|
+
username: testwiki2
|
59
|
+
password: testwiki2
|
60
|
+
notes: test wiki comment2
|
61
|
+
groups: []
|
62
|
+
- id: 5
|
63
|
+
name: eMail
|
64
|
+
icon: 19
|
65
|
+
level: 0
|
66
|
+
flags: 0
|
67
|
+
entries: []
|
68
|
+
groups:
|
69
|
+
- id: 6
|
70
|
+
name: Personnal
|
71
|
+
icon: 61
|
72
|
+
level: 1
|
73
|
+
flags: 0
|
74
|
+
entries: []
|
75
|
+
groups:
|
76
|
+
- id: 7
|
77
|
+
name: Microsoft
|
78
|
+
icon: 80
|
79
|
+
level: 2
|
80
|
+
flags: 0
|
81
|
+
entries: []
|
82
|
+
groups: []
|
83
|
+
- id: 8
|
84
|
+
name: Apple
|
85
|
+
icon: 81
|
86
|
+
level: 2
|
87
|
+
flags: 0
|
88
|
+
entries: []
|
89
|
+
groups: []
|
90
|
+
- id: 9
|
91
|
+
name: Google
|
92
|
+
icon: 82
|
93
|
+
level: 2
|
94
|
+
flags: 0
|
95
|
+
entries: []
|
96
|
+
groups: []
|
97
|
+
- id: 10
|
98
|
+
name: Profesionnal
|
99
|
+
icon: 61
|
100
|
+
level: 1
|
101
|
+
flags: 0
|
102
|
+
entries: []
|
103
|
+
groups:
|
104
|
+
- id: 11
|
105
|
+
name: Microsoft
|
106
|
+
icon: 70
|
107
|
+
level: 2
|
108
|
+
flags: 0
|
109
|
+
entries: []
|
110
|
+
groups: []
|
111
|
+
- id: 12
|
112
|
+
name: Apple
|
113
|
+
icon: 71
|
114
|
+
level: 2
|
115
|
+
flags: 0
|
116
|
+
entries: []
|
117
|
+
groups: []
|
118
|
+
- id: 13
|
119
|
+
name: Google
|
120
|
+
icon: 72
|
121
|
+
level: 2
|
122
|
+
flags: 0
|
123
|
+
entries: []
|
124
|
+
groups: []
|