passwordstate 0.0.3 → 0.1.0

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 (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 +11 -0
  7. data/README.md +64 -16
  8. data/Rakefile +2 -2
  9. data/lib/passwordstate/client.rb +55 -16
  10. data/lib/passwordstate/errors.rb +6 -1
  11. data/lib/passwordstate/resource.rb +131 -49
  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,56 +81,82 @@ 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] }]
88
- end
89
-
90
- def api_path
91
- self.class.instance_variable_get :@api_path
117
+ client.request :delete, "#{path}/#{object}", query: query, reason: reason
92
118
  end
93
119
 
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
108
152
  end
109
153
 
110
154
  protected
111
155
 
156
+ def api_path
157
+ self.class.instance_variable_get :@api_path
158
+ end
159
+
112
160
  def modified
113
161
  attribs = attributes
114
162
  attribs.reject { |field| old[field] == attribs[field] }
@@ -122,7 +170,7 @@ module Passwordstate
122
170
  @old ||= attributes.dup
123
171
  end
124
172
 
125
- def set!(data, store_old = true)
173
+ def set!(data, store_old: true)
126
174
  @old = attributes.dup if store_old
127
175
  data = data.attributes if data.is_a? Passwordstate::Resource
128
176
  data.each do |key, value|
@@ -133,8 +181,11 @@ module Passwordstate
133
181
 
134
182
  if !value.nil? && opts&.key?(:is)
135
183
  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
184
+ parsed_value = klass.send :parse, value rescue nil if klass.respond_to?(:parse)
185
+ parsed_value ||= klass.send :new, value rescue nil if klass.respond_to?(:new) && !klass.respond_to?(:parse)
186
+ elsif !value.nil? && opts&.key?(:convert)
187
+ convert = opts.fetch(:convert)
188
+ parsed_value = convert.call(value, direction: :from)
138
189
  end
139
190
 
140
191
  instance_variable_set "@#{field}".to_sym, parsed_value || value
@@ -145,26 +196,52 @@ module Passwordstate
145
196
  class << self
146
197
  alias search all
147
198
 
199
+ # Get/Set the API path for the resource type
148
200
  def api_path(path = nil)
149
201
  @api_path = path unless path.nil?
150
202
  @api_path
151
203
  end
152
204
 
205
+ # Get/Set the index field for the resource type
153
206
  def index_field(field = nil)
154
207
  @index_field = field unless field.nil?
155
208
  @index_field
156
209
  end
157
210
 
211
+ # Get/Set whether the resource type requires nil values to be handled as the empty string
158
212
  def nil_as_string(opt = nil)
159
213
  @nil_as_string = opt unless opt.nil?
160
214
  @nil_as_string
161
215
  end
162
216
 
217
+ # Get/Set acceptable CRUD methods for the resource type
218
+ def acceptable_methods(*meths)
219
+ if meths.empty?
220
+ @acceptable_methods || %i[post get put delete]
221
+ elsif meths.count == 1 && meths.to_s.upcase == meths.to_s
222
+ @acceptable_methods = []
223
+ meths = meths.first.to_s
224
+ @acceptable_methods << :post if meths.include? 'C'
225
+ @acceptable_methods << :get if meths.include? 'R'
226
+ @acceptable_methods << :put if meths.include? 'U'
227
+ @acceptable_methods << :delete if meths.include? 'D'
228
+ else
229
+ @acceptable_methods = meths
230
+ end
231
+ end
232
+
233
+ # Convert a hash from Ruby syntax to Passwordstate syntax
234
+ def passwordstateify_hash(hash)
235
+ hash.transform_keys { |k| ruby_to_passwordstate_field(k) }
236
+ end
237
+
238
+ # Convert a Passwordstate field name into Ruby syntax
163
239
  def passwordstate_to_ruby_field(field)
164
240
  opts = send(:field_options).find { |(_k, v)| v[:name] == field }
165
241
  opts&.first || field.to_s.snake_case.to_sym
166
242
  end
167
243
 
244
+ # Convert a Ruby field name into Passwordstate syntax
168
245
  def ruby_to_passwordstate_field(field)
169
246
  send(:field_options)[field]&.[](:name) || field.to_s.camel_case
170
247
  end
@@ -225,18 +302,23 @@ module Passwordstate
225
302
  end
226
303
  end
227
304
  end
305
+ # rubocop:enable Metrics/ClassLength
228
306
 
229
307
  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'
308
+ autoload :ActiveDirectory, 'passwordstate/resources/active_directory'
309
+ autoload :AddressBook, 'passwordstate/resources/address_book'
310
+ autoload :Document, 'passwordstate/resources/document'
311
+ autoload :Folder, 'passwordstate/resources/folder'
312
+ autoload :FolderPermission, 'passwordstate/resources/folder'
313
+ autoload :Host, 'passwordstate/resources/host'
314
+ autoload :Password, 'passwordstate/resources/password'
315
+ autoload :PasswordList, 'passwordstate/resources/password_list'
316
+ autoload :PasswordListPermission, 'passwordstate/resources/password_list'
317
+ autoload :PasswordHistory, 'passwordstate/resources/password'
318
+ autoload :PasswordPermission, 'passwordstate/resources/password_list'
319
+ autoload :PrivilegedAccount, 'passwordstate/resources/privileged_account'
320
+ autoload :PrivilegedAccountPermission, 'passwordstate/resources/privileged_account'
321
+ autoload :Permission, 'passwordstate/resources/permission'
322
+ autoload :Report, 'passwordstate/resources/report'
241
323
  end
242
324
  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