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,8 +21,42 @@
21
21
  module Conjur
22
22
 
23
23
  class << self
24
- # Sets the Configuration for the current thread, yields the block, then resets the thread-local variable.
25
- def with_configuration(config, &block)
24
+ # Saves the current thread local {Conjur::Configuration},
25
+ # sets the thread local {Conjur::Configuration} to `config`, yields to the block, and ensures that
26
+ # the original thread local configuration is restored.
27
+ #
28
+ # Because Conjur configuration is accessed from the 'global' {Conjur.configuration} method by all Conjur
29
+ # API methods, this method provides the ability to set a thread local value for use within the current,
30
+ # or within a block in a single threaded application.
31
+ #
32
+ # Note that the {Conjur.configuration=} method sets the *global* {Conjur::Configuration}, not the thread-local
33
+ # value.
34
+ #
35
+ # @example Override Configuration in a Thread
36
+ # # in this rather contrived example, we'll override the {Conjur::Configuration#appliance_url} parameter
37
+ # # used by calls within a thread.
38
+ #
39
+ # # Set up the configuration in the main thread
40
+ # Conjur.configure do |c|
41
+ # # ...
42
+ # c.appliance_url = 'https://conjur.main-url.com/api'
43
+ # end
44
+ #
45
+ # # Start a new thread that will perform requests to another server. In practice, you might
46
+ # # have a web server that uses a Conjur endpoint specified by a request header.
47
+ # Thread.new do
48
+ # Conjur.with_configuration Conjur.config.clone(appliance_url: 'https://conjur.local-url.com/api') do
49
+ # sleep 2
50
+ # puts "Thread local url is #{Conjur.config.appliance_url}"
51
+ # end
52
+ # end
53
+ # puts "Global url is #{Conjur.config.appliance_url}"
54
+ # # Outputs:
55
+ # Global url is https://conjur.main-url.com/api
56
+ # Thread local url is https://conjur.local-url.com/api
57
+ #
58
+ # @return [void]
59
+ def with_configuration(config)
26
60
  oldvalue = Thread.current[:conjur_configuration]
27
61
  Thread.current[:conjur_configuration] = config
28
62
  yield
@@ -31,26 +65,123 @@ module Conjur
31
65
  end
32
66
 
33
67
  # Gets the current thread-local or global configuration.
68
+ #
69
+ # The thread-local Conjur configuration can only be set using the {Conjur.with_configuration}
70
+ # method. This method will try to return that value first, then the global configuration as
71
+ # set with {Conjur.configuration=} (which is lazily initialized if not set).
72
+ #
73
+ # @return [Conjur::Configuration] the thread-local or global Conjur configuration.
34
74
  def configuration
35
75
  Thread.current[:conjur_configuration] || (@config ||= Configuration.new)
36
76
  end
37
77
 
38
78
  # Sets the global configuration.
79
+ #
80
+ # This method *has no effect* on the thread local configuration. Use {Conjur.with_configuration} instead if
81
+ # that's what you want.
82
+ #
83
+ # @param [Conjur::Configuration] config the new configuration
84
+ # @return [Conjur::Configuration] the new value of the configuration
39
85
  def configuration=(config)
40
86
  @config = config
41
87
  end
88
+
89
+ alias config configuration
90
+ alias config= configuration=
91
+
92
+ # Configure Conjur with a block.
93
+ #
94
+ # @example
95
+ # Conjur.configure do |c|
96
+ # c.account = 'some-account'
97
+ # c.appliance_url = 'https://conjur.companyname.com/api'
98
+ # end
99
+ #
100
+ # @yieldparam [Conjur::Configuration] c the configuration instance to modify.
101
+ def configure
102
+ yield configuration
103
+ end
42
104
  end
43
-
105
+
106
+ # Stores a configuration for the Conjur API client. This class provides *global* and *thread local* storage
107
+ # for common options used by the Conjur API. Most importantly, it specifies the
108
+ #
109
+ # * REST endpoints, derived from the {Conjur::Configuration#appliance_url} and {Conjur::Configuration#account} options
110
+ # * The certificate used for secure connections to the Conjur appliance ({Conjur::Configuration#cert_file})
111
+ #
112
+ # ### Environment Variables
113
+ #
114
+ # Option values used by Conjur can be given by environment variables, using a standard naming scheme. Specifically,
115
+ # an environment variable named `CONJUR_ACCOUNT` will be used to provide a default value for the {Conjur::Configuration#account}
116
+ # option.
117
+ #
118
+ #
119
+ # ### Required Options
120
+ #
121
+ # The {Conjur::Configuration#account} and {Conjur::Configuration#appliance_url} are always required. Except in
122
+ # special cases, the {Conjur::Configuration#cert_file} is also required, but you may omit it if your Conjur root
123
+ # certificate is in the OpenSSl default certificate store.
124
+ #
125
+ # ### Thread Local Configuration
126
+ #
127
+ # While using a globally available configuration is convenient for most applications, sometimes you will need to
128
+ # use different configurations in different threads. This is supported by returning a thread local version from {Conjur.configuration}
129
+ # if one has been set by {Conjur.with_configuration}.
130
+ #
131
+ # @see Conjur.configuration
132
+ # @see Conjur.configure
133
+ # @see Conjur.with_configuration
134
+ #
135
+ # @example Basic Configuration
136
+ # Conjur.configure do |c|
137
+ # c.account = 'the-account'
138
+ # c.cert_file = find_conjur_cert_file
139
+ # end
140
+ #
141
+ # @example Setting the appliance_url from an environment variable
142
+ # ENV['CONJUR_APPLIANCE_URL'] = 'https://some-host.com/api'
143
+ # Conjur::Configuration.new.appliance_url # => 'https://some-host.com/api'
144
+ #
145
+ # @example Using thread local configuration in a web application request handler
146
+ # # Assume that we're in a request handler thread in a multithreaded web server.
147
+ #
148
+ # requested_appliance_url = request.header 'X-Conjur-Appliance-Url'
149
+ #
150
+ # with_configuration Conjur.config.clone(appliance_url: requested_appliance_url) do
151
+ # # `api` is an instance attribute. Note that we can use an api that was created
152
+ # # before we modified the thread local configuration.
153
+ #
154
+ #
155
+ # # 404 if the user doesn't exist
156
+ #
157
+ # user = api.user request.header('X-Conjur-Login')
158
+ # raise HttpError, 404, "User #{user.login} does not exist" unless user.exists?
159
+ # # ... finish the request
160
+ # end
161
+ #
162
+ #
44
163
  class Configuration
45
- # All explicit values.
164
+ # @api private
46
165
  attr_reader :explicit
47
-
48
- # All explicit and cached values.
166
+
167
+ # @api private
49
168
  attr_reader :supplied
50
-
51
- def initialize explicit = {}
52
- @explicit = explicit.dup
53
- @supplied = explicit.dup
169
+
170
+
171
+ # Create a new {Conjur::Configuration}, setting initial values from
172
+ # `options`.
173
+ #
174
+ # @note `options` must use symbols for keys.
175
+ #
176
+ # @example
177
+ # Conjur.config = Conjur::Configuration.new account: 'companyname'
178
+ # Conjur.config.account # => 'companyname'
179
+ #
180
+ # @param [Hash] options hash of options to set on the new instance.
181
+ #
182
+ def initialize options = {}
183
+ @explicit = options.dup
184
+ @supplied = options.dup
54
185
  end
55
186
 
56
187
  class << self
@@ -112,44 +243,129 @@ module Conjur
112
243
  end
113
244
  end
114
245
 
115
- # Copies the current configuration, except a set of overridden options.
116
- def clone override_options
246
+ # Return a copy of this {Conjur::Configuration} instance, optionally
247
+ # updating the copy with options from the `override_options` hash.
248
+ #
249
+ # @example
250
+ # original = Conjur.configuration
251
+ # original.account # => 'conjur'
252
+ # copy = original.clone account: 'some-other-account'
253
+ # copy.account # => 'some-other-account'
254
+ # original.account # => 'conjur'
255
+ #
256
+ # @param [Hash] override_options options to set on the new instance
257
+ # @return [Conjur::Configuration] a copy of this configuration
258
+ def clone override_options = {}
117
259
  self.class.new self.explicit.dup.merge(override_options)
118
260
  end
119
261
 
262
+ # Manually set an option. Note that setting an option not present in
263
+ # {Conjur::Configuration.accepted_options} is a no op.
264
+ # @api private
265
+ # @param [Symbol, String] key the name of the option to set
266
+ # @param [Object] value the option value.
120
267
  def set(key, value)
121
268
  if self.class.accepted_options.include?(key.to_sym)
122
269
  explicit[key.to_sym] = value
123
270
  supplied[key.to_sym] = value
124
271
  end
125
272
  end
126
-
273
+
274
+ # @!attribute authn_url
275
+ # The url for the {http://developer.conjur.net/reference/services/authentication Conjur authentication service}.
276
+ #
277
+ # @note You should not generally set this value. Instead, Conjur will derive it from the
278
+ # {Conjur::Configuration#account} and {Conjur::Configuration#appliance_url}
279
+ # properties.
280
+ #
281
+ # @return [String] the authentication service url
127
282
  add_option :authn_url do
128
283
  account_service_url 'authn', 0
129
284
  end
130
-
285
+
286
+ # @!attribute authz_url
287
+ # The url for the {http://developer.conjur.net/reference/services/authorization Conjur authorization service}.
288
+ #
289
+ # @note You should not generally set this value. Instead, Conjur will derive it from the
290
+ # {Conjur::Configuration#account} and {Conjur::Configuration#appliance_url}
291
+ # properties.
292
+ #
293
+ # @return [String] the authorization service url
131
294
  add_option :authz_url do
132
295
  global_service_url 'authz', 100
133
296
  end
134
297
 
298
+ # @!attribute core_url
299
+ # The url for the {http://developer.conjur.net/reference/services/directory Conjur core/directory service}.
300
+ #
301
+ # @note You should not generally set this value. Instead, Conjur will derive it from the
302
+ # {Conjur::Configuration#account} and {Conjur::Configuration#appliance_url}
303
+ # properties.
304
+ #
305
+ # @return [String] the core/directory service url
135
306
  add_option :core_url do
136
307
  default_service_url 'core', 200
137
- end
138
-
308
+ end
309
+
310
+ # @!attribute audit_url
311
+ # The url for the {http://developer.conjur.net/reference/services/audit Conjur audit service}.
312
+ #
313
+ # @note You should not generally set this value. Instead, Conjur will derive it from the
314
+ # {Conjur::Configuration#account} and {Conjur::Configuration#appliance_url}
315
+ # properties.
316
+ #
317
+ # @return [String] the audit service url
139
318
  add_option :audit_url do
140
319
  global_service_url 'audit', 300
141
320
  end
142
-
321
+
322
+ # @!attribute appliance_url
323
+ # The url for your Conjur appliance.
324
+ #
325
+ # If your appliance's hostname is `'conjur.companyname.com'`, then your `appliance_url` will
326
+ # be `'https://conjur.companyname.com/api'`.
327
+ #
328
+ # @note If you are using an appliance (if you're not sure, you probably are), this option is *required*.
329
+ #
330
+ # @return [String] the appliance URL
143
331
  add_option :appliance_url
144
-
332
+
333
+ # NOTE DO NOT DOCUMENT THIS AS AN ATTRIBUTE, IT IS PRIVATE AND YARD DOESN'T SUPPORT @api private ON ATTRIBUTES.
334
+ #
335
+ # The port used to derive ports for conjur services running locally. You will only use this if you are
336
+ # running the Conjur services locally, in which case you are probably a Conjur developer, and should ask
337
+ # someone in chat ;-)
338
+ #
145
339
  add_option :service_base_port, default: 5000
146
340
 
341
+ # @!attribute account
342
+ # The organizational account used by Conjur.
343
+ #
344
+ # On Conjur appliances, this option will be set once when the appliance is first configured. You can get the
345
+ # value for the acccount option from your conjur administrator, or if you have installed
346
+ # the {http://developer.conjur.net/client_setup/cli.html Conjur command line tools} by running
347
+ # {http://developer.conjur.net/reference/services/authentication/whoami.html conjur authn whoami},
348
+ # or examining your {http://developer.conjur.net/client_setup/cli.html#Configure .conjurrc file}.
349
+ #
350
+ # @note this option is **required**, and attempting to make any api calls prior to setting it (either
351
+ # explicitly or with the `"CONJUR_ACCOUNT"` environment variable) will raise an exception.
352
+ #
353
+ # @return [String]
147
354
  add_option :account, required: true
148
-
355
+
356
+
357
+ # @!attribute env
358
+ #
359
+ # The type of environment your program is running in (e.g., `development`, `production`, `test`).
360
+ #
361
+ # @deprecated
362
+ #
363
+ # @return [String] the environment name
149
364
  add_option :env do
150
365
  ENV['CONJUR_ENV'] || ENV['RAILS_ENV'] || ENV['RACK_ENV'] || "production"
151
366
  end
152
-
367
+
368
+ # DEPRECATED SaaS option, do not doc comment!
153
369
  add_option :stack do
154
370
  case env
155
371
  when "production"
@@ -159,6 +375,17 @@ module Conjur
159
375
  end
160
376
  end
161
377
 
378
+ # @!attribute cert_file
379
+ #
380
+ # Path to the certificate file to use when making secure connections to your Conjur appliance.
381
+ #
382
+ # This should be the path to the root Conjur SSL certificate in PEM format. You will normally get the
383
+ # certificate file using the {http://developer.conjur.net/reference/tools/utilities/init.html conjur init} command.
384
+ # This option is not required if the certificate or its root is in the OpenSSL default cert store.
385
+ # If your program throws an error indicating that SSL verification has failed, you probably need
386
+ # to set or fix this option.
387
+ #
388
+ # @return [String, nil] path to the certificate file, or nil if you aren't using one.
162
389
  add_option :cert_file
163
390
 
164
391
  private
data/lib/conjur/exists.rb CHANGED
@@ -19,7 +19,33 @@
19
19
  # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
20
  #
21
21
  module Conjur
22
+ # Provides an `exists?` method for things that may or may not exist.
23
+ #
24
+ #
25
+ # Most conjur assets returned by `api.asset_name` methods (e.g., {Conjur::API#group}, {Conjur::API#user})
26
+ # may or may not exist. The {Conjur::Exists#exists?} method lets you determine whether or not such assets
27
+ # do in fact exist.
22
28
  module Exists
29
+
30
+ # Check whether this asset exists by performing a HEAD request to its URL.
31
+ #
32
+ # This method will return false if the asset doesn't exist.
33
+ #
34
+ # @example
35
+ # does_not_exist = api.user 'does-not-exist' # This returns without error.
36
+ #
37
+ # # this is wrong!
38
+ # owner = does_not_exist.ownerid # raises RestClient::ResourceNotFound
39
+ #
40
+ # # this is right!
41
+ # owner = if does_not_exist.exists?
42
+ # does_not_exist.ownerid
43
+ # else
44
+ # nil # or some sensible default
45
+ # end
46
+ #
47
+ # @param [Hash] options included for compatibility: **don't use this argument**!
48
+ # @return [Boolean] does it exist?
23
49
  def exists?(options = {})
24
50
  begin
25
51
  self.head(options)
data/lib/conjur/group.rb CHANGED
@@ -18,21 +18,67 @@
18
18
  # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19
19
  #
20
20
  module Conjur
21
+
22
+ # A Conjur {http://developer.conjur.net/reference/services/directory/group Group} represents a collection of
23
+ # Conjur {http://developer.conjur.net/reference/services/directory/user Users}.
24
+ # This class represents Conjur group assets and operations on them.
25
+ #
26
+ # You should not create instances of this class directly. Instead, you can get them from
27
+ # API methods like {Conjur::API#group} and {Conjur::API#groups}.
28
+ #
21
29
  class Group < RestClient::Resource
22
30
  include ActsAsAsset
23
31
  include ActsAsRole
24
-
32
+
33
+ # Add a user to the group or change whether an existing member can manage other members.
34
+ #
35
+ # @example
36
+ # # create an empty group
37
+ # group = api.create_group 'hoommans'
38
+ # # put a user in the group, with the ability to manage members
39
+ # group.add_member 'conjur:user:bob', admin_option: True
40
+ # # Hmm, bob is getting a little suspicious, better lower his privileges.
41
+ # group.add_member 'conjur:user:bob', admin_option: False
42
+ #
43
+ # # Notice that this method is idempotent:
44
+ # group.add_member 'alice'
45
+ # group.add_member 'alice' # Does nothing, alice is already a member
46
+ #
47
+ #
48
+ # @param [String, Conjur::User, Conjur::Role] member the member to add. If a String is given, it must
49
+ # be a *fully qualified* Conjur id.
25
50
  # @param [Hash] options
26
- # * *admin_option* enables the +member+ to manage members of this group
51
+ # @option options [Boolean] :admin_option (False) determines whether the member is able to manage members
52
+ # of this group.
53
+ # @return [void]
27
54
  def add_member(member, options = {})
28
55
  role.grant_to member, options
29
56
  end
30
-
57
+
58
+ # Remove a member from this group.
59
+ #
60
+ # ### Notes
61
+ # * Unlike {#add_member}, this method is *not* idempotent.
62
+ # This means that calling it twice with the same user will raise a `RestClient::ResourceNotFound`
63
+ # exception.
64
+ # * The member may be represented as a *qualified* conjur id or a {Conjur::User} instance. Although
65
+ # it will accept anything that responds to `#roleid`, the behavior when adding or removing a non-user
66
+ # role is **undefined**.
67
+ #
68
+ # @example
69
+ # group = api.group 'admins'
70
+ # group.add_member 'bob'
71
+ # group.remove_member 'bob' # OK, bob is a member
72
+ # group.remove_member 'bob' # raises RestClient::ResourceNotFound
73
+ #
74
+ # @param [String, Conjur::User,Conjur::Role] member
75
+ # @return [void]
76
+ # @raise [RestClient::ResourceNotFound] when you try to remove a user who is not a member of the group.
31
77
  def remove_member(member)
32
78
  role.revoke_from member
33
79
  end
34
80
 
35
- # Update group properties
81
+ # Update group properties. Currently the only supported property is `:gidnumber`.
36
82
  #
37
83
  # @param [Hash] props new property values
38
84
  # @option props [Integer] :gidnumber new GID number
@@ -19,27 +19,87 @@
19
19
  # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
20
  #
21
21
  module Conjur
22
+ # Many Conjur assets have key-value attributes. Although these should generally be accessed via
23
+ # methods on specific asset classes (for example, {Conjur::Resource#owner}), the are available as
24
+ # a `Hash` on all types supporting attributes.
22
25
  module HasAttributes
26
+ # Returns this objects {#attributes}. This is primarily to support
27
+ # simple JSON serialization of Conjur assets.
28
+ #
29
+ # @param options [Hash,nil] unused, kept for compatibility reasons
30
+ # @see #attributes
23
31
  def to_json(options = {})
24
32
  attributes
25
33
  end
26
34
 
27
- def attributes=(a); @attributes = a; end
35
+ # @api private
36
+ # Set the attributes for this Resource.
37
+ # @param [Hash] attributes new attributes for the object.
38
+ # @return [Hash] the new attributes
39
+ def attributes=(attributes); @attributes = attributes; end
40
+
41
+ # Get the attributes for this asset.
42
+ #
43
+ # Although the `Hash` returned by this method is mutable, you should treat as immutable unless you know
44
+ # exactly what you're doing. Each asset's attributes are constrained by a server side schema, which means
45
+ # that you will get an error if you violate the schema. and then try to save the asset.
46
+ #
47
+ #
48
+ # @note this method will use a cached copy of the objects attributes instead of fetching them
49
+ # with each call. To ensure that the attributes are fresh, you can use the {#refresh} method
50
+ #
51
+ # @return [Hash] the asset's attributes.
28
52
  def attributes
29
53
  return @attributes if @attributes
30
54
  fetch
31
55
  end
32
-
56
+
57
+
58
+ # Update this asset's attributes on the server.
59
+ #
60
+ #
61
+ # @note If the objects attributes haven't been fetched (for example, by calling {#attributes}),
62
+ # this method is a no-op.
63
+ #
64
+ # Although you can manipulate an assets attributes and then call {#save}, the attributes are constrained
65
+ # by a server side schema, and attempting to set an attribute that doesn't exist will result in
66
+ # a 422 Unprocessable Entity error.
67
+ #
68
+ # If you want to set arbitrary metadata on an asset, you might consider using the {Conjur::Resource#tags}
69
+ # method instead.
70
+ #
71
+ # @return [void]
33
72
  def save
34
- self.put(attributes.to_json)
73
+ if @attributes
74
+ self.put(attributes.to_json)
75
+ end
35
76
  end
36
77
 
37
- # Reload the attributes. This action can be used to guarantee a current view of the entity in the case
78
+ # Reload this asset's attributes. This method can be used to guarantee a current view of the entity in the case
38
79
  # that it has been modified by an update method or by an external party.
80
+ #
81
+ # @note any changes to {#attributes} without a call to #save will be overwritten by this method.
82
+ #
83
+ # @example
84
+ # res = api.resources.firs
85
+ # res.attributes # => { ... }
86
+ # res.attributes['hello'] = 'blah'
87
+ # res.refresh
88
+ # res.attributes['hello'] # => nil
89
+ #
90
+ #
91
+ # @return [Hash] the asset's attributes.
39
92
  def refresh
40
93
  fetch
41
94
  end
42
95
 
96
+ # Call a block that will perform actions that might change the asset's attributes.
97
+ # No matter what happens in the block, this method ensures that the cached attributes
98
+ # will be invalidated.
99
+ #
100
+ # @note this is mainly used internally, but included in the public api for completeness.
101
+ #
102
+ # @return [void]
43
103
  def invalidate(&block)
44
104
  yield
45
105
  ensure
@@ -47,7 +107,8 @@ module Conjur
47
107
  end
48
108
 
49
109
  protected
50
-
110
+ # @api private
111
+ # Fetch the attributes, overwriting any current ones.
51
112
  def fetch
52
113
  @attributes = JSON.parse(get.body)
53
114
  end