passwordstate 0.0.4 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +33 -0
  3. data/.gitignore +2 -1
  4. data/.gitlab-ci.yml +10 -7
  5. data/.rubocop.yml +6 -9
  6. data/CHANGELOG.md +7 -0
  7. data/README.md +64 -16
  8. data/Rakefile +2 -2
  9. data/lib/passwordstate/client.rb +62 -16
  10. data/lib/passwordstate/errors.rb +6 -1
  11. data/lib/passwordstate/resource.rb +137 -53
  12. data/lib/passwordstate/resource_list.rb +56 -42
  13. data/lib/passwordstate/resources/active_directory.rb +23 -0
  14. data/lib/passwordstate/resources/address_book.rb +28 -0
  15. data/lib/passwordstate/resources/document.rb +32 -3
  16. data/lib/passwordstate/resources/folder.rb +6 -3
  17. data/lib/passwordstate/resources/host.rb +2 -0
  18. data/lib/passwordstate/resources/password.rb +50 -15
  19. data/lib/passwordstate/resources/password_list.rb +41 -8
  20. data/lib/passwordstate/resources/permission.rb +2 -0
  21. data/lib/passwordstate/resources/privileged_account.rb +32 -0
  22. data/lib/passwordstate/resources/remote_site.rb +26 -0
  23. data/lib/passwordstate/resources/report.rb +2 -0
  24. data/lib/passwordstate/util.rb +22 -2
  25. data/lib/passwordstate/version.rb +3 -1
  26. data/lib/passwordstate.rb +4 -1
  27. data/passwordstate.gemspec +9 -3
  28. data/test/client_test.rb +39 -0
  29. data/test/fixtures/get_password.json +31 -0
  30. data/test/fixtures/get_password_list.json +42 -0
  31. data/test/fixtures/get_password_otp.json +5 -0
  32. data/test/fixtures/password_list_search_passwords.json +60 -0
  33. data/test/fixtures/update_password.json +31 -0
  34. data/test/fixtures/update_password_managed.json +8 -0
  35. data/test/passwordstate_test.rb +10 -2
  36. data/test/resources/password_list_test.rb +81 -0
  37. data/test/resources/password_test.rb +105 -0
  38. data/test/test_helper.rb +8 -0
  39. metadata +56 -15
@@ -1,57 +1,79 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Passwordstate
2
- # A simple resource DSL
4
+ # A simple resource DSL helper
5
+ #
6
+ # rubocop:disable Metrics/ClassLength This DSL class will be large
3
7
  class Resource
4
8
  attr_reader :client
5
9
 
6
- def get(query = {})
7
- set! self.class.get(client, send(self.class.index_field), query)
10
+ # Update the object based off of up-to-date upstream data
11
+ # @return [Resource] self
12
+ def get(**query)
13
+ set! self.class.get(client, send(self.class.index_field), **query)
8
14
  end
9
15
 
10
- def put(body = {}, query = {})
16
+ # Push any unapplied changes to the object
17
+ # @return [Resource] self
18
+ def put(body = {}, **query)
11
19
  to_send = modified.merge(self.class.index_field => send(self.class.index_field))
12
- set! self.class.put(client, to_send.merge(body), query).first
20
+ set! self.class.put(client, to_send.merge(body), **query).first
13
21
  end
14
22
 
15
- def post(body = {}, query = {})
16
- set! self.class.post(client, attributes.merge(body), query)
23
+ # Create the object based off of provided information
24
+ # @return [Resource] self
25
+ def post(body = {}, **query)
26
+ set! self.class.post(client, attributes.merge(body), **query)
17
27
  end
18
28
 
19
- def delete(query = {})
20
- self.class.delete(client, send(self.class.index_field), query)
29
+ # Delete the object
30
+ # @return [Resource] self
31
+ def delete(**query)
32
+ self.class.delete(client, send(self.class.index_field), **query)
21
33
  end
22
34
 
23
35
  def initialize(data)
24
36
  @client = data.delete :_client
25
- set! data, false
37
+ set! data, store_old: false
26
38
  old
27
39
  end
28
40
 
41
+ # Is the object stored on the Passwordstate server
29
42
  def stored?
30
43
  !send(self.class.index_field).nil?
31
44
  end
32
45
 
46
+ # Check if the resource type is available on the connected Passwordstate server
33
47
  def self.available?(_client)
34
48
  true
35
49
  end
36
50
 
37
- def self.all(client, query = {})
51
+ # Retrieve all accessible instances of the resource type, requires the method :get
52
+ def self.all(client, **query)
53
+ raise NotAcceptableError, "Read is not implemented for #{self}" unless acceptable_methods.include? :get
54
+
38
55
  path = query.fetch(:_api_path, api_path)
56
+ reason = query.delete(:_reason)
39
57
  query = passwordstateify_hash query.reject { |k| k.to_s.start_with? '_' }
40
58
 
41
- [client.request(:get, path, query: query)].flatten.map do |object|
59
+ [client.request(:get, path, query: query, reason: reason)].flatten.map do |object|
42
60
  new object.merge(_client: client)
43
61
  end
44
62
  end
45
63
 
46
- def self.get(client, object, query = {})
64
+ # Retrieve a specific instance of the resource type, requires the method :get
65
+ def self.get(client, object, **query)
66
+ raise NotAcceptableError, "Read is not implemented for #{self}" unless acceptable_methods.include? :get
67
+
47
68
  object = object.send(object.class.send(index_field)) if object.is_a? Resource
48
69
 
49
70
  return new _client: client, index_field => object if query[:_bare]
50
71
 
51
72
  path = query.fetch(:_api_path, api_path)
73
+ reason = query.delete(:_reason)
52
74
  query = passwordstateify_hash query.reject { |k| k.to_s.start_with? '_' }
53
75
 
54
- resp = client.request(:get, "#{path}/#{object}", query: query).map do |data|
76
+ resp = client.request(:get, "#{path}/#{object}", query: query, reason: reason).map do |data|
55
77
  new data.merge(_client: client)
56
78
  end
57
79
  return resp.first if resp.one? || resp.empty?
@@ -59,70 +81,98 @@ module Passwordstate
59
81
  resp
60
82
  end
61
83
 
62
- def self.post(client, data, query = {})
84
+ # Create a new instance of the resource type, requires the method :post
85
+ def self.post(client, data, **query)
86
+ raise NotAcceptableError, "Create is not implemented for #{self}" unless acceptable_methods.include? :post
87
+
63
88
  path = query.fetch(:_api_path, api_path)
89
+ reason = query.delete(:_reason)
64
90
  data = passwordstateify_hash data
65
91
  query = passwordstateify_hash query.reject { |k| k.to_s.start_with? '_' }
66
92
 
67
- new [client.request(:post, path, body: data, query: query)].flatten.first.merge(_client: client)
93
+ new [client.request(:post, path, body: data, query: query, reason: reason)].flatten.first.merge(_client: client)
68
94
  end
69
95
 
70
- def self.put(client, data, query = {})
96
+ # Push new data to an instance of the resource type, requires the method :put
97
+ def self.put(client, data, **query)
98
+ raise NotAcceptableError, "Update is not implemented for #{self}" unless acceptable_methods.include? :put
99
+
71
100
  path = query.fetch(:_api_path, api_path)
101
+ reason = query.delete(:_reason)
72
102
  data = passwordstateify_hash data
73
103
  query = passwordstateify_hash query.reject { |k| k.to_s.start_with? '_' }
74
104
 
75
- client.request :put, path, body: data, query: query
105
+ client.request :put, path, body: data, query: query, reason: reason
76
106
  end
77
107
 
78
- def self.delete(client, object, query = {})
108
+ # Delete an instance of the resource type, requires the method :delete
109
+ def self.delete(client, object, **query)
110
+ raise NotAcceptableError, "Delete is not implemented for #{self}" unless acceptable_methods.include? :delete
111
+
79
112
  path = query.fetch(:_api_path, api_path)
113
+ reason = query.delete(:_reason)
80
114
  query = passwordstateify_hash query.reject { |k| k.to_s.start_with? '_' }
81
115
 
82
116
  object = object.send(object.class.send(index_field)) if object.is_a? Resource
83
- client.request :delete, "#{path}/#{object}", query: query
84
- end
85
-
86
- def self.passwordstateify_hash(hash)
87
- Hash[hash.map { |k, v| [ruby_to_passwordstate_field(k), v] }]
117
+ client.request :delete, "#{path}/#{object}", query: query, reason: reason
88
118
  end
89
119
 
90
- def api_path
91
- self.class.instance_variable_get :@api_path
92
- end
93
-
94
- def attributes(opts = {})
95
- ignore_redact = opts.fetch(:ignore_redact, true)
120
+ # Get a hash of all active attributes on the resource instance
121
+ #
122
+ # @param ignore_redact [Boolean] Should any normally redacted fields be displayed in clear-text
123
+ # @param atify [Boolean] Should the resulting hash be returned with instance variable names ('@'-prefixed)
124
+ # @option opts nil_as_string [Boolean] (resource dependent) Should nil values be treated as the empty string
125
+ def attributes(ignore_redact: true, atify: false, **opts)
96
126
  nil_as_string = opts.fetch(:nil_as_string, self.class.nil_as_string)
97
- Hash[(self.class.send(:accessor_field_names) + self.class.send(:read_field_names) + self.class.send(:write_field_names)).map do |field|
127
+ (self.class.send(:accessor_field_names) + self.class.send(:read_field_names) + self.class.send(:write_field_names)).to_h do |field|
98
128
  redact = self.class.send(:field_options)[field]&.fetch(:redact, false) && !ignore_redact
99
- value = instance_variable_get("@#{field}".to_sym) unless redact
129
+ at_field = "@#{field}".to_sym
130
+ field = at_field if atify
131
+ value = instance_variable_get(at_field) unless redact
100
132
  value = '[ REDACTED ]' if redact
101
133
  value = '' if value.nil? && nil_as_string
102
134
  [field, value]
103
- end].reject { |_k, v| v.nil? }
135
+ end.compact
104
136
  end
105
137
 
106
- def inspect
107
- "#{to_s[0..-2]} #{attributes(nil_as_string: false, ignore_redact: false).reject { |_k, v| v.nil? }.map { |k, v| "@#{k}=#{v.inspect}" }.join(', ')}>"
138
+ def pretty_print(pp)
139
+ pp.object_address_group(self) do
140
+ attrs = attributes(atify: true, nil_as_string: false, ignore_redact: false).compact
141
+ pp.seplist(attrs.keys.sort, -> { pp.text ',' }) do |v|
142
+ pp.breakable
143
+ v = v.to_s if v.is_a? Symbol
144
+ pp.text v
145
+ pp.text '='
146
+ pp.group(1) do
147
+ pp.breakable ''
148
+ pp.pp(attrs[v.to_sym])
149
+ end
150
+ end
151
+ end
152
+ end
153
+
154
+ def modified?(field = nil)
155
+ return modified.any? unless field
156
+
157
+ modified.include? field
108
158
  end
109
159
 
110
160
  protected
111
161
 
162
+ def api_path
163
+ self.class.instance_variable_get :@api_path
164
+ end
165
+
112
166
  def modified
113
167
  attribs = attributes
114
168
  attribs.reject { |field| old[field] == attribs[field] }
115
169
  end
116
170
 
117
- def modified?(field)
118
- modified.include? field
119
- end
120
-
121
171
  def old
122
172
  @old ||= attributes.dup
123
173
  end
124
174
 
125
- def set!(data, store_old = true)
175
+ def set!(data, store_old: true)
126
176
  @old = attributes.dup if store_old
127
177
  data = data.attributes if data.is_a? Passwordstate::Resource
128
178
  data.each do |key, value|
@@ -133,8 +183,11 @@ module Passwordstate
133
183
 
134
184
  if !value.nil? && opts&.key?(:is)
135
185
  klass = opts.fetch(:is)
136
- parsed_value = klass.send :parse, value rescue nil if klass.respond_to? :parse
137
- parsed_value ||= klass.send :new, value rescue nil if klass.respond_to? :new
186
+ parsed_value = klass.send :parse, value rescue nil if klass.respond_to?(:parse)
187
+ parsed_value ||= klass.send :new, value rescue nil if klass.respond_to?(:new) && !klass.respond_to?(:parse)
188
+ elsif !value.nil? && opts&.key?(:convert)
189
+ convert = opts.fetch(:convert)
190
+ parsed_value = convert.call(value, direction: :from)
138
191
  end
139
192
 
140
193
  instance_variable_set "@#{field}".to_sym, parsed_value || value
@@ -145,26 +198,52 @@ module Passwordstate
145
198
  class << self
146
199
  alias search all
147
200
 
201
+ # Get/Set the API path for the resource type
148
202
  def api_path(path = nil)
149
203
  @api_path = path unless path.nil?
150
204
  @api_path
151
205
  end
152
206
 
207
+ # Get/Set the index field for the resource type
153
208
  def index_field(field = nil)
154
209
  @index_field = field unless field.nil?
155
210
  @index_field
156
211
  end
157
212
 
213
+ # Get/Set whether the resource type requires nil values to be handled as the empty string
158
214
  def nil_as_string(opt = nil)
159
215
  @nil_as_string = opt unless opt.nil?
160
216
  @nil_as_string
161
217
  end
162
218
 
219
+ # Get/Set acceptable CRUD methods for the resource type
220
+ def acceptable_methods(*meths)
221
+ if meths.empty?
222
+ @acceptable_methods || %i[post get put delete]
223
+ elsif meths.count == 1 && meths.to_s.upcase == meths.to_s
224
+ @acceptable_methods = []
225
+ meths = meths.first.to_s
226
+ @acceptable_methods << :post if meths.include? 'C'
227
+ @acceptable_methods << :get if meths.include? 'R'
228
+ @acceptable_methods << :put if meths.include? 'U'
229
+ @acceptable_methods << :delete if meths.include? 'D'
230
+ else
231
+ @acceptable_methods = meths
232
+ end
233
+ end
234
+
235
+ # Convert a hash from Ruby syntax to Passwordstate syntax
236
+ def passwordstateify_hash(hash)
237
+ hash.transform_keys { |k| ruby_to_passwordstate_field(k) }
238
+ end
239
+
240
+ # Convert a Passwordstate field name into Ruby syntax
163
241
  def passwordstate_to_ruby_field(field)
164
242
  opts = send(:field_options).find { |(_k, v)| v[:name] == field }
165
243
  opts&.first || field.to_s.snake_case.to_sym
166
244
  end
167
245
 
246
+ # Convert a Ruby field name into Passwordstate syntax
168
247
  def ruby_to_passwordstate_field(field)
169
248
  send(:field_options)[field]&.[](:name) || field.to_s.camel_case
170
249
  end
@@ -225,18 +304,23 @@ module Passwordstate
225
304
  end
226
305
  end
227
306
  end
307
+ # rubocop:enable Metrics/ClassLength
228
308
 
229
309
  module Resources
230
- autoload :Document, 'passwordstate/resources/document'
231
- autoload :Folder, 'passwordstate/resources/folder'
232
- autoload :FolderPermission, 'passwordstate/resources/folder'
233
- autoload :Host, 'passwordstate/resources/host'
234
- autoload :PasswordList, 'passwordstate/resources/password_list'
235
- autoload :PasswordListPermission, 'passwordstate/resources/password_list'
236
- autoload :Password, 'passwordstate/resources/password'
237
- autoload :PasswordHistory, 'passwordstate/resources/password'
238
- autoload :PasswordPermission, 'passwordstate/resources/password_list'
239
- autoload :Permission, 'passwordstate/resources/permission'
240
- autoload :Report, 'passwordstate/resources/report'
310
+ autoload :ActiveDirectory, 'passwordstate/resources/active_directory'
311
+ autoload :AddressBook, 'passwordstate/resources/address_book'
312
+ autoload :Document, 'passwordstate/resources/document'
313
+ autoload :Folder, 'passwordstate/resources/folder'
314
+ autoload :FolderPermission, 'passwordstate/resources/folder'
315
+ autoload :Host, 'passwordstate/resources/host'
316
+ autoload :Password, 'passwordstate/resources/password'
317
+ autoload :PasswordList, 'passwordstate/resources/password_list'
318
+ autoload :PasswordListPermission, 'passwordstate/resources/password_list'
319
+ autoload :PasswordHistory, 'passwordstate/resources/password'
320
+ autoload :PasswordPermission, 'passwordstate/resources/password_list'
321
+ autoload :PrivilegedAccount, 'passwordstate/resources/privileged_account'
322
+ autoload :PrivilegedAccountPermission, 'passwordstate/resources/privileged_account'
323
+ autoload :Permission, 'passwordstate/resources/permission'
324
+ autoload :Report, 'passwordstate/resources/report'
241
325
  end
242
326
  end
@@ -1,45 +1,49 @@
1
- module Passwordstate
2
- class ResourceList < Array
3
- Array.public_instance_methods(false).each do |method|
4
- next if %i[reject select slice clear inspect].include?(method.to_sym)
5
-
6
- class_eval <<-EVAL, __FILE__, __LINE__ + 1
7
- def #{method}(*args)
8
- lazy_load unless @loaded
9
- super
10
- end
11
- EVAL
12
- end
1
+ # frozen_string_literal: true
13
2
 
14
- %w[reject select slice].each do |method|
15
- class_eval <<-EVAL, __FILE__, __LINE__ + 1
16
- def #{method}(*args)
17
- lazy_load unless @loaded
18
- data = super
19
- self.clone.clear.concat(data)
20
- end
21
- EVAL
22
- end
23
-
24
- def inspect
25
- lazy_load unless @loaded
26
- super
27
- end
3
+ module Passwordstate
4
+ class ResourceList
5
+ include Enumerable
28
6
 
29
7
  attr_reader :client, :resource, :options
30
8
 
31
- def initialize(client, resource, options = {})
9
+ def initialize(resource, client:, **options)
32
10
  @client = client
33
11
  @resource = resource
34
12
  @loaded = false
35
13
  @options = options
14
+ @data = []
36
15
 
37
16
  options[:only] = [options[:only]].flatten if options.key? :only
38
17
  options[:except] = [options[:except]].flatten if options.key? :except
39
18
  end
40
19
 
20
+ def pretty_print_instance_variables
21
+ instance_variables.reject { |k| %i[@client @data].include? k }.sort
22
+ end
23
+
24
+ def pretty_print(pp)
25
+ return pp.pp self if respond_to? :mocha_inspect
26
+
27
+ pp.pp_object(self)
28
+ end
29
+
30
+ alias inspect pretty_print_inspect
31
+
32
+ def each(&block)
33
+ lazy_load unless @loaded
34
+
35
+ return to_enum(__method__) { @data.size } unless block_given?
36
+
37
+ @data.each(&block)
38
+ end
39
+
40
+ def [](index)
41
+ @data[index]
42
+ end
43
+
41
44
  def clear
42
- @loaded = super
45
+ @data = []
46
+ @loaded = false
43
47
  end
44
48
 
45
49
  def reload
@@ -48,9 +52,12 @@ module Passwordstate
48
52
  end
49
53
 
50
54
  def load(entries)
51
- clear && entries.tap do |loaded|
55
+ clear
56
+ entries.tap do |loaded|
52
57
  loaded.sort! { |a, b| a.send(a.class.index_field) <=> b.send(b.class.index_field) } if options.fetch(:sort, true)
53
- end.each { |obj| self << obj }
58
+ end
59
+ entries.each { |obj| @data << obj }
60
+ @loaded = true
54
61
  self
55
62
  end
56
63
 
@@ -74,58 +81,65 @@ module Passwordstate
74
81
  obj
75
82
  end
76
83
 
77
- def search(query = {})
84
+ def search(**query)
78
85
  raise 'Operation not supported' unless operation_supported?(:search)
79
86
 
80
87
  api_path = options.fetch(:search_path, resource.api_path)
81
88
  query = options.fetch(:search_query, {}).merge(query)
82
89
 
83
- resource.search(client, query.merge(_api_path: api_path))
90
+ resource.search(client, **query.merge(_api_path: api_path))
84
91
  end
85
92
 
86
- def all(query = {})
93
+ def all(**query)
87
94
  raise 'Operation not supported' unless operation_supported?(:all)
88
95
 
89
96
  api_path = options.fetch(:all_path, resource.api_path)
90
97
  query = options.fetch(:all_query, {}).merge(query)
91
98
 
92
- load resource.all(client, query.merge(_api_path: api_path))
99
+ load resource.all(client, **query.merge(_api_path: api_path))
93
100
  end
94
101
 
95
- def get(id, query = {})
102
+ def get(id, **query)
96
103
  raise 'Operation not supported' unless operation_supported?(:get)
97
104
 
105
+ if query.empty? && !@data.empty?
106
+ existing = @data.find do |entry|
107
+ entry.send(entry.class.index_field) == id
108
+ end
109
+ return existing if existing
110
+ end
111
+
98
112
  api_path = options.fetch(:get_path, resource.api_path)
99
113
  query = options.fetch(:get_query, {}).merge(query)
100
114
 
101
- resource.get(client, id, query.merge(_api_path: api_path))
115
+ resource.get(client, id, **query.merge(_api_path: api_path))
102
116
  end
103
117
 
104
- def post(data, query = {})
118
+ def post(data, **query)
105
119
  raise 'Operation not supported' unless operation_supported?(:post)
106
120
 
107
121
  api_path = options.fetch(:post_path, resource.api_path)
108
122
  query = options.fetch(:post_query, {}).merge(query)
109
123
 
110
- resource.post(client, data, query.merge(_api_path: api_path))
124
+ resource.post(client, data, **query.merge(_api_path: api_path))
111
125
  end
112
126
 
113
- def put(data, query = {})
127
+ def put(data, **query)
114
128
  raise 'Operation not supported' unless operation_supported?(:put)
115
129
 
116
130
  api_path = options.fetch(:put_path, resource.api_path)
117
131
  query = options.fetch(:put_query, {}).merge(query)
118
132
 
119
- resource.put(client, data, query.merge(_api_path: api_path))
133
+ resource.put(client, data, **query.merge(_api_path: api_path))
120
134
  end
121
135
 
122
- def delete(id, query = {})
136
+ def delete(id, **query)
123
137
  raise 'Operation not supported' unless operation_supported?(:delete)
124
138
 
125
139
  api_path = options.fetch(:delete_path, resource.api_path)
126
140
  query = options.fetch(:delete_query, {}).merge(query)
127
141
 
128
- resource.delete(client, id, query.merge(_api_path: api_path))
142
+ resource.delete(client, id, **query.merge(_api_path: api_path))
129
143
  end
130
144
 
131
145
  private
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Passwordstate
4
+ module Resources
5
+ class ActiveDirectory < Passwordstate::Resource
6
+ api_path 'activedirectory'
7
+
8
+ index_field :ad_domain_id
9
+
10
+ accessor_fields :ad_domain_netbios, { name: 'ADDomainNetBIOS' },
11
+ :ad_domain_ldap, { name: 'ADDomainLDAP' },
12
+ :fqdn, { name: 'FQDN' },
13
+ :default_domain,
14
+ :pa_read_id, { name: 'PAReadID' },
15
+ :site_id, { name: 'SiteID' },
16
+ :used_for_authentication,
17
+ :protocol,
18
+ :domain_controller_fqdn, { name: 'DomainControllerFQDN' }
19
+
20
+ read_fields :ad_domain_id, { name: 'ADDomainID' }
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Passwordstate
4
+ module Resources
5
+ class AddressBook < Passwordstate::Resource
6
+ api_path 'addressbook'
7
+
8
+ index_field :address_book_id
9
+
10
+ accessor_fields :first_name,
11
+ :surname,
12
+ :email_adress,
13
+ :company,
14
+ :business_phone,
15
+ :personal_phone,
16
+ :street,
17
+ :city,
18
+ :state,
19
+ :zipcode,
20
+ :country,
21
+ :notes,
22
+ :pass_phrase,
23
+ :global_contact
24
+
25
+ read_fields :address_book_id, { name: 'AddressBookID' }
26
+ end
27
+ end
28
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Passwordstate
2
4
  module Resources
3
5
  class Document < Passwordstate::Resource
@@ -10,12 +12,39 @@ module Passwordstate
10
12
 
11
13
  alias title document_name
12
14
 
13
- def self.search(client, store, options = {})
14
- client.request :get, "#{api_path}/#{store}/", query: options
15
+ def self.all(client, store, **query)
16
+ super client, query.merge(_api_path: "#{api_path}/#{validate_store! store}")
17
+ end
18
+
19
+ def self.search(client, store, **options)
20
+ all client, store, **options
15
21
  end
16
22
 
17
23
  def self.get(client, store, object)
18
- client.request :get, "#{api_path}/#{store}/#{object}"
24
+ super client, object, _api_path: "#{api_path}/#{validate_store! store}"
25
+ end
26
+
27
+ def self.post(client, store, data, **query)
28
+ super client, data, query.merge(_api_path: "#{api_path}/#{validate_store! store}")
29
+ end
30
+
31
+ def self.put(client, store, data, **query)
32
+ super client, data, query.merge(_api_path: "#{api_path}/#{validate_store! store}")
33
+ end
34
+
35
+ def self.delete(client, store, object, **query)
36
+ super client, object, query.merge(_api_path: "#{api_path}/#{validate_store! store}")
37
+ end
38
+
39
+ class << self
40
+ private
41
+
42
+ def validate_store!(store)
43
+ raise ArgumentError, 'Store must be one of password, passwordlist, folder' \
44
+ unless %i[password passwordlist folder].include?(store.to_s.downcase.to_sym)
45
+
46
+ store.to_s.downcase.to_sym
47
+ end
19
48
  end
20
49
  end
21
50
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Passwordstate
2
4
  module Resources
3
5
  class Folder < Passwordstate::Resource
@@ -16,7 +18,8 @@ module Passwordstate
16
18
  alias title folder_name
17
19
 
18
20
  def password_lists
19
- Passwordstate::ResourceList.new client, Passwordstate::Resources::PasswordList,
21
+ Passwordstate::ResourceList.new Passwordstate::Resources::PasswordList,
22
+ client: client,
20
23
  search_query: { tree_path: tree_path },
21
24
  all_path: 'searchpasswordlists',
22
25
  all_query: { tree_path: tree_path },
@@ -28,7 +31,7 @@ module Passwordstate
28
31
  FolderPermission.new(_client: client, folder_id: folder_id)
29
32
  end
30
33
 
31
- def full_path(unix = false)
34
+ def full_path(unix: false)
32
35
  return tree_path.tr('\\', '/') if unix
33
36
 
34
37
  tree_path
@@ -40,7 +43,7 @@ module Passwordstate
40
43
 
41
44
  index_field :folder_id
42
45
 
43
- read_fields :folder_id, { name: 'FolderID' } # rubocop:disable Style/BracesAroundHashParameters
46
+ read_fields :folder_id, { name: 'FolderID' }
44
47
  end
45
48
  end
46
49
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'ipaddr'
2
4
 
3
5
  module Passwordstate