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