passwordstate 0.0.2 → 0.0.3
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.
- checksums.yaml +4 -4
- data/.gitlab-ci.yml +1 -1
- data/CHANGELOG.md +11 -2
- data/lib/passwordstate/client.rb +26 -6
- data/lib/passwordstate/errors.rb +16 -7
- data/lib/passwordstate/resource.rb +30 -10
- data/lib/passwordstate/resource_list.rb +10 -1
- data/lib/passwordstate/resources/folder.rb +14 -0
- data/lib/passwordstate/resources/host.rb +15 -1
- data/lib/passwordstate/resources/password.rb +21 -4
- data/lib/passwordstate/resources/password_list.rb +13 -0
- data/lib/passwordstate/resources/permission.rb +16 -0
- data/lib/passwordstate/resources/report.rb +9 -9
- data/lib/passwordstate/util.rb +6 -3
- data/lib/passwordstate/version.rb +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1fb957850976d1356b56000ef102e03638ff25ded2fc7d978ec9cd4399a51b4d
|
4
|
+
data.tar.gz: 2dc78e6bd303d713bc5e61e2fdfc608f1d65e826edbe04e7bb17698fdc6e88af
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 73027a2b795d5611b76c5733bc5ec1cbcde1de34cc956e48f3bc52c22f473df5d9ce0dcc54540688ef7481669d61db185711b64ada6f1d04528ff459dbd6c9bb
|
7
|
+
data.tar.gz: 6a9bfb4fd0d9f9579876334c219f0403fdb35a3dbf871bb0a884c9de07f86f7f95cdde9cd1434cdba86b0adbabe8c4613e8a30beab0d49db6ca404d741713f1f
|
data/.gitlab-ci.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,7 +1,16 @@
|
|
1
|
+
## v0.0.3 2019-10-23
|
2
|
+
|
3
|
+
- Added method to check if resource types are available
|
4
|
+
- Added `_bare: true` flag on resource getter to create a bare object for
|
5
|
+
method calls
|
6
|
+
- Fixed handling of host objects
|
7
|
+
- Further improved exception handling
|
8
|
+
|
1
9
|
## v0.0.2 2018-08-14
|
2
10
|
|
3
|
-
-
|
4
|
-
|
11
|
+
- Added title and full_path fields to the appropriate resources - almost every
|
12
|
+
resource should now have an obvious human-readable name
|
13
|
+
- Fixed password searching in password lists
|
5
14
|
- Improved exception handling
|
6
15
|
|
7
16
|
## v0.0.1 2018-07-31
|
data/lib/passwordstate/client.rb
CHANGED
@@ -4,11 +4,12 @@ module Passwordstate
|
|
4
4
|
class Client
|
5
5
|
USER_AGENT = "RubyPasswordstate/#{Passwordstate::VERSION}".freeze
|
6
6
|
DEFAULT_HEADERS = {
|
7
|
-
'accept'
|
8
|
-
'user-agent'
|
7
|
+
'accept' => 'application/json',
|
8
|
+
'user-agent' => USER_AGENT
|
9
9
|
}.freeze
|
10
10
|
|
11
11
|
attr_accessor :server_url, :auth_data, :headers, :validate_certificate
|
12
|
+
attr_reader :timeout
|
12
13
|
attr_writer :api_type
|
13
14
|
|
14
15
|
def initialize(url, options = {})
|
@@ -17,6 +18,7 @@ module Passwordstate
|
|
17
18
|
@headers = DEFAULT_HEADERS
|
18
19
|
@auth_data = options.select { |k, _v| %i[apikey username password].include? k }
|
19
20
|
@api_type = options.fetch(:api_type) if options.key? :api_type
|
21
|
+
@timeout = options.fetch(:timeout, 15)
|
20
22
|
end
|
21
23
|
|
22
24
|
def logger
|
@@ -27,6 +29,11 @@ module Passwordstate
|
|
27
29
|
@api_type || (auth_data.key?(:apikey) ? :api : :winapi)
|
28
30
|
end
|
29
31
|
|
32
|
+
def timeout=(sec)
|
33
|
+
@timeout = sec
|
34
|
+
@http.read_timeout = sec if @http
|
35
|
+
end
|
36
|
+
|
30
37
|
def folders
|
31
38
|
ResourceList.new self, Passwordstate::Resources::Folder,
|
32
39
|
only: %i[all search post]
|
@@ -34,7 +41,7 @@ module Passwordstate
|
|
34
41
|
|
35
42
|
def hosts
|
36
43
|
ResourceList.new self, Passwordstate::Resources::Host,
|
37
|
-
|
44
|
+
except: %i[search put]
|
38
45
|
end
|
39
46
|
|
40
47
|
def passwords
|
@@ -62,10 +69,18 @@ module Passwordstate
|
|
62
69
|
end
|
63
70
|
end
|
64
71
|
|
72
|
+
def version?(compare)
|
73
|
+
Gem::Dependency.new(to_s, compare).match?(to_s, version)
|
74
|
+
end
|
75
|
+
|
76
|
+
def require_version(compare)
|
77
|
+
raise "Your version of Passwordstate (#{version}) doesn't support the requested feature" unless version? compare
|
78
|
+
end
|
79
|
+
|
65
80
|
def request(method, api_path, options = {})
|
66
81
|
uri = URI(server_url + "/#{api_type}/" + api_path)
|
67
82
|
uri.query = URI.encode_www_form(options.fetch(:query)) if options.key? :query
|
68
|
-
uri.query = nil
|
83
|
+
uri.query = nil unless uri.query&.any?
|
69
84
|
|
70
85
|
req_obj = Net::HTTP.const_get(method.to_s.capitalize.to_sym).new uri
|
71
86
|
if options.key? :body
|
@@ -77,6 +92,7 @@ module Passwordstate
|
|
77
92
|
req_obj.ntlm_auth(auth_data[:username], auth_data[:password]) if api_type == :winapi
|
78
93
|
headers.each { |h, v| req_obj[h] = v }
|
79
94
|
req_obj['APIKey'] = auth_data[:apikey] if api_type == :api
|
95
|
+
req_obj['Reason'] = options.fetch(:reason) if options.key?(:reason) && version?('>= 8.4.8449')
|
80
96
|
|
81
97
|
print_http req_obj
|
82
98
|
res_obj = http.request req_obj
|
@@ -87,12 +103,14 @@ module Passwordstate
|
|
87
103
|
data = JSON.parse(res_obj.body) rescue nil
|
88
104
|
if data
|
89
105
|
return data if res_obj.is_a? Net::HTTPSuccess
|
106
|
+
|
90
107
|
data = data&.first
|
91
108
|
|
92
109
|
raise Passwordstate::HTTPError.new_by_code(res_obj.code, req_obj, res_obj, data&.fetch('errors', []) || [])
|
93
110
|
else
|
94
|
-
return res_obj.body if options.fetch(:allow_html,
|
95
|
-
|
111
|
+
return res_obj.body if res_obj.is_a?(Net::HTTPSuccess) && options.fetch(:allow_html, true)
|
112
|
+
|
113
|
+
raise Passwordstate::HTTPError.new_by_code(res_obj.code, req_obj, res_obj, [{ 'message' => res_obj.body }])
|
96
114
|
end
|
97
115
|
end
|
98
116
|
|
@@ -106,6 +124,7 @@ module Passwordstate
|
|
106
124
|
@http ||= Net::HTTP.new server_url.host, server_url.port
|
107
125
|
return @http if @http.active?
|
108
126
|
|
127
|
+
@http.read_timeout = @timeout if @timeout
|
109
128
|
@http.use_ssl = server_url.scheme == 'https'
|
110
129
|
@http.verify_mode = validate_certificate ? ::OpenSSL::SSL::VERIFY_NONE : nil
|
111
130
|
@http.start
|
@@ -127,6 +146,7 @@ module Passwordstate
|
|
127
146
|
logger.debug dir
|
128
147
|
|
129
148
|
return if http.body.nil?
|
149
|
+
|
130
150
|
clean_body = JSON.parse(http.body) rescue nil
|
131
151
|
if clean_body
|
132
152
|
clean_body = clean_body.each { |k, v| v.replace('[ REDACTED ]') if k.is_a?(String) && %w[password apikey].include?(k.downcase) }.to_json if http.body
|
data/lib/passwordstate/errors.rb
CHANGED
@@ -10,20 +10,24 @@ module Passwordstate
|
|
10
10
|
@response = response
|
11
11
|
@errors = errors
|
12
12
|
|
13
|
-
super
|
14
|
-
Passwordstate responded with an error to the request;
|
15
|
-
#{errors.map { |err| err['message'] || err['phrase'] }.join(', ')}
|
16
|
-
ERRMSG
|
13
|
+
super "Passwordstate responded with an error to the request:\n#{errors.map { |err| err['message'] || err['phrase'] }.join('; ')}"
|
17
14
|
end
|
18
15
|
|
19
16
|
def self.new_by_code(code, req, res, errors = [])
|
20
17
|
code_i = code.to_i
|
21
18
|
|
22
19
|
errtype = nil
|
20
|
+
errtype ||= UnauthorizedError if code_i == 401
|
21
|
+
errtype ||= ForbiddenError if code_i == 403
|
23
22
|
errtype ||= NotFoundError if code_i == 404
|
24
23
|
errtype ||= ClientError if code_i >= 400 && code_i < 500
|
25
24
|
errtype ||= ServerError if code_i >= 500 && code_i < 600
|
26
25
|
|
26
|
+
if code_i == 302 && res['location'].start_with?('/error/generalerror.aspx?')
|
27
|
+
errtype ||= ServerError
|
28
|
+
errors = [{ 'phrase' => 'Response code 302, most likely meaning an authorization error' }]
|
29
|
+
end
|
30
|
+
|
27
31
|
errtype ||= HTTPError
|
28
32
|
errtype.new(code_i, req, res, errors)
|
29
33
|
end
|
@@ -36,11 +40,16 @@ ERRMSG
|
|
36
40
|
end
|
37
41
|
end
|
38
42
|
|
43
|
+
# 401
|
44
|
+
class UnauthorizedError < ClientError
|
45
|
+
end
|
46
|
+
|
47
|
+
# 403
|
48
|
+
class ForbiddenError < ClientError
|
49
|
+
end
|
50
|
+
|
39
51
|
# 404
|
40
52
|
class NotFoundError < ClientError
|
41
|
-
def initialize(code, req, res, errors = [])
|
42
|
-
super
|
43
|
-
end
|
44
53
|
end
|
45
54
|
|
46
55
|
# 5xx
|
@@ -30,6 +30,10 @@ module Passwordstate
|
|
30
30
|
!send(self.class.index_field).nil?
|
31
31
|
end
|
32
32
|
|
33
|
+
def self.available?(_client)
|
34
|
+
true
|
35
|
+
end
|
36
|
+
|
33
37
|
def self.all(client, query = {})
|
34
38
|
path = query.fetch(:_api_path, api_path)
|
35
39
|
query = passwordstateify_hash query.reject { |k| k.to_s.start_with? '_' }
|
@@ -40,14 +44,18 @@ module Passwordstate
|
|
40
44
|
end
|
41
45
|
|
42
46
|
def self.get(client, object, query = {})
|
47
|
+
object = object.send(object.class.send(index_field)) if object.is_a? Resource
|
48
|
+
|
49
|
+
return new _client: client, index_field => object if query[:_bare]
|
50
|
+
|
43
51
|
path = query.fetch(:_api_path, api_path)
|
44
52
|
query = passwordstateify_hash query.reject { |k| k.to_s.start_with? '_' }
|
45
53
|
|
46
|
-
object = object.send(object.class.send(index_field)) if object.is_a? Resource
|
47
54
|
resp = client.request(:get, "#{path}/#{object}", query: query).map do |data|
|
48
55
|
new data.merge(_client: client)
|
49
56
|
end
|
50
57
|
return resp.first if resp.one? || resp.empty?
|
58
|
+
|
51
59
|
resp
|
52
60
|
end
|
53
61
|
|
@@ -83,17 +91,20 @@ module Passwordstate
|
|
83
91
|
self.class.instance_variable_get :@api_path
|
84
92
|
end
|
85
93
|
|
86
|
-
def attributes(
|
94
|
+
def attributes(opts = {})
|
95
|
+
ignore_redact = opts.fetch(:ignore_redact, true)
|
96
|
+
nil_as_string = opts.fetch(:nil_as_string, self.class.nil_as_string)
|
87
97
|
Hash[(self.class.send(:accessor_field_names) + self.class.send(:read_field_names) + self.class.send(:write_field_names)).map do |field|
|
88
98
|
redact = self.class.send(:field_options)[field]&.fetch(:redact, false) && !ignore_redact
|
89
99
|
value = instance_variable_get("@#{field}".to_sym) unless redact
|
90
100
|
value = '[ REDACTED ]' if redact
|
101
|
+
value = '' if value.nil? && nil_as_string
|
91
102
|
[field, value]
|
92
103
|
end].reject { |_k, v| v.nil? }
|
93
104
|
end
|
94
105
|
|
95
106
|
def inspect
|
96
|
-
"#{to_s[0..-2]} #{attributes(false).reject { |_k, v| v.nil? }.map { |k, v| "@#{k}=#{v.inspect}" }.join(', ')}>"
|
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(', ')}>"
|
97
108
|
end
|
98
109
|
|
99
110
|
protected
|
@@ -144,6 +155,11 @@ module Passwordstate
|
|
144
155
|
@index_field
|
145
156
|
end
|
146
157
|
|
158
|
+
def nil_as_string(opt = nil)
|
159
|
+
@nil_as_string = opt unless opt.nil?
|
160
|
+
@nil_as_string
|
161
|
+
end
|
162
|
+
|
147
163
|
def passwordstate_to_ruby_field(field)
|
148
164
|
opts = send(:field_options).find { |(_k, v)| v[:name] == field }
|
149
165
|
opts&.first || field.to_s.snake_case.to_sym
|
@@ -211,12 +227,16 @@ module Passwordstate
|
|
211
227
|
end
|
212
228
|
|
213
229
|
module Resources
|
214
|
-
autoload :Document,
|
215
|
-
autoload :Folder,
|
216
|
-
autoload :
|
217
|
-
autoload :
|
218
|
-
autoload :
|
219
|
-
autoload :
|
220
|
-
autoload :
|
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'
|
221
241
|
end
|
222
242
|
end
|
@@ -2,6 +2,7 @@ module Passwordstate
|
|
2
2
|
class ResourceList < Array
|
3
3
|
Array.public_instance_methods(false).each do |method|
|
4
4
|
next if %i[reject select slice clear inspect].include?(method.to_sym)
|
5
|
+
|
5
6
|
class_eval <<-EVAL, __FILE__, __LINE__ + 1
|
6
7
|
def #{method}(*args)
|
7
8
|
lazy_load unless @loaded
|
@@ -48,7 +49,7 @@ module Passwordstate
|
|
48
49
|
|
49
50
|
def load(entries)
|
50
51
|
clear && entries.tap do |loaded|
|
51
|
-
loaded.sort! { |
|
52
|
+
loaded.sort! { |a, b| a.send(a.class.index_field) <=> b.send(b.class.index_field) } if options.fetch(:sort, true)
|
52
53
|
end.each { |obj| self << obj }
|
53
54
|
self
|
54
55
|
end
|
@@ -57,6 +58,7 @@ module Passwordstate
|
|
57
58
|
return nil unless %i[search all get post put delete].include?(operation)
|
58
59
|
return false if options.key?(:only) && !options[:only].include?(operation)
|
59
60
|
return false if options.key?(:except) && options[:except].include?(operation)
|
61
|
+
|
60
62
|
!options.fetch("#{operation}_path".to_sym, '').nil?
|
61
63
|
end
|
62
64
|
|
@@ -66,6 +68,7 @@ module Passwordstate
|
|
66
68
|
|
67
69
|
def create(data)
|
68
70
|
raise 'Operation not supported' unless operation_supported?(:post)
|
71
|
+
|
69
72
|
obj = resource.new options.fetch(:object_data, {}).merge(data).merge(_client: client)
|
70
73
|
obj.post
|
71
74
|
obj
|
@@ -73,6 +76,7 @@ module Passwordstate
|
|
73
76
|
|
74
77
|
def search(query = {})
|
75
78
|
raise 'Operation not supported' unless operation_supported?(:search)
|
79
|
+
|
76
80
|
api_path = options.fetch(:search_path, resource.api_path)
|
77
81
|
query = options.fetch(:search_query, {}).merge(query)
|
78
82
|
|
@@ -81,6 +85,7 @@ module Passwordstate
|
|
81
85
|
|
82
86
|
def all(query = {})
|
83
87
|
raise 'Operation not supported' unless operation_supported?(:all)
|
88
|
+
|
84
89
|
api_path = options.fetch(:all_path, resource.api_path)
|
85
90
|
query = options.fetch(:all_query, {}).merge(query)
|
86
91
|
|
@@ -89,6 +94,7 @@ module Passwordstate
|
|
89
94
|
|
90
95
|
def get(id, query = {})
|
91
96
|
raise 'Operation not supported' unless operation_supported?(:get)
|
97
|
+
|
92
98
|
api_path = options.fetch(:get_path, resource.api_path)
|
93
99
|
query = options.fetch(:get_query, {}).merge(query)
|
94
100
|
|
@@ -97,6 +103,7 @@ module Passwordstate
|
|
97
103
|
|
98
104
|
def post(data, query = {})
|
99
105
|
raise 'Operation not supported' unless operation_supported?(:post)
|
106
|
+
|
100
107
|
api_path = options.fetch(:post_path, resource.api_path)
|
101
108
|
query = options.fetch(:post_query, {}).merge(query)
|
102
109
|
|
@@ -105,6 +112,7 @@ module Passwordstate
|
|
105
112
|
|
106
113
|
def put(data, query = {})
|
107
114
|
raise 'Operation not supported' unless operation_supported?(:put)
|
115
|
+
|
108
116
|
api_path = options.fetch(:put_path, resource.api_path)
|
109
117
|
query = options.fetch(:put_query, {}).merge(query)
|
110
118
|
|
@@ -113,6 +121,7 @@ module Passwordstate
|
|
113
121
|
|
114
122
|
def delete(id, query = {})
|
115
123
|
raise 'Operation not supported' unless operation_supported?(:delete)
|
124
|
+
|
116
125
|
api_path = options.fetch(:delete_path, resource.api_path)
|
117
126
|
query = options.fetch(:delete_query, {}).merge(query)
|
118
127
|
|
@@ -23,10 +23,24 @@ module Passwordstate
|
|
23
23
|
object_data: { nest_undef_folder_id: folder_id }
|
24
24
|
end
|
25
25
|
|
26
|
+
def permissions
|
27
|
+
client.require_version('>= 8.4.8449')
|
28
|
+
FolderPermission.new(_client: client, folder_id: folder_id)
|
29
|
+
end
|
30
|
+
|
26
31
|
def full_path(unix = false)
|
27
32
|
return tree_path.tr('\\', '/') if unix
|
33
|
+
|
28
34
|
tree_path
|
29
35
|
end
|
30
36
|
end
|
37
|
+
|
38
|
+
class FolderPermission < Permission
|
39
|
+
api_path 'folderpermissions'
|
40
|
+
|
41
|
+
index_field :folder_id
|
42
|
+
|
43
|
+
read_fields :folder_id, { name: 'FolderID' } # rubocop:disable Style/BracesAroundHashParameters
|
44
|
+
end
|
31
45
|
end
|
32
46
|
end
|
@@ -18,6 +18,7 @@ module Passwordstate
|
|
18
18
|
:remote_connection_parameters,
|
19
19
|
:tag,
|
20
20
|
:title,
|
21
|
+
:discovery_job_id, { name: 'DiscoveryJobID' },
|
21
22
|
:site_id, { name: 'SiteID' },
|
22
23
|
:internal_ip, { name: 'InternalIP', is: IPAddr },
|
23
24
|
:external_ip, { name: 'ExternalIP', is: IPAddr },
|
@@ -26,7 +27,20 @@ module Passwordstate
|
|
26
27
|
:virtual_machine_type,
|
27
28
|
:notes
|
28
29
|
|
29
|
-
read_fields :host_id, { name: 'HostID' }
|
30
|
+
read_fields :host_id, { name: 'HostID' },
|
31
|
+
:site_location
|
32
|
+
|
33
|
+
# TODO: API breaks if all fields aren't included
|
34
|
+
nil_as_string true
|
35
|
+
|
36
|
+
def self.available?(client)
|
37
|
+
client.request :get, api_path
|
38
|
+
true
|
39
|
+
rescue Passwordstate::NotFoundError
|
40
|
+
true
|
41
|
+
rescue Passwordstate::ForbiddenError
|
42
|
+
false
|
43
|
+
end
|
30
44
|
end
|
31
45
|
end
|
32
46
|
end
|
@@ -21,16 +21,18 @@ module Passwordstate
|
|
21
21
|
:generic_field_9, { name: 'GenericField9' },
|
22
22
|
:generic_field_10, { name: 'GenericField10' },
|
23
23
|
:account_type_id, { name: 'AccountTypeID' },
|
24
|
-
:account_type,
|
25
24
|
:notes,
|
26
25
|
:url,
|
27
26
|
:password, { redact: true },
|
28
27
|
:expiry_date, { is: Time },
|
29
28
|
:allow_export,
|
30
29
|
:web_user_id, { name: 'WebUser_ID' },
|
31
|
-
:web_password_id, { name: 'WebPassword_ID' }
|
30
|
+
:web_password_id, { name: 'WebPassword_ID' },
|
31
|
+
:password_list_id, { name: 'PasswordListID' } # Note: POST only # rubocop:disable Style/BracesAroundHashParameters
|
32
32
|
|
33
|
-
read_fields :
|
33
|
+
read_fields :account_type,
|
34
|
+
:password_id, { name: 'PasswordID' },
|
35
|
+
:password_list
|
34
36
|
|
35
37
|
# Things that can be set in a POST/PUT request
|
36
38
|
# TODO: Do this properly
|
@@ -41,7 +43,6 @@ module Passwordstate
|
|
41
43
|
:password_reset_schedule,
|
42
44
|
:add_days_to_expiry_date,
|
43
45
|
:script_id, { name: 'ScriptID' },
|
44
|
-
:password_list_id, { name: 'PasswordListID' }, # POST only
|
45
46
|
:privileged_account_id,
|
46
47
|
:heartbeat_enabled,
|
47
48
|
:heartbeat_schedule,
|
@@ -56,17 +57,24 @@ module Passwordstate
|
|
56
57
|
|
57
58
|
def history
|
58
59
|
raise 'Password history only available on stored passwords' unless stored?
|
60
|
+
|
59
61
|
Passwordstate::ResourceList.new client, PasswordHistory,
|
60
62
|
all_path: "passwordhistory/#{password_id}",
|
61
63
|
only: :all
|
62
64
|
end
|
63
65
|
|
66
|
+
def permissions
|
67
|
+
client.require_version('>= 8.4.8449')
|
68
|
+
PasswordPermission.new(_client: client, password_id: password_id)
|
69
|
+
end
|
70
|
+
|
64
71
|
def delete(recycle = false, query = {})
|
65
72
|
super query.merge(move_to_recycle_bin: recycle)
|
66
73
|
end
|
67
74
|
|
68
75
|
def add_dependency(data = {})
|
69
76
|
raise 'Password dependency creation only available for stored passwords' unless stored?
|
77
|
+
|
70
78
|
client.request :post, 'dependencies', body: self.class.passwordstatify_hash(data.merge(password_id: password_id))
|
71
79
|
end
|
72
80
|
|
@@ -81,6 +89,7 @@ module Passwordstate
|
|
81
89
|
def self.generate(client, options = {})
|
82
90
|
results = client.request(:get, 'generatepassword', query: options).map { |r| r['Password'] }
|
83
91
|
return results.first if results.count == 1
|
92
|
+
|
84
93
|
results
|
85
94
|
end
|
86
95
|
end
|
@@ -130,5 +139,13 @@ module Passwordstate
|
|
130
139
|
raise 'Not applicable'
|
131
140
|
end
|
132
141
|
end
|
142
|
+
|
143
|
+
class PasswordPermission < Permission
|
144
|
+
api_path 'passwordpermissions'
|
145
|
+
|
146
|
+
index_field :password_id
|
147
|
+
|
148
|
+
read_fields :password_id, { name: 'PasswordID' } # rubocop:disable Style/BracesAroundHashParameters
|
149
|
+
end
|
133
150
|
end
|
134
151
|
end
|
@@ -52,11 +52,24 @@ module Passwordstate
|
|
52
52
|
object_data: { password_list_id: password_list_id }
|
53
53
|
end
|
54
54
|
|
55
|
+
def permissions
|
56
|
+
client.require_version('>= 8.4.8449')
|
57
|
+
PasswordListPermission.new(_client: client, password_list_id: password_list_id)
|
58
|
+
end
|
59
|
+
|
55
60
|
def full_path(unix = false)
|
56
61
|
[tree_path, password_list].compact.join('\\').tap do |full|
|
57
62
|
full.tr!('\\', '/') if unix
|
58
63
|
end
|
59
64
|
end
|
60
65
|
end
|
66
|
+
|
67
|
+
class PasswordListPermission < Permission
|
68
|
+
api_path 'passwordlistpermissions'
|
69
|
+
|
70
|
+
index_field :password_list_id
|
71
|
+
|
72
|
+
read_fields :password_list_id, { name: 'PasswordListID' } # rubocop:disable Style/BracesAroundHashParameters
|
73
|
+
end
|
61
74
|
end
|
62
75
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Passwordstate
|
2
|
+
module Resources
|
3
|
+
class Permission < Passwordstate::Resource
|
4
|
+
accessor_fields :permission
|
5
|
+
|
6
|
+
# TODO: Only one of the apply_* can be set at a time
|
7
|
+
write_fields :apply_permissions_for_user_id, { name: 'ApplyPermissionsForUserID' },
|
8
|
+
:apply_permissions_for_security_group_id, { name: 'ApplyPermissionsForSecurityGroupID' },
|
9
|
+
:apply_permissions_for_security_group_name
|
10
|
+
|
11
|
+
def get
|
12
|
+
raise 'Not applicable'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -2,15 +2,15 @@ module Passwordstate
|
|
2
2
|
module Resources
|
3
3
|
class Report < Passwordstate::Resource
|
4
4
|
REPORT_PARAMETERS = {
|
5
|
-
1
|
6
|
-
2
|
7
|
-
3
|
8
|
-
4
|
9
|
-
5
|
10
|
-
6
|
11
|
-
7
|
12
|
-
8
|
13
|
-
9
|
5
|
+
1 => %i[user_id],
|
6
|
+
2 => %i[user_id site_id],
|
7
|
+
3 => %i[user_id duration],
|
8
|
+
4 => %i[user_id site_id duration],
|
9
|
+
5 => %i[duration],
|
10
|
+
6 => %i[],
|
11
|
+
7 => %i[user_id site_id duration],
|
12
|
+
8 => %i[],
|
13
|
+
9 => %i[],
|
14
14
|
10 => %i[duration],
|
15
15
|
11 => %i[duration],
|
16
16
|
12 => %i[site_id],
|
data/lib/passwordstate/util.rb
CHANGED
@@ -11,7 +11,9 @@ module Net
|
|
11
11
|
password: password
|
12
12
|
}
|
13
13
|
@ntlm_auth_information[:domain] = domain unless domain.nil?
|
14
|
-
@ntlm_auth_options = {
|
14
|
+
@ntlm_auth_options = {
|
15
|
+
ntlmv2: true
|
16
|
+
}
|
15
17
|
@ntlm_auth_options[:workstation] = workstation unless workstation.nil?
|
16
18
|
end
|
17
19
|
end
|
@@ -31,6 +33,7 @@ class String
|
|
31
33
|
|
32
34
|
def find_line(&_block)
|
33
35
|
raise ArgumentError, 'No block given' unless block_given?
|
36
|
+
|
34
37
|
each_line do |line|
|
35
38
|
return line if yield line
|
36
39
|
end
|
@@ -59,7 +62,7 @@ module Passwordstate
|
|
59
62
|
|
60
63
|
if challenge && res.code == '401'
|
61
64
|
type2 = Net::NTLM::Message.decode64 challenge
|
62
|
-
type3 = type2.response(req.ntlm_auth_information, req.ntlm_auth_options)
|
65
|
+
type3 = type2.response(req.ntlm_auth_information, req.ntlm_auth_options.dup)
|
63
66
|
|
64
67
|
req['authorization'] = 'NTLM ' + type3.encode64
|
65
68
|
req.body_stream.rewind if req.body_stream
|
@@ -74,4 +77,4 @@ module Passwordstate
|
|
74
77
|
end
|
75
78
|
end
|
76
79
|
|
77
|
-
Net::HTTP.
|
80
|
+
Net::HTTP.prepend Passwordstate::NetHTTPExtensions unless Net::HTTP.ancestors.include? Passwordstate::NetHTTPExtensions
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: passwordstate
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alexander Olofsson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-10-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: logging
|
@@ -119,6 +119,7 @@ files:
|
|
119
119
|
- lib/passwordstate/resources/host.rb
|
120
120
|
- lib/passwordstate/resources/password.rb
|
121
121
|
- lib/passwordstate/resources/password_list.rb
|
122
|
+
- lib/passwordstate/resources/permission.rb
|
122
123
|
- lib/passwordstate/resources/report.rb
|
123
124
|
- lib/passwordstate/util.rb
|
124
125
|
- lib/passwordstate/version.rb
|
@@ -144,8 +145,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
144
145
|
- !ruby/object:Gem::Version
|
145
146
|
version: '0'
|
146
147
|
requirements: []
|
147
|
-
|
148
|
-
rubygems_version: 2.7.6
|
148
|
+
rubygems_version: 3.0.6
|
149
149
|
signing_key:
|
150
150
|
specification_version: 4
|
151
151
|
summary: A ruby API client for interacting with a passwordstate server
|