conjur-api 4.13.0 → 4.14.0

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