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.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +33 -0
- data/.gitignore +2 -1
- data/.gitlab-ci.yml +10 -7
- data/.rubocop.yml +6 -9
- data/CHANGELOG.md +11 -0
- data/README.md +64 -16
- data/Rakefile +2 -2
- data/lib/passwordstate/client.rb +55 -16
- data/lib/passwordstate/errors.rb +6 -1
- data/lib/passwordstate/resource.rb +131 -49
- data/lib/passwordstate/resource_list.rb +56 -42
- data/lib/passwordstate/resources/active_directory.rb +23 -0
- data/lib/passwordstate/resources/address_book.rb +28 -0
- data/lib/passwordstate/resources/document.rb +32 -3
- data/lib/passwordstate/resources/folder.rb +6 -3
- data/lib/passwordstate/resources/host.rb +2 -0
- data/lib/passwordstate/resources/password.rb +50 -15
- data/lib/passwordstate/resources/password_list.rb +41 -8
- data/lib/passwordstate/resources/permission.rb +2 -0
- data/lib/passwordstate/resources/privileged_account.rb +32 -0
- data/lib/passwordstate/resources/remote_site.rb +26 -0
- data/lib/passwordstate/resources/report.rb +2 -0
- data/lib/passwordstate/util.rb +22 -2
- data/lib/passwordstate/version.rb +3 -1
- data/lib/passwordstate.rb +4 -1
- data/passwordstate.gemspec +9 -3
- data/test/client_test.rb +39 -0
- data/test/fixtures/get_password.json +31 -0
- data/test/fixtures/get_password_list.json +42 -0
- data/test/fixtures/get_password_otp.json +5 -0
- data/test/fixtures/password_list_search_passwords.json +60 -0
- data/test/fixtures/update_password.json +31 -0
- data/test/fixtures/update_password_managed.json +8 -0
- data/test/passwordstate_test.rb +10 -2
- data/test/resources/password_list_test.rb +81 -0
- data/test/resources/password_test.rb +105 -0
- data/test/test_helper.rb +8 -0
- metadata +56 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ead3a53cdb04468a85069d55f1e6764562ea518dfb1ccb2e0b640c883ecfc85f
|
4
|
+
data.tar.gz: 4d4dee12f8a862302b80c7fdb2027a0fb98bfe0a7ab45ece68622a6e42a9ed6a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f306e2cf53329fc8f83f6364d02ec940a3df971f1f9b79e9e9866f40084c75e1fd96aeef42bca640f98e7507bb799006603f2007cf3cd01e5004eaeb93695ff9
|
7
|
+
data.tar.gz: 1f744f7a0f2bdd43b536b71de33bd218f1d496b79fcbfc4d0d284afdf9f80b35307c2acc21165e94d692268c22203ba1adb0a470ef76ba246c6292458cd971a2
|
@@ -0,0 +1,33 @@
|
|
1
|
+
---
|
2
|
+
name: Ruby
|
3
|
+
|
4
|
+
on:
|
5
|
+
push:
|
6
|
+
tags: ["*"]
|
7
|
+
branches: ["master"]
|
8
|
+
pull_request:
|
9
|
+
branches: ["master"]
|
10
|
+
|
11
|
+
permissions:
|
12
|
+
contents: read
|
13
|
+
|
14
|
+
jobs:
|
15
|
+
tests:
|
16
|
+
runs-on: ubuntu-latest
|
17
|
+
strategy:
|
18
|
+
matrix:
|
19
|
+
ruby-version: ['2.7', '3.0', '3.1']
|
20
|
+
|
21
|
+
steps:
|
22
|
+
- uses: actions/checkout@v3
|
23
|
+
- name: Set up Ruby
|
24
|
+
uses: ruby/setup-ruby@v1
|
25
|
+
with:
|
26
|
+
ruby-version: ${{ matrix.ruby-version }}
|
27
|
+
bundler-cache: true
|
28
|
+
- name: Install rubocop
|
29
|
+
run: gem install -N rubocop
|
30
|
+
- name: Rubocop
|
31
|
+
run: rubocop lib/
|
32
|
+
- name: Run tests
|
33
|
+
run: bundle exec rake
|
data/.gitignore
CHANGED
data/.gitlab-ci.yml
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
---
|
2
|
-
image: "ruby:2.
|
2
|
+
image: "ruby:2.7"
|
3
3
|
|
4
4
|
# Cache gems in between builds
|
5
5
|
cache:
|
@@ -12,12 +12,15 @@ before_script:
|
|
12
12
|
|
13
13
|
rubocop:
|
14
14
|
script:
|
15
|
-
- bundle exec rubocop
|
15
|
+
- bundle exec rubocop lib/ -f p -f ju -o junit.xml
|
16
|
+
artifacts:
|
17
|
+
reports:
|
18
|
+
junit: junit.xml
|
16
19
|
|
17
20
|
pages:
|
18
|
-
|
21
|
+
before_script: []
|
19
22
|
script:
|
20
|
-
- gem install yard
|
23
|
+
- gem install yard redcarpet
|
21
24
|
- yard doc -o public/
|
22
25
|
artifacts:
|
23
26
|
paths:
|
@@ -25,6 +28,6 @@ pages:
|
|
25
28
|
only:
|
26
29
|
- master
|
27
30
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
+
rake:
|
32
|
+
script:
|
33
|
+
- bundle exec rake
|
data/.rubocop.yml
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
---
|
2
2
|
AllCops:
|
3
|
-
TargetRubyVersion: 2.
|
3
|
+
TargetRubyVersion: 2.7
|
4
4
|
Exclude:
|
5
5
|
- '*.spec'
|
6
6
|
- 'Rakefile'
|
7
7
|
- 'vendor/**/*'
|
8
|
+
NewCops: enable
|
8
9
|
|
9
10
|
# Don't enforce documentation
|
10
11
|
Style/Documentation:
|
@@ -22,7 +23,7 @@ Style/SafeNavigation:
|
|
22
23
|
Layout/ClosingHeredocIndentation:
|
23
24
|
Enabled: false
|
24
25
|
|
25
|
-
Layout/
|
26
|
+
Layout/HeredocIndentation:
|
26
27
|
Enabled: false
|
27
28
|
|
28
29
|
Metrics/PerceivedComplexity:
|
@@ -34,19 +35,15 @@ Metrics/CyclomaticComplexity:
|
|
34
35
|
Style/RescueModifier:
|
35
36
|
Enabled: false
|
36
37
|
|
38
|
+
Layout/LineLength:
|
39
|
+
Max: 140
|
40
|
+
|
37
41
|
Metrics/MethodLength:
|
38
42
|
Max: 40
|
39
43
|
|
40
|
-
Metrics/LineLength:
|
41
|
-
Max: 190
|
42
|
-
|
43
44
|
Metrics/AbcSize:
|
44
45
|
Enabled: false
|
45
46
|
|
46
|
-
Performance/FixedSize:
|
47
|
-
Exclude:
|
48
|
-
- 'test/**/*'
|
49
|
-
|
50
47
|
Metrics/BlockLength:
|
51
48
|
Exclude:
|
52
49
|
- 'test/**/*'
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
## v0.1.0 2022-12-05
|
2
|
+
|
3
|
+
- Reduced data transferred for regular queries
|
4
|
+
- Fixed support for more modern Ruby (3+)
|
5
|
+
- Fixed error handling and changes for newer Passwordstate versions
|
6
|
+
- Improved pretty printing when debugging
|
7
|
+
|
8
|
+
## v0.0.4 2019-10-23
|
9
|
+
|
10
|
+
- Fixed a client request issue due to a rubocop change
|
11
|
+
|
1
12
|
## v0.0.3 2019-10-23
|
2
13
|
|
3
14
|
- Added method to check if resource types are available
|
data/README.md
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# Passwordstate
|
2
2
|
|
3
|
-
A
|
3
|
+
A Ruby gem for communicating with a [Passwordstate](https://clickstudios.com.au/passwordstate.aspx) instance
|
4
4
|
|
5
|
-
The documentation for the development version can be found at
|
5
|
+
The documentation for the development version can be found at https://iti.gitlab-pages.liu.se/ruby-passwordstate
|
6
6
|
|
7
7
|
## Installation
|
8
8
|
|
@@ -22,22 +22,70 @@ Or install it yourself as:
|
|
22
22
|
|
23
23
|
## Usage example
|
24
24
|
|
25
|
-
```
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
25
|
+
```ruby
|
26
|
+
require 'passwordstate'
|
27
|
+
client = Passwordstate::Client.new 'https://passwordstate.example.com', username: 'user', password: 'password'
|
28
|
+
# Passwordstate::Client.new 'https://passwordstate.example.com', apikey: 'key'
|
29
|
+
# #<Passwordstate::Client:0x0000559eb1fabec8
|
30
|
+
# @headers=
|
31
|
+
# {"accept"=>"application/json", "user-agent"=>"RubyPasswordstate/0.1.0"},
|
32
|
+
# @server_url=#<URI::HTTPS https://passwordstate.example.com>,
|
33
|
+
# @timeout=15,
|
34
|
+
# @validate_certificate=true>
|
35
|
+
|
36
|
+
client.folders
|
37
|
+
# [#<Passwordstate::Resources::Folder:0x000055ed493636e8
|
38
|
+
# @folder_name="Example",
|
39
|
+
# @folder_id=2,
|
40
|
+
# @tree_path="\\Example">,
|
41
|
+
# #<Passwordstate::Resources::Folder:0x000055ed49361fa0
|
42
|
+
# @folder_name="Folder",
|
43
|
+
# @folder_id=3,
|
44
|
+
# @tree_path="\\Example\\Folder">]
|
45
|
+
|
46
|
+
client.password_lists.get(7).passwords
|
47
|
+
# [#<Passwordstate::Resources::Password:0x0000555fda8acdb8
|
48
|
+
# @title="Webserver1",
|
49
|
+
# @user_name="test_web_account",
|
50
|
+
# @account_type_id=0,
|
51
|
+
# @password="[ REDACTED ]",
|
52
|
+
# @allow_export=false,
|
53
|
+
# @password_id=2>,
|
54
|
+
# #<Passwordstate::Resources::Password:0x0000555fda868640
|
55
|
+
# @title="Webserver2",
|
56
|
+
# @user_name="test_web_account2",
|
57
|
+
# @account_type_id=0,
|
58
|
+
# @password="[ REDACTED ]",
|
59
|
+
# @allow_export=false,
|
60
|
+
# @password_id=3>,
|
61
|
+
# #<Passwordstate::Resources::Password:0x0000555fda84da48
|
62
|
+
# @title="Webserver3",
|
63
|
+
# @user_name="test_web_account3",
|
64
|
+
# @account_type_id=0,
|
65
|
+
# @password="[ REDACTED ]",
|
66
|
+
# @allow_export=false,
|
67
|
+
# @password_id=4>]
|
68
|
+
|
69
|
+
pw = client.password_lists.first.passwords.create title: 'example', user_name: 'someone', generate_password: true
|
70
|
+
# #<Passwordstate::Resources::Password:0x0000555fdaf9ce98
|
71
|
+
# @title="example",
|
72
|
+
# @user_name="someone",
|
73
|
+
# @account_type_id=0,
|
74
|
+
# @password="[ REDACTED ]",
|
75
|
+
# @allow_export=true,
|
76
|
+
# @password_id=12,
|
77
|
+
# @generate_password=true,
|
78
|
+
# @password_list_id=6>
|
79
|
+
|
80
|
+
pw.password
|
81
|
+
# "millionfE2rMrcb2LngBTHnDyxdpsGSmK3"
|
82
|
+
|
83
|
+
pw.delete
|
84
|
+
# true
|
39
85
|
```
|
40
86
|
|
87
|
+
A larger - and much more convoluted - example can be found at https://github.com/ananace/foreman_passwordstate/
|
88
|
+
|
41
89
|
## Contributing
|
42
90
|
|
43
91
|
Bug reports and pull requests are welcome on GitHub at https://github.com/ananace/ruby-passwordstate
|
data/Rakefile
CHANGED
data/lib/passwordstate/client.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'json'
|
2
4
|
|
3
5
|
module Passwordstate
|
4
6
|
class Client
|
5
|
-
USER_AGENT = "RubyPasswordstate/#{Passwordstate::VERSION}"
|
7
|
+
USER_AGENT = "RubyPasswordstate/#{Passwordstate::VERSION}"
|
6
8
|
DEFAULT_HEADERS = {
|
7
9
|
'accept' => 'application/json',
|
8
10
|
'user-agent' => USER_AGENT
|
@@ -34,22 +36,31 @@ module Passwordstate
|
|
34
36
|
@http.read_timeout = sec if @http
|
35
37
|
end
|
36
38
|
|
39
|
+
def address_book
|
40
|
+
ResourceList.new Passwordstate::Resources::AddressBook,
|
41
|
+
client: self
|
42
|
+
end
|
43
|
+
|
37
44
|
def folders
|
38
|
-
ResourceList.new
|
45
|
+
ResourceList.new Passwordstate::Resources::Folder,
|
46
|
+
client: self,
|
39
47
|
only: %i[all search post]
|
40
48
|
end
|
41
49
|
|
42
50
|
def hosts
|
43
|
-
ResourceList.new
|
51
|
+
ResourceList.new Passwordstate::Resources::Host,
|
52
|
+
client: self,
|
44
53
|
except: %i[search put]
|
45
54
|
end
|
46
55
|
|
47
56
|
def passwords
|
48
|
-
ResourceList.new
|
57
|
+
ResourceList.new Passwordstate::Resources::Password,
|
58
|
+
client: self
|
49
59
|
end
|
50
60
|
|
51
61
|
def password_lists
|
52
|
-
ResourceList.new
|
62
|
+
ResourceList.new Passwordstate::Resources::PasswordList,
|
63
|
+
client: self,
|
53
64
|
except: %i[put delete]
|
54
65
|
end
|
55
66
|
|
@@ -65,22 +76,32 @@ module Passwordstate
|
|
65
76
|
html = request(:get, '', allow_html: true)
|
66
77
|
version = html.find_line { |line| line.include? '<span>V</span>' }
|
67
78
|
version = />(\d\.\d) \(Build (.+)\)</.match(version)
|
68
|
-
"#{version[1]}.#{version[2]}"
|
79
|
+
"#{version[1]}.#{version[2]}" if version
|
69
80
|
end
|
70
81
|
end
|
71
82
|
|
72
83
|
def version?(compare)
|
84
|
+
if version.nil?
|
85
|
+
logger.debug 'Unable to detect Passwordstate version, assuming recent enough.'
|
86
|
+
return true
|
87
|
+
end
|
88
|
+
|
73
89
|
Gem::Dependency.new(to_s, compare).match?(to_s, version)
|
74
90
|
end
|
75
91
|
|
76
92
|
def require_version(compare)
|
93
|
+
if version.nil?
|
94
|
+
logger.debug 'Unable to detect Passwordstate version, assuming recent enough.'
|
95
|
+
return true
|
96
|
+
end
|
97
|
+
|
77
98
|
raise "Your version of Passwordstate (#{version}) doesn't support the requested feature" unless version? compare
|
78
99
|
end
|
79
100
|
|
80
|
-
def request(method, api_path,
|
101
|
+
def request(method, api_path, query: nil, reason: nil, **options)
|
81
102
|
uri = URI(server_url + "/#{api_type}/" + api_path)
|
82
|
-
uri.query = URI.encode_www_form(
|
83
|
-
uri.query = nil
|
103
|
+
uri.query = URI.encode_www_form(query) unless query.nil?
|
104
|
+
uri.query = nil if uri.query.nil? || uri.query.empty?
|
84
105
|
|
85
106
|
req_obj = Net::HTTP.const_get(method.to_s.capitalize.to_sym).new uri
|
86
107
|
if options.key? :body
|
@@ -92,7 +113,7 @@ module Passwordstate
|
|
92
113
|
req_obj.ntlm_auth(auth_data[:username], auth_data[:password]) if api_type == :winapi
|
93
114
|
headers.each { |h, v| req_obj[h] = v }
|
94
115
|
req_obj['APIKey'] = auth_data[:apikey] if api_type == :api
|
95
|
-
req_obj['Reason'] =
|
116
|
+
req_obj['Reason'] = reason if !reason.nil? && version?('>= 8.4.8449')
|
96
117
|
|
97
118
|
print_http req_obj
|
98
119
|
res_obj = http.request req_obj
|
@@ -104,9 +125,11 @@ module Passwordstate
|
|
104
125
|
if data
|
105
126
|
return data if res_obj.is_a? Net::HTTPSuccess
|
106
127
|
|
107
|
-
data = data
|
128
|
+
# data = data.first if data.is_a? Array
|
129
|
+
# parsed = data.fetch('errors', []) if data.is_a?(Hash) && data.key?('errors')
|
130
|
+
parsed = [data].flatten
|
108
131
|
|
109
|
-
raise Passwordstate::HTTPError.new_by_code(res_obj.code, req_obj, res_obj,
|
132
|
+
raise Passwordstate::HTTPError.new_by_code(res_obj.code, req_obj, res_obj, parsed || [])
|
110
133
|
else
|
111
134
|
return res_obj.body if res_obj.is_a?(Net::HTTPSuccess) && options.fetch(:allow_html, true)
|
112
135
|
|
@@ -114,10 +137,18 @@ module Passwordstate
|
|
114
137
|
end
|
115
138
|
end
|
116
139
|
|
117
|
-
def
|
118
|
-
|
140
|
+
def pretty_print_instance_variables
|
141
|
+
instance_variables.reject { |k| %i[@auth_data @http @logger].include? k }.sort
|
119
142
|
end
|
120
143
|
|
144
|
+
def pretty_print(pp)
|
145
|
+
return pp.pp self if respond_to? :mocha_inspect
|
146
|
+
|
147
|
+
pp.pp_object self
|
148
|
+
end
|
149
|
+
|
150
|
+
alias inspect pretty_print_inspect
|
151
|
+
|
121
152
|
private
|
122
153
|
|
123
154
|
def http
|
@@ -130,7 +161,7 @@ module Passwordstate
|
|
130
161
|
@http.start
|
131
162
|
end
|
132
163
|
|
133
|
-
def print_http(http, truncate
|
164
|
+
def print_http(http, truncate: true)
|
134
165
|
return unless logger.debug?
|
135
166
|
|
136
167
|
if http.is_a? Net::HTTPRequest
|
@@ -147,9 +178,17 @@ module Passwordstate
|
|
147
178
|
|
148
179
|
return if http.body.nil?
|
149
180
|
|
181
|
+
body_cleaner = lambda do |obj|
|
182
|
+
obj.each { |k, v| v.replace('[ REDACTED ]') if k.is_a?(String) && %w[password apikey].include?(k.downcase) } if obj.is_a? Hash
|
183
|
+
end
|
184
|
+
|
150
185
|
clean_body = JSON.parse(http.body) rescue nil
|
151
186
|
if clean_body
|
152
|
-
|
187
|
+
if clean_body.is_a? Array
|
188
|
+
clean_body.each { |val| body_cleaner.call(val) }
|
189
|
+
else
|
190
|
+
body_cleaner.call(clean_body)
|
191
|
+
end
|
153
192
|
else
|
154
193
|
clean_body = http.body
|
155
194
|
end
|
data/lib/passwordstate/errors.rb
CHANGED
@@ -1,6 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Passwordstate
|
2
4
|
class PasswordstateError < RuntimeError; end
|
3
5
|
|
6
|
+
class NotAcceptableError < PasswordstateError; end
|
7
|
+
|
4
8
|
class HTTPError < PasswordstateError
|
5
9
|
attr_reader :code, :request, :response, :errors
|
6
10
|
|
@@ -10,7 +14,8 @@ module Passwordstate
|
|
10
14
|
@response = response
|
11
15
|
@errors = errors
|
12
16
|
|
13
|
-
|
17
|
+
errorstr = errors.map { |err| err['message'] || err['phrase'] || err['error'] }.join('; ')
|
18
|
+
super "Passwordstate responded with an error to the request:\n#{errorstr}"
|
14
19
|
end
|
15
20
|
|
16
21
|
def self.new_by_code(code, req, res, errors = [])
|