conjur-api 4.13.0 → 4.14.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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -0
  3. data/CHANGELOG.md +8 -0
  4. data/Gemfile +0 -1
  5. data/README.md +59 -18
  6. data/conjur-api.gemspec +2 -1
  7. data/lib/conjur/acts_as_asset.rb +6 -9
  8. data/lib/conjur/acts_as_role.rb +54 -7
  9. data/lib/conjur/annotations.rb +52 -14
  10. data/lib/conjur/api/audit.rb +54 -8
  11. data/lib/conjur/api/authn.rb +54 -9
  12. data/lib/conjur/api/groups.rb +67 -4
  13. data/lib/conjur/api/hosts.rb +62 -3
  14. data/lib/conjur/api/layers.rb +48 -3
  15. data/lib/conjur/api/pubkeys.rb +140 -9
  16. data/lib/conjur/api.rb +13 -1
  17. data/lib/conjur/base.rb +93 -6
  18. data/lib/conjur/configuration.rb +247 -20
  19. data/lib/conjur/exists.rb +26 -0
  20. data/lib/conjur/group.rb +50 -4
  21. data/lib/conjur/has_attributes.rb +66 -5
  22. data/lib/conjur/resource.rb +155 -13
  23. data/lib/conjur/role.rb +1 -1
  24. data/lib/conjur/standard_methods.rb +7 -2
  25. data/lib/conjur/variable.rb +168 -6
  26. data/lib/conjur-api/version.rb +1 -1
  27. data/lib/conjur-api.rb +2 -0
  28. data/spec/api/authn_spec.rb +6 -3
  29. data/spec/api/groups_spec.rb +1 -1
  30. data/spec/api/pubkeys_spec.rb +4 -4
  31. data/spec/api/roles_spec.rb +4 -2
  32. data/spec/api/users_spec.rb +2 -2
  33. data/spec/api/variables_spec.rb +1 -1
  34. data/spec/helpers/errors_matcher.rb +34 -0
  35. data/spec/helpers/request_helpers.rb +10 -0
  36. data/spec/lib/annotations_spec.rb +5 -2
  37. data/spec/lib/audit_spec.rb +5 -5
  38. data/spec/lib/group_spec.rb +1 -1
  39. data/spec/lib/resource_spec.rb +11 -11
  40. data/spec/lib/role_spec.rb +8 -7
  41. data/spec/lib/user_spec.rb +7 -3
  42. data/spec/ssl_spec.rb +85 -0
  43. data/spec/standard_methods_helper.rb +2 -0
  44. data/spec/variable_spec.rb +5 -3
  45. metadata +34 -7
  46. data/lib/conjur/patches/rest-client.rb +0 -32
@@ -21,28 +21,62 @@
21
21
  require 'conjur/annotations'
22
22
 
23
23
  module Conjur
24
+
25
+ # A `Conjur::Resource` instance represents a Conjur
26
+ # {http://developer.conjur.net/reference/services/authorization/resource Resource}.
27
+ #
28
+ # You should not instantiate this class directly. Instead, you can get an instance from the
29
+ # {Conjur::API#resource} and {Conjur::API#resources} methods, or from the {ActsAsResource#resource} method
30
+ # present on objects representing Conjur assets that have associated resources.
31
+ #
24
32
  class Resource < RestClient::Resource
25
33
  include HasAttributes
26
34
  include PathBased
27
35
  include Exists
28
-
36
+
37
+ # The identifier part of the `resource_id` for this resource. The identifier
38
+ # is the resource id without the `account` and `kind` parts.
39
+ #
40
+ # @example
41
+ # resource = api.resource 'conjur:layer:pubkeys-1.0/public-keys'
42
+ # resource.identifier # => 'pubkeys-1.0/public-keys'
43
+ #
44
+ # @return [String] the identifier part of the id.
29
45
  def identifier
30
46
  match_path(3..-1)
31
47
  end
32
-
48
+
49
+ # The full role id of the role that owns this resource.
50
+ #
51
+ # @example
52
+ # api.current_role # => 'conjur:user:jon'
53
+ # resource = api.create_resource 'conjur:example:resource-owner'
54
+ # resource.owner # => 'conjur:user:jon'
55
+ #
56
+ # @return [String] the full role id of this resource's owner.
33
57
  def ownerid
34
58
  attributes['owner']
35
59
  end
36
60
 
37
61
  alias owner ownerid
38
62
 
39
- # Name convention according to Role#roleid.
63
+ # Return the full id for this resource. The format is `account:kind:identifier`
64
+ #
65
+ # @example
66
+ # resource = api.layer('pubkeys-1.0/public-keys').resource
67
+ # resource.account # => 'conjur'
68
+ # resource.kind # => 'layer'
69
+ # resource.identifier # => 'pubkeys-1.0/public-keys'
70
+ # resource.resourceid # => 'conjur:layer:pubkeys-1.0/public-keys'
71
+ # @return [String]
40
72
  def resourceid
41
73
  [account, kind, identifier].join ':'
42
74
  end
43
75
 
44
76
  alias :resource_id :resourceid
45
-
77
+
78
+
79
+ # @api private
46
80
  def create(options = {})
47
81
  log do |logger|
48
82
  logger << "Creating resource #{resourceid}"
@@ -54,16 +88,42 @@ module Conjur
54
88
  end
55
89
 
56
90
  # Lists roles that have a specified permission on the resource.
91
+ #
92
+ # This will return only roles of which api.current_user is a member.
93
+ #
94
+ # @example
95
+ # resource = api.resource 'conjur:variable:example'
96
+ # resource.permitted_roles 'execute' # => ['conjur:user:admin']
97
+ # resource.permit 'execute', api.user('jon')
98
+ # resource.permitted_roles 'execute' # => ['conjur:user:admin', 'conjur:user:jon']
99
+ #
100
+ # @param permission [String] the permission``
101
+ # @param options [Hash, nil] extra options to pass to RestClient::Resource#get
102
+ # @return [Array<String>] the ids of roles that have `permission` on this resource.
57
103
  def permitted_roles(permission, options = {})
58
104
  JSON.parse RestClient::Resource.new(Conjur::Authz::API.host, self.options)["#{account}/roles/allowed_to/#{permission}/#{path_escape kind}/#{path_escape identifier}"].get(options)
59
105
  end
60
106
 
61
- # Changes the owner of a resource
107
+ # Changes the owner of a resource. You must be the owner of the resource
108
+ # or a member of the owner role to do this.
109
+ #
110
+ # @example
111
+ # resource.owner # => 'conjur:user:admin'
112
+ # resource.give_to 'conjur:user:jon'
113
+ # resource.owner # => 'conjur:user:jon'
114
+ #
115
+ # @param owner [String, #roleid] the new owner.
116
+ # @return [void]
62
117
  def give_to(owner, options = {})
63
118
  owner = cast(owner, :roleid)
64
- self.put(options.merge(owner: owner))
119
+ invalidate do
120
+ self.put(options.merge(owner: owner))
121
+ end
122
+
123
+ nil
65
124
  end
66
125
 
126
+ # @api private
67
127
  def delete(options = {})
68
128
  log do |logger|
69
129
  logger << "Deleting resource #{resourceid}"
@@ -74,6 +134,32 @@ module Conjur
74
134
  super options
75
135
  end
76
136
 
137
+ # Grant `privilege` on this resource to `role`.
138
+ #
139
+ # This operation is idempotent, that is, nothing will happen if
140
+ # you attempt to grant a privilege that the role already has on
141
+ # this resource.
142
+ #
143
+ # @example
144
+ # user = api.user 'bob'
145
+ # resource = api.variable('example').resource
146
+ # resource.permitted_roles 'bake' # => ['conjur:user:admin']
147
+ # resource.permit 'fry', user
148
+ # resource.permitted_roles 'fry' # => ['conjur:user:admin', 'conjur:user:bob']
149
+ # resource.permit ['boil', 'bake'], bob
150
+ # resource.permitted_roles 'boil' # => ['conjur:user:admin', 'conjur:user:bob']
151
+ # resource.permitted_roles 'bake' # => ['conjur:user:admin', 'conjur:user:bob']
152
+ #
153
+ # @param privilege [String, #each] The privilege to grant, for example
154
+ # `'execute'`, `'read'`, or `'update'`. You may also pass an `Enumerable`
155
+ # object, in which case the Strings yielded by #each will all be granted
156
+ #
157
+ # @param role [String, #roleid] The role-ish object or full role id
158
+ # to which the permission is to be granted/
159
+ #
160
+ # @param options [Hash, nil] options to pass through to `RestClient::Resource#post`
161
+ #
162
+ # @return [void]
77
163
  def permit(privilege, role, options = {})
78
164
  role = cast(role, :roleid)
79
165
  eachable(privilege).each do |p|
@@ -91,8 +177,24 @@ module Conjur
91
177
  raise $! unless $!.http_body == "Privilege already granted."
92
178
  end
93
179
  end
180
+ nil
94
181
  end
95
-
182
+
183
+ # The inverse operation of `#permit`. Deny permission `privilege` to `role`
184
+ # on this resource.
185
+ #
186
+ # @example
187
+ #
188
+ # resource.permitted_roles 'execute' # => ['conjur:user:admin', 'conjur:user:alice']
189
+ # resource.deny 'execute', 'conjur:user:alice'
190
+ # resource.permitted_roles 'execute' # => ['conjur:user:admin']
191
+ #
192
+ # @param privilege [String, #each] A permission name or an `Enumerable` of permissions to deny. In the
193
+ # later, all permissions will be denied.
194
+ #
195
+ # @param role [String, :roleid] A full role id or a role-ish object whose permissions we will deny.
196
+ #
197
+ # @return [void]
96
198
  def deny(privilege, role, options = {})
97
199
  role = cast(role, :roleid)
98
200
  eachable(privilege).each do |p|
@@ -104,11 +206,27 @@ module Conjur
104
206
  end
105
207
  self["?deny&privilege=#{query_escape p}&role=#{query_escape role}"].post(options)
106
208
  end
209
+ nil
107
210
  end
108
211
 
109
- # True if the logged-in role, or a role specified using the acting-as option, has the
212
+ # True if the logged-in role, or a role specified using the :acting_as option, has the
110
213
  # specified +privilege+ on this resource.
214
+ #
215
+ # @example
216
+ # api.current_role # => 'conjur:cat:mouse'
217
+ # resource.permitted_roles 'execute' # => ['conjur:user:admin', 'conjur:cat:mouse']
218
+ # resource.permitted_roles 'update', # => ['conjur:user:admin', 'conjur:cat:gino']
219
+ #
220
+ # resource.permitted? 'update' # => false, `mouse` can't update this resource
221
+ # resource.permitted? 'execute' # => true, `mouse` can execute it.
222
+ # resource.permitted? 'update',acting_as: 'conjur:cat:gino' # => true, `gino` can update it.
223
+ # @param privilege [String] the privilege to check
224
+ # @param [Hash, nil] options for the request
225
+ # @option options [String,nil] :acting_as check whether the role given by this full role id is permitted
226
+ # instead of checking +api.current_role+.
227
+ # @return [Boolean]
111
228
  def permitted?(privilege, options = {})
229
+ # TODO this method should accept an optional role rather than putting it in the options hash.
112
230
  params = {
113
231
  check: true,
114
232
  privilege: query_escape(privilege)
@@ -121,15 +239,31 @@ module Conjur
121
239
  rescue RestClient::ResourceNotFound
122
240
  false
123
241
  end
124
-
125
- # Return a Conjur::Annotations instance to read and manipulate our annotations.
242
+
243
+ # Return an {Conjur::Annotations} object to manipulate and view annotations.
244
+ #
245
+ # @see Conjur::Annotations
246
+ # @example
247
+ # resource.annotations.count # => 0
248
+ # resource.annotations['foo'] = 'bar'
249
+ # resource.annotations.each do |k,v|
250
+ # puts "#{k}=#{v}"
251
+ # end
252
+ # # output is
253
+ # # foo=bar
254
+ #
255
+ #
256
+ # @return [Conjur::Annotations]
126
257
  def annotations
127
258
  @annotations ||= Conjur::Annotations.new(self)
128
259
  end
129
260
  alias tags annotations
130
261
 
131
- # Returns all resources (optionally qualified by kind)
132
- # visible to the user with given credentials.
262
+ # @api private
263
+ # This is documented by Conjur::API#resources.
264
+ # Returns all resources (optionally qualified by kind) visible to the user with given credentials.
265
+ #
266
+ #
133
267
  # Options are:
134
268
  # - host - authz url,
135
269
  # - credentials,
@@ -154,7 +288,15 @@ module Conjur
154
288
  end
155
289
 
156
290
  protected
157
-
291
+
292
+
293
+ # Given an Object, return something that responds to each. In particular,
294
+ # if `item.respond_to? :each` is true, we return the item, otherwise we put it in
295
+ # an array.
296
+ #
297
+ # @param item [Object] the value we want to each over.
298
+ # @return [#each] `item` if item is already eachable, otherwise `[item]`.
299
+ # @api private
158
300
  def eachable(item)
159
301
  item.respond_to?(:each) ? item : [ item ]
160
302
  end
data/lib/conjur/role.rb CHANGED
@@ -98,7 +98,7 @@ module Conjur
98
98
  false
99
99
  end
100
100
 
101
- def members
101
+ def members options = {}
102
102
  JSON.parse(self["?members"].get(options)).collect do |json|
103
103
  RoleGrant.parse_from_json(json, self.options)
104
104
  end
@@ -23,6 +23,9 @@ require 'active_support/dependencies/autoload'
23
23
  require 'active_support/core_ext'
24
24
 
25
25
  module Conjur
26
+ # @api private
27
+ # This module provides a number of "standard" `REST` helpers,
28
+ # to wit, create, list and show.
26
29
  module StandardMethods
27
30
 
28
31
  protected
@@ -43,10 +46,12 @@ module Conjur
43
46
 
44
47
  def standard_list(host, type, options)
45
48
  JSON.parse(RestClient::Resource.new(host, credentials)[type.to_s.pluralize].get(options)).collect do |item|
49
+ # Note that we don't want to fully_escape the ids below -- methods like #layer, #host, etc don't expect
50
+ # ids to be escaped, and will escape them again!.
46
51
  if item.is_a? String # lists w/o details are just list of ids
47
- send(type, fully_escape(item))
52
+ send(type,item)
48
53
  else # list w/ details consists of hashes
49
- send(type, fully_escape(item['id'])).tap { |obj| obj.attributes=item }
54
+ send(type, item['id']).tap { |obj| obj.attributes=item }
50
55
  end
51
56
  end
52
57
  end
@@ -19,12 +19,137 @@
19
19
  # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
20
  #
21
21
  module Conjur
22
+
23
+ # Secrets stored in Conjur are represented by {http://developer.conjur.net/reference/services/directory/variable Variables}.
24
+ # The code responsible for the actual encryption of variables is open source as part of the
25
+ # {https://github.com/conjurinc/slosilo Slosilo} library.
26
+ #
27
+ # You should not generally create instances of this class directly. Instead, you can get them from
28
+ # {Conjur::API} methods such as {Conjur::API#create_variable} and {Conjur::API#variable}.
29
+ #
30
+ # Conjur variables store metadata (mime-type and secret kind) with each secret.
31
+ #
32
+ # Variables are *versioned*. Storing secrets in multiple places is a bad security practice, but
33
+ # overwriting a secret accidentally can create a major problem for development and ops teams. Conjur
34
+ # discourages bad security practices while avoiding ops disasters by storing all previous versions of
35
+ # a secret.
36
+ #
37
+ # ### Important
38
+ # A common pitfall when trying to access older versions of a variable is to assume that `0` is the oldest
39
+ # version. In fact, `0` references the *latest* version, while *1* is the oldest.
40
+ #
41
+ #
42
+ # ### Permissions
43
+ #
44
+ # * To *read* the value of a `variable`, you must have permission to `'execute'` the variable.
45
+ # * To *add* a value to a `variable`, you must have permission to `'update'` the variable.
46
+ # * To *show* metadata associated with a variable, but *not* the value of the secret, you must have `'read'`
47
+ # permission on the variable.
48
+ #
49
+ # When you create a secret, the creator role is granted all three of the above permissions.
50
+ #
51
+ # @example Get a variable and access its metadata and the latest value
52
+ # variable = api.variable 'example'
53
+ # puts variable.kind # "example-secret"
54
+ # puts variable.mime_type # "text/plain"
55
+ # puts variable.value # "supahsecret"
56
+ #
57
+ # @example Variable permissions
58
+ # # use our 'admin' api to create a variable 'permissions-example
59
+ # admin_var = admin_api.create_variable 'text/plain', 'example', 'permissions-example'
60
+ #
61
+ # # get a 'view' to it from user 'alice'
62
+ # alice_var = alice_api.variable admin_var.id
63
+ #
64
+ # # Initilally, all of the following raise a RestClient::Forbidden exception
65
+ # alice_var.attributes
66
+ # alice_var.value
67
+ # alice_var.add_value 'hi'
68
+ #
69
+ # # Allow alice to see the variables attributes
70
+ # admin_var.permit 'read', alice
71
+ # alice_var.attributes # OK
72
+ #
73
+ # # Allow alice to update the variable
74
+ # admin_var.permit 'update', alice
75
+ # alice_var.add_value 'hello'
76
+ #
77
+ # # Notice that alice still can't see the variable's value:
78
+ # alice_var.value # raises RestClient::Forbidden
79
+ #
80
+ # # Finally, we let alice execute the variable
81
+ # admin_var.permit 'execute', alice
82
+ # alice_var.value # 'hello'
83
+ #
84
+ # @example Variables are versioned
85
+ # var = api.variable 'version-example'
86
+ # # Unless you set a variables value when you create it, the variable starts out without a value and version_count
87
+ # # is 0.
88
+ # var.version_count # => 0
89
+ # var.value # raises RestClient::ResourceNotFound (404)
90
+ #
91
+ # # Add a value
92
+ # var.add_value 'value 1'
93
+ # var.version_count # => 1
94
+ # var.value # => 'value 1'
95
+ #
96
+ # # Add another value
97
+ # var.add_value 'value 2'
98
+ # var.version_count # => 2
99
+ #
100
+ # # 'value' with no argument returns the most recent value
101
+ # var.value # => 'value 2'
102
+ #
103
+ # # We can access older versions by their 1 based index:
104
+ # var.value 1 # => 'value 1'
105
+ # var.value 2 # => 'value 2'
106
+ # # Notice that version 0 of a variable is always the most recent:
107
+ # var.value 0 # => 'value 2'
108
+ #
22
109
  class Variable < RestClient::Resource
23
110
  include ActsAsAsset
24
-
25
- def kind; attributes['kind']; end
26
- def mime_type; attributes['mime_type']; end
27
-
111
+
112
+ # The kind of secret represented by this variable, for example, `'postgres-url'` or
113
+ # `'aws-secret-access-key'`.
114
+ #
115
+ # You must have the **`'read'`** permission on a variable to call this method.
116
+ #
117
+ # This attribute is only for human consumption, and does not take part in the Conjur permissions
118
+ # model.
119
+ #
120
+ # @note this is **not** the same as the `kind` part of a qualified Conjur id.
121
+ # @return [String] a string representing the kind of secret.
122
+ def kind
123
+ attributes['kind']
124
+ end
125
+
126
+ # The MIME Type of the variable's value.
127
+ #
128
+ # You must have the **`'read'`** permission on a variable to call this method.
129
+ #
130
+ # This attribute is used by the Conjur services to set a response `Content-Type` header when
131
+ # returning the value of a variable. Conjur applies the same MIME Type to all versions of a variable,
132
+ # so if you plan on accessing the variable in a way that depends on a correct `Content-Type` header
133
+ # you should make sure to store appropriate data for the mime type in all versions.
134
+ #
135
+ # @return [String] a MIME type, such as `'text/plain'` or `'application/octet-stream'`.
136
+ def mime_type
137
+ attributes['mime_type']
138
+ end
139
+
140
+ # Add a new value to the variable.
141
+ #
142
+ # You must have the **`'update'`** permission on a variable to call this method.
143
+ #
144
+ # @example Add a value to a variable
145
+ # var = api.variable 'my-secret'
146
+ # puts var.version_count # 1
147
+ # puts var.value # 'supersecret'
148
+ # var.add_value "new_secret"
149
+ # puts var.version_count # 2
150
+ # puts var.value # 'new_secret'
151
+ # @param [String] value the new value to add
152
+ # @return [void]
28
153
  def add_value value
29
154
  log do |logger|
30
155
  logger << "Adding a value to variable #{id}"
@@ -33,11 +158,48 @@ module Conjur
33
158
  self['values'].post value: value
34
159
  end
35
160
  end
36
-
161
+
162
+ # Return the number of versions of the variable.
163
+ #
164
+ # You must have the **`'read'`** permission on a variable to call this method.
165
+ #
166
+ # @example
167
+ # var.version_count # => 4
168
+ # var.add_value "something new"
169
+ # var.version_count # => 5
170
+ #
171
+ # @return [Integer] the number of versions
37
172
  def version_count
38
173
  self.attributes['version_count']
39
174
  end
40
-
175
+
176
+ # Return the version of a variable.
177
+ #
178
+ # You must have the **`'execute'`** permission on a variable to call this method.
179
+ #
180
+ # When no argument is given, the most recent version is returned.
181
+ #
182
+ # When a `version` argument is given, the method returns a version according to the following rules:
183
+ # * If `version` is 0, the *most recent* version is returned.
184
+ # * If `version` is less than 0 or greater than {#version_count}, a `RestClient::ResourceNotFound` exception
185
+ # will be raised.
186
+ # * If {#version_count} is 0, a `RestClient::ResourceNotFound` exception will be raised.
187
+ # * If `version` is >= 1 and `version` <= {#version_count}, the version at the **1 based** index given by `version`
188
+ # will be returned.
189
+ #
190
+ # @example Fetch all versions of a variable
191
+ # versions = (1..var.version_count).map do |version|
192
+ # var.value version
193
+ # end
194
+ #
195
+ # @example Get the current version of a variable
196
+ # # All of these return the same thing:
197
+ # var.value
198
+ # var.value 0
199
+ # var.value var.version_count
200
+ #
201
+ # @param [Integer] version the **1 based** version.
202
+ # @return [String] the value of the variable
41
203
  def value(version = nil)
42
204
  url = 'value'
43
205
  url << "?version=#{version}" if version
@@ -19,6 +19,6 @@
19
19
 
20
20
  module Conjur
21
21
  class API
22
- VERSION = "4.13.0"
22
+ VERSION = "4.14.0"
23
23
  end
24
24
  end
data/lib/conjur-api.rb ADDED
@@ -0,0 +1,2 @@
1
+ # Just a stub so that require 'conjur-api' works
2
+ require 'conjur/api'
@@ -1,7 +1,10 @@
1
1
  require 'spec_helper'
2
2
  require 'cas_rest_client'
3
+ require 'helpers/request_helpers'
3
4
 
4
5
  describe Conjur::API do
6
+ include RequestHelpers
7
+
5
8
  let(:host) { 'http://authn.example.com' }
6
9
  let(:user) { 'kmitnick' }
7
10
  let(:password) { 'sikret' }
@@ -12,7 +15,7 @@ describe Conjur::API do
12
15
 
13
16
  describe "::login" do
14
17
  it "gets /users/login" do
15
- expect(RestClient::Request).to receive(:execute).with(
18
+ expect_request(
16
19
  method: :get, url: "http://authn.example.com/users/login",
17
20
  user: user,
18
21
  password: password,
@@ -41,7 +44,7 @@ describe Conjur::API do
41
44
 
42
45
  describe "::authenticate" do
43
46
  it "posts the password and dejsons the result" do
44
- expect(RestClient::Request).to receive(:execute).with(
47
+ expect_request(
45
48
  method: :post, url: "http://authn.example.com/users/#{user}/authenticate",
46
49
  payload: password, headers: { content_type: 'text/plain' }
47
50
  ).and_return '{ "response": "foo"}'
@@ -51,7 +54,7 @@ describe Conjur::API do
51
54
 
52
55
  describe "::update_password" do
53
56
  it "logs in and puts the new password" do
54
- expect(RestClient::Request).to receive(:execute).with(
57
+ expect_request(
55
58
  method: :put,
56
59
  url: "http://authn.example.com/users/password",
57
60
  user: user,
@@ -28,7 +28,7 @@ describe Conjur::API, api: :dummy do
28
28
 
29
29
  describe '#find_groups' do
30
30
  it "searches the group by GID" do
31
- expect(RestClient::Request).to receive(:execute).with(
31
+ expect_request(
32
32
  method: :get,
33
33
  url: "#{core_host}/groups/search?gidnumber=12345",
34
34
  headers: credentials[:headers]
@@ -32,7 +32,7 @@ describe Conjur::API, api: :dummy do
32
32
 
33
33
  describe "#public_keys" do
34
34
  it "GETs /:username" do
35
- expect(RestClient::Request).to receive(:execute).with(
35
+ expect_request(
36
36
  url: pubkeys_url_for("bob"),
37
37
  method: :get,
38
38
  headers: credentials[:headers],
@@ -43,7 +43,7 @@ describe Conjur::API, api: :dummy do
43
43
 
44
44
  describe "#add_public_key" do
45
45
  it "POSTs /:username with the data" do
46
- expect(RestClient::Request).to receive(:execute).with(
46
+ expect_request(
47
47
  url: pubkeys_url_for("bob"),
48
48
  method: :post,
49
49
  headers: credentials[:headers],
@@ -55,7 +55,7 @@ describe Conjur::API, api: :dummy do
55
55
 
56
56
  describe "#delete_public_key" do
57
57
  it "DELETEs /:username/:keyname" do
58
- expect(RestClient::Request).to receive(:execute).with(
58
+ expect_request(
59
59
  url: pubkeys_url_for("bob", "bob-key"),
60
60
  method: :delete,
61
61
  headers: credentials[:headers]
@@ -63,4 +63,4 @@ describe Conjur::API, api: :dummy do
63
63
  api.delete_public_key("bob", "bob-key")
64
64
  end
65
65
  end
66
- end
66
+ end
@@ -1,6 +1,8 @@
1
1
  require 'spec_helper'
2
+ require 'helpers/request_helpers'
2
3
 
3
4
  describe Conjur::API, api: :dummy do
5
+ include RequestHelpers
4
6
  subject { api }
5
7
 
6
8
  describe 'role_graph' do
@@ -34,8 +36,8 @@ describe Conjur::API, api: :dummy do
34
36
  end
35
37
 
36
38
  def expect_request_with_params params={}
37
- expect(RestClient::Request).to receive(:execute)
38
- .with(headers: credentials[:headers], method: :get, url: role_graph_url_for(roles, options, current_role))
39
+ expect_request(headers: credentials[:headers], method: :get,
40
+ url: role_graph_url_for(roles, options, current_role))
39
41
  .and_return(response)
40
42
  end
41
43
 
@@ -11,7 +11,7 @@ describe Conjur::API, api: :dummy do
11
11
  describe 'user#update' do
12
12
  let(:userid) { "alice@wonderland" }
13
13
  it "PUTs to /users/:id?uidnumber=:uidnumber" do
14
- expect(RestClient::Request).to receive(:execute).with(
14
+ expect_request(
15
15
  method: :put,
16
16
  url: "#{core_host}/users/#{api.fully_escape(userid)}",
17
17
  headers: credentials[:headers],
@@ -28,7 +28,7 @@ describe Conjur::API, api: :dummy do
28
28
  let(:search_result) { ["someuser"].to_json }
29
29
 
30
30
  it "GETs /users/search with appropriate options, and returns parsed JSON response" do
31
- expect(RestClient::Request).to receive(:execute).with(
31
+ expect_request(
32
32
  method: :get,
33
33
  url: "#{core_host}/users/search?uidnumber=12345",
34
34
  headers: credentials[:headers]
@@ -25,7 +25,7 @@ describe Conjur::API, api: :dummy do
25
25
  shared_context "Stubbed API" do
26
26
  let (:expected_url) { "#{core_host}/variables/values?vars=#{varlist.map {|v| api.fully_escape(v) }.join(",")}" }
27
27
  before {
28
- expect(RestClient::Request).to receive(:execute).with(
28
+ expect_request(
29
29
  method: :get,
30
30
  url: expected_url,
31
31
  headers: credentials[:headers]
@@ -0,0 +1,34 @@
1
+ require 'rspec/expectations'
2
+
3
+ RSpec::Matchers.define :raise_one_of do |*exn_classes|
4
+ supports_block_expectations
5
+
6
+ match do |block|
7
+ expect(&block).to raise_error do |error|
8
+ @actual_error = error
9
+ expect(exn_classes).to include error.class
10
+ end
11
+ end
12
+
13
+ failure_message do
14
+ "expected #{expected_error}#{given_error}"
15
+ end
16
+
17
+ define_method :expected_error do
18
+ "one of " + exn_classes.join(', ')
19
+ end
20
+
21
+ def given_error
22
+ return " but nothing was raised" unless @actual_error
23
+ backtrace = format_backtrace(@actual_error.backtrace)
24
+ [
25
+ ", got #{@actual_error.inspect} with backtrace:",
26
+ *backtrace
27
+ ].join("\n # ")
28
+ end
29
+
30
+ def format_backtrace backtrace
31
+ formatter = RSpec::Matchers.configuration.backtrace_formatter
32
+ formatter.format_backtrace(backtrace)
33
+ end
34
+ end
@@ -0,0 +1,10 @@
1
+ # Helpers for REST client tests
2
+ module RequestHelpers
3
+ def expect_request details, &block
4
+ expect(RestClient::Request).to receive(:execute).with(hash_including(details), &block)
5
+ end
6
+
7
+ def allow_request details, &block
8
+ allow(RestClient::Request).to receive(:execute).with(hash_including(details), &block)
9
+ end
10
+ end