identikey 0.7.1 → 0.9.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7eb968c0e5dd54488d15939d79d81ebccbf7052c93e3da22ff77bad6fc623106
4
- data.tar.gz: d90c163b2fdebaf21e6386a721c8f17b4ec0248dd44394e234c646007842a5fd
3
+ metadata.gz: 7ca7bba53578f15faed142b15d7938712cc93733eb62dce480a139fe42f49a91
4
+ data.tar.gz: b90c1e4451d845d80c2de47d9bf4ad5e6d17318819b2201aa0afca6f0fcc9090
5
5
  SHA512:
6
- metadata.gz: 984d1e2f3b20b1d882568d70e338420062ba54b89b26b5f7c9495fd92ee2f5507db7d1da38c4d45662c32fc478c68ac88622d3b69a8c50bb6a617b01a9761d7b
7
- data.tar.gz: 1c8092afbd35a441141c9830422a26467fc1fd851cbb9eee3109e8e69edea710de97b1ab22041c66869f5a84ea2bc6a29d6e465142b0dba3727f2453cd0831a5
6
+ metadata.gz: b1f3749db40bb5656106906424383f94dd05887587bca7897ba68d2c936e8f5ee7641aba9be8730a19a03b37f80c3df5f4cad757308e37d07320e70e0d8e5f28
7
+ data.tar.gz: 3a4a6ef801813a63153a14c93f8365efd08f2d450fa562a86cc6dc4415a44ede0026168c5a1cc7097de8f3ce48bf8c355084adacbda0d3ab8be89c2ae9adbb16
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Identikey
2
2
 
3
- This library is a thin yet incomplete wrapper of the VASCO Identikey SOAP API.
3
+ This library is a thin yet featureful wrapper of the VASCO Identikey SOAP API.
4
4
 
5
5
  Vasco Identikey has been recently re-branded as OneSpan Authentication Server.
6
6
 
@@ -25,6 +25,49 @@ And then execute:
25
25
 
26
26
  $ bundle
27
27
 
28
+ ## Features
29
+
30
+ This client implements the Authentication, Administration and Provisioning
31
+ SOAP APIs.
32
+
33
+ ### Authentication
34
+
35
+ * `auth_user`: end user authentication with OTP / static password / back-end
36
+
37
+
38
+ ### Administration
39
+
40
+ * `logon` / `logoff`: log on or log off an administrative session.
41
+ You are advised to use a connection pool ([such as
42
+ mperham's](https://github.com/mperham/connection_pool)) to keep multiple
43
+ instances of administration sessions alive. This gem is used in production
44
+ with puma, and has been extensively tested so it is thread-safe.
45
+
46
+ * `alive?`: checks whether an administrative session is alive. You can use
47
+ `.logon` again when `.alive?` returns `false`.
48
+
49
+ * `admin_session_query`: returns active admin sessions
50
+
51
+ * `user_execute`: `view`, `create`, `update`, `delete`, `reset_password`,
52
+ `set_password`, and `unlock` user accounts.
53
+
54
+ * `user_query`: search for users
55
+
56
+ * `digipass_execute`: `view`, `assign`, `unassign` digipasses
57
+
58
+ * `digipass_query`: search for digipasses
59
+
60
+ * `digipassappl_execute`: `test_otp`, `set_pin` on applicable digipasses
61
+
62
+
63
+ ### Provisioning
64
+
65
+ * `provisioning_execute`: `mdl_register`, `dsapp_srp_register`. bonus:
66
+ generation of CRONTO images for online activation, for use with the push
67
+ notification gateways. You can use [this gem](https://github.com/ifad/cronto)
68
+ to generate the PNG to serve to your users.
69
+
70
+
28
71
  ## Configuration
29
72
 
30
73
  By default the client expects WSDL files in the current working directory,
@@ -53,7 +96,14 @@ Identikey::Authentication.configure do
53
96
  end
54
97
 
55
98
  Identikey::Administration.configure do
56
- wsdl './path/to/your/administrtaion.wsdl'
99
+ wsdl './path/to/your/administrtation.wsdl'
100
+ endpoint 'https://your-identikey.example.com:8888'
101
+
102
+ # ... more configuration options as needed ...
103
+ end
104
+
105
+ Identikey::Provisioning.configure do
106
+ wsdl './path/to/your/provisioning.wsdl'
57
107
  endpoint 'https://your-identikey.example.com:8888'
58
108
 
59
109
  # ... more configuration options as needed ...
@@ -17,6 +17,13 @@ end
17
17
 
18
18
  puts "Configured Admin WSDL #{ENV.fetch('IK_WSDL_ADMIN')} against #{ENV.fetch('IK_HOST')}"
19
19
 
20
+ Identikey::Provisioning.configure do
21
+ wsdl ENV.fetch('IK_WSDL_PROVN')
22
+ endpoint ENV.fetch('IK_HOST')
23
+ end
24
+
25
+ puts "Configured Provisioning WSDL #{ENV.fetch('IK_WSDL_PROVN')} against #{ENV.fetch('IK_HOST')}"
26
+
20
27
  $ik = Identikey::Administration::Session.new(
21
28
  username: ENV.fetch('IK_USER'),
22
29
  password: ENV.fetch('IK_PASS'),
@@ -18,11 +18,10 @@ Gem::Specification.new do |spec|
18
18
  spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
19
19
  `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
20
20
  end
21
- spec.bindir = "exe"
22
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
21
  spec.require_paths = ["lib"]
24
22
 
25
23
  spec.add_dependency "savon", "~> 2.0"
24
+ spec.add_dependency "wasabi", "~> 3.5.0"
26
25
 
27
26
  spec.add_development_dependency "bundler", "~> 2.0"
28
27
  spec.add_development_dependency "rake", ">= 12.3.3"
@@ -5,3 +5,4 @@ require 'identikey/error'
5
5
  require 'identikey/unsigned'
6
6
  require 'identikey/authentication'
7
7
  require 'identikey/administration'
8
+ require 'identikey/provisioning'
@@ -18,6 +18,10 @@ module Identikey
18
18
  :admin_session_query, :user_execute, :user_query,
19
19
  :digipass_execute, :digipass_query, :digipassappl_execute
20
20
 
21
+ ###
22
+ ## LOGON/LOGOFF/PING
23
+ ###
24
+
21
25
  def logon(username:, password:, domain:)
22
26
  resp = super(message: {
23
27
  attributeSet: {
@@ -95,6 +99,10 @@ module Identikey
95
99
  parse_response resp, :admin_session_query_response
96
100
  end
97
101
 
102
+ ###
103
+ ## USER_EXECUTE
104
+ ###
105
+
98
106
  def user_execute(session_id:, cmd:, attributes: [])
99
107
  resp = super(message: {
100
108
  sessionID: session_id,
@@ -180,6 +188,10 @@ module Identikey
180
188
  )
181
189
  end
182
190
 
191
+ ###
192
+ ## USER_QUERY
193
+ ###
194
+
183
195
  # Executes a userQuery command that searches users. By default, it doesn't
184
196
  # log anywhere. To enable logging to a specific destination, pass a logger
185
197
  # as the log: option. To log to the default destination, pass `true` as
@@ -199,6 +211,9 @@ module Identikey
199
211
  end
200
212
  end
201
213
 
214
+ ###
215
+ ## DIGIPASS_EXECUTE
216
+ ###
202
217
 
203
218
  def digipass_execute(session_id:, cmd:, attributes: [])
204
219
  resp = super(message: {
@@ -245,6 +260,9 @@ module Identikey
245
260
  )
246
261
  end
247
262
 
263
+ ###
264
+ ## DIGIPASS_QUERY
265
+ ###
248
266
 
249
267
  def digipass_query(session_id:, attributes:, query_options:)
250
268
  resp = super(message: {
@@ -258,6 +276,9 @@ module Identikey
258
276
  parse_response resp, :digipass_query_response
259
277
  end
260
278
 
279
+ ###
280
+ ## DIGIPASSAPPL_EXECUTE
281
+ ###
261
282
 
262
283
  def digipassappl_execute(session_id:, cmd:, attributes:)
263
284
  resp = super(message: {
@@ -314,6 +335,5 @@ module Identikey
314
335
  client.globals[:log] = old_log
315
336
  end
316
337
 
317
-
318
338
  end
319
339
  end
@@ -6,12 +6,21 @@ module Identikey
6
6
  attr_reader :session_id, :product, :version
7
7
  attr_reader :privileges, :location
8
8
 
9
- def initialize(username:, password:, domain: 'master')
9
+ def initialize(username:, password: nil, apikey: nil, domain: 'master')
10
+ if password.nil? && apikey.nil?
11
+ raise Identikey::UsageError, "Either a password or an API Key is required"
12
+ end
13
+
10
14
  @client = Identikey::Administration.new
11
15
 
12
16
  @username = username
13
17
  @password = password
14
18
  @domain = domain
19
+
20
+ if apikey
21
+ @service_user = true
22
+ @session_id = "Apikey #{username}:#{apikey}"
23
+ end
15
24
  end
16
25
 
17
26
  def endpoint
@@ -23,6 +32,8 @@ module Identikey
23
32
  end
24
33
 
25
34
  def logon
35
+ require_classic_user!
36
+
26
37
  stat, sess, error = @client.logon(username: @username, password: @password, domain: @domain)
27
38
 
28
39
  if stat != 'STAT_SUCCESS'
@@ -42,6 +53,7 @@ module Identikey
42
53
  end
43
54
 
44
55
  def logoff
56
+ require_classic_user!
45
57
  require_logged_on!
46
58
 
47
59
  stat, _, error = @client.logoff session_id: @session_id
@@ -60,6 +72,8 @@ module Identikey
60
72
  end
61
73
 
62
74
  def alive?(log: true)
75
+ require_classic_user!
76
+
63
77
  return false unless logged_on?
64
78
 
65
79
  stat, _ = @client.ping session_id: @session_id, log: log
@@ -108,7 +122,17 @@ module Identikey
108
122
  end
109
123
 
110
124
  def inspect
111
- "#<#{self.class.name} sid=#@session_id username=#@username domain=#@domain product=#@product>"
125
+ descr = if service_user?
126
+ "SERVICE USER"
127
+ else
128
+ "domain=#@domain product=#@product"
129
+ end
130
+
131
+ "#<#{self.class.name} sid=#@session_id username=#@username #{descr}>"
132
+ end
133
+
134
+ def service_user?
135
+ !!@service_user
112
136
  end
113
137
 
114
138
  alias sid session_id
@@ -123,6 +147,12 @@ module Identikey
123
147
  end
124
148
  end
125
149
 
150
+ def require_classic_user!
151
+ if service_user?
152
+ raise Identikey::UsageError, "This command is not supported with Service users"
153
+ end
154
+ end
155
+
126
156
  def parse_privileges(privileges)
127
157
  privileges.split(', ').inject({}) do |h, priv|
128
158
  privilege, status = priv.split(' ')
@@ -161,6 +161,16 @@ module Identikey
161
161
  true
162
162
  end
163
163
 
164
+ def set_local_auth!(value)
165
+ ensure_persisted!
166
+
167
+ self.local_auth = value
168
+
169
+ self.save!
170
+
171
+ self
172
+ end
173
+
164
174
  def unlock!
165
175
  ensure_persisted!
166
176
 
@@ -6,11 +6,13 @@ module Identikey
6
6
 
7
7
  operations :auth_user
8
8
 
9
- def auth_user(user, domain, otp)
9
+ def auth_user(user, domain, otp, client = nil)
10
+ client ||= 'Administration Program'
11
+
10
12
  resp = super(message: {
11
13
  credentialAttributeSet: {
12
14
  attributes: typed_attributes_list_from(
13
- CREDFLD_COMPONENT_TYPE: 'Administration Program',
15
+ CREDFLD_COMPONENT_TYPE: client,
14
16
  CREDFLD_USERID: user,
15
17
  CREDFLD_DOMAIN: domain,
16
18
  CREDFLD_PASSWORD_FORMAT: Unsigned(0),
@@ -22,18 +24,18 @@ module Identikey
22
24
  parse_response resp, :auth_user_response
23
25
  end
24
26
 
25
- def self.valid_otp?(user, domain, otp)
26
- status, result, _ = new.auth_user(user, domain, otp)
27
+ def self.valid_otp?(user, domain, otp, client = nil)
28
+ status, result, _ = new.auth_user(user, domain, otp, client)
27
29
  return otp_validated_ok?(status, result)
28
30
  end
29
31
 
30
- def self.validate!(user, domain, otp)
31
- status, result, error_stack = new.auth_user(user, domain, otp)
32
+ def self.validate!(user, domain, otp, client = nil)
33
+ status, result, error_stack = new.auth_user(user, domain, otp, client)
32
34
 
33
35
  if otp_validated_ok?(status, result)
34
36
  return true
35
37
  else
36
- error_message = result['CREDFLD_STATUS_MESSAGE']
38
+ error_message = result ? result['CREDFLD_STATUS_MESSAGE'] : 'no status returned'
37
39
  raise Identikey::OperationFailed.new("OTP Validation error (#{status}): #{error_message}", error_stack)
38
40
  end
39
41
  end
@@ -52,5 +54,16 @@ module Identikey
52
54
  def self.otp_validated_ok?(status, result)
53
55
  status == 'STAT_SUCCESS' && !result.key?('CREDFLD_STATUS_MESSAGE')
54
56
  end
57
+
58
+
59
+ protected
60
+ def parse_result_root(body, root_element)
61
+ root = super
62
+
63
+ # The authentication API wraps the results with another element
64
+ #
65
+ results_key = root_element.to_s.sub(/_response$/, '_results').to_sym
66
+ return root[results_key]
67
+ end
55
68
  end
56
69
  end
@@ -82,6 +82,7 @@ module Identikey
82
82
 
83
83
  filters: [
84
84
  'sessionID',
85
+ 'staticPassword',
85
86
  'identikey:CREDFLD_PASSWORD',
86
87
  'identikey:CREDFLD_STATIC_PASSWORD',
87
88
  'identikey:CREDFLD_SESSION_ID'
@@ -100,7 +101,7 @@ module Identikey
100
101
 
101
102
  # Parse the generic response types that the API returns.
102
103
  #
103
- # The returned attributes (up to now...) are always:
104
+ # The returned attributes in the Administration API are:
104
105
  #
105
106
  # - The given root element, whose name is derived from the SOAP command
106
107
  # that was invoked
@@ -111,6 +112,11 @@ module Identikey
111
112
  # or multiple ones.
112
113
  # - :error_stack, a list of error that occurred
113
114
  #
115
+ # The authentication API wraps the results element in another one
116
+ #
117
+ # The provisioning API uses a status element for the result code and
118
+ # error stack, and a result element for the results.
119
+ #
114
120
  # The returned value is a three-elements array, containing:
115
121
  #
116
122
  # [Response code, Attribute(s) list, Errors list]
@@ -129,11 +135,19 @@ module Identikey
129
135
  # formats. TODO maybe create a separate class for errors, that includes
130
136
  # the error code.
131
137
  #
132
- # TODO refactor and split in separate methods
133
- #
134
138
  def parse_response(resp, root_element)
135
- body = resp.body
139
+ root = parse_result_root(resp.body, root_element)
140
+
141
+ results = parse_result_element(root, root_element)
142
+
143
+ result_code = parse_result_code(results, root_element)
144
+ result_attributes = parse_result_attributes(results, root_element)
145
+ result_errors = parse_result_errors(results, root_element)
136
146
 
147
+ return result_code, result_attributes, result_errors
148
+ end
149
+
150
+ def parse_result_root(body, root_element)
137
151
  if body.size.zero?
138
152
  raise Identikey::ParseError, "Empty response received"
139
153
  end
@@ -144,40 +158,35 @@ module Identikey
144
158
 
145
159
  # The root results element
146
160
  #
147
- root = body[root_element]
148
-
149
- # ... that the authentication API wraps with another element
150
- #
151
- results_key = root_element.to_s.sub(/_response$/, '_results').to_sym
152
- if root.keys.size == 1 && root.key?(results_key)
153
- root = root[results_key]
154
- end
161
+ return body[root_element]
162
+ end
155
163
 
164
+ def parse_result_element(root, root_element)
156
165
  # The results element
157
166
  #
158
167
  unless root.key?(:results)
159
168
  raise Identikey::ParseError, "Results element not found below #{root_element}"
160
169
  end
161
170
 
162
- results = root[:results]
171
+ return root[:results]
172
+ end
163
173
 
164
- # Result code
165
- #
174
+ def parse_result_code(results, root_element)
166
175
  unless results.key?(:result_codes)
167
176
  raise Identikey::ParseError, "Result codes not found below #{root_element}"
168
177
  end
169
178
 
170
- result_code = results[:result_codes][:status_code_enum] || 'STAT_UNKNOWN'
179
+ results[:result_codes][:status_code_enum] || 'STAT_UNKNOWN'
180
+ end
171
181
 
172
- # Result attributes
173
- #
182
+ def parse_result_attributes(results, root_element)
174
183
  unless results.key?(:result_attribute)
175
184
  raise Identikey::ParseError, "Result attribute not found below #{root_element}"
176
185
  end
177
186
 
178
187
  results_attr = results[:result_attribute]
179
188
 
180
- result_attributes = if results_attr.key?(:attributes)
189
+ if results_attr.key?(:attributes)
181
190
  entries = [ results_attr[:attributes] ].flatten
182
191
  parse_attributes entries
183
192
 
@@ -193,16 +202,14 @@ module Identikey
193
202
  else
194
203
  nil
195
204
  end
205
+ end
196
206
 
197
- # Errors
198
- #
199
- errors = if results[:error_stack].key?(:errors)
207
+ def parse_result_errors(results, root_element)
208
+ if results[:error_stack].key?(:errors)
200
209
  parse_errors results[:error_stack][:errors]
201
210
  else
202
211
  nil
203
212
  end
204
-
205
- return result_code, result_attributes, errors
206
213
  end
207
214
 
208
215
  def parse_attributes(attributes)
@@ -0,0 +1,150 @@
1
+ require 'identikey/base'
2
+
3
+ module Identikey
4
+ # This class wraps the Provisioning API.
5
+ #
6
+ class Provisioning < Base
7
+ client wsdl: './sdk/wsdl/provisioning.wsdl'
8
+
9
+ operations :provisioning_execute, :dsapp_srp_register
10
+
11
+ ###
12
+ ## PROVISIONING_EXECUTE
13
+ ###
14
+
15
+ def provisioning_execute(cmd:, attributes:)
16
+ resp = super(message: {
17
+ cmd: cmd,
18
+ attributeSet: {
19
+ attributes: attributes
20
+ }
21
+ })
22
+
23
+ parse_response resp, :provisioning_execute_response
24
+ end
25
+
26
+ def provisioning_execute_MDL_REGISTER(component:, user:, domain:, password:)
27
+ provisioning_execute(
28
+ cmd: 'PROVISIONCMD_MDL_REGISTER',
29
+ attributes: typed_attributes_list_from(
30
+ PROVFLD_USERID: user,
31
+ PROVFLD_DOMAIN: domain,
32
+ PROVFLD_COMPONENT_TYPE: component,
33
+ PROVFLD_STATIC_PASSWORD: password,
34
+ PROVFLD_ACTIVATION_TYPE: Unsigned(0),
35
+ )
36
+ )
37
+ end
38
+
39
+ ###
40
+ ## dsappSRPRegister
41
+ ###
42
+
43
+ def dsapp_srp_register(component:, user:, domain:, password:)
44
+ resp = super(message: {
45
+ componentType: component,
46
+ user: {
47
+ userID: user,
48
+ domain: domain,
49
+ },
50
+ credential: {
51
+ staticPassword: password
52
+ }
53
+ })
54
+
55
+ parse_response resp, :dsapp_srp_register_response
56
+ end
57
+
58
+ ###
59
+ ## Wraps dsapp_srp_register and returns directly the activation
60
+ ## message through which a CRONTO image can be generated, to be
61
+ ## used for push notifications setups in combination with a MDC
62
+ ## configured on your OneSpan control panel and on Identikey.
63
+ ##
64
+ ## You may want to look into https://github.com/ifad/cronto for
65
+ ## a generator to produce PNGs to deliver to your clients.
66
+ ####
67
+ def self.cronto_code_for_srp_registration(gateway:, **kwargs)
68
+ status, result, error = new.dsapp_srp_register(**kwargs)
69
+
70
+ if status != 'STAT_SUCCESS'
71
+ raise Identikey::OperationFailed, "Error while assigning DAL: #{status} - #{[error].flatten.join('; ')}"
72
+ end
73
+
74
+ # Compose proprietary string
75
+ message = '01;01;%s;%s;%s;%s;%s' % [
76
+ result[:user][:user_id],
77
+ result[:user][:domain],
78
+ result[:registration_id],
79
+ result[:activation_password],
80
+ gateway
81
+ ]
82
+
83
+ # Encode it as hex
84
+ return message.split(//).map {|c| '%x' % c.ord}.join
85
+ end
86
+
87
+ protected
88
+ # The provisioningExecute command has the same
89
+ # design as the rest of the API, with a single
90
+ # multi-purpose `results` element that carries
91
+ # key-value results.
92
+ #
93
+ # Instead, dsappSRPRegister and other commands
94
+ # use a different design with return types and
95
+ # values predefined in the WSDL.
96
+ #
97
+ # So if we have a `results` element, this is a
98
+ # old-style response, and we delegate parsing
99
+ # to the parent class, otherwise if we detect
100
+ # the `result` and `status` elements, we parse
101
+ # in this class, basically just returning the
102
+ # values that Savon parsed for us.
103
+ #
104
+ def parse_result_element(root, root_element)
105
+ if root.key?(:results)
106
+ root[:results]
107
+ else
108
+ root
109
+ end
110
+ end
111
+
112
+ def parse_result_code(root, root_element)
113
+ if root.key?(:status)
114
+ super root[:status], root_element
115
+ else
116
+ super
117
+ end
118
+ end
119
+
120
+ # This may be an old or a new style response.
121
+ #
122
+ # New style responses may have both `result`
123
+ # and `status` elements, or only a `status`
124
+ # element.
125
+ #
126
+ # If there is no `result` but there is a
127
+ # `status`, then consider this a new style
128
+ # response and return an empty result. Else,
129
+ # pass along to the superclass to parse the
130
+ # old style response.
131
+ #
132
+ def parse_result_attributes(root, root_element)
133
+ if root.key?(:result)
134
+ root[:result]
135
+ elsif root.key?(:status)
136
+ nil
137
+ else
138
+ super
139
+ end
140
+ end
141
+
142
+ def parse_result_errors(root, root_element)
143
+ if root.key?(:status)
144
+ super root[:status], root_element
145
+ else
146
+ super
147
+ end
148
+ end
149
+ end
150
+ end
@@ -1,3 +1,3 @@
1
1
  module Identikey
2
- VERSION = "0.7.1"
2
+ VERSION = "0.9.1"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: identikey
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.1
4
+ version: 0.9.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marcello Barnaba
8
8
  autorequire:
9
- bindir: exe
9
+ bindir: bin
10
10
  cert_chain: []
11
- date: 2020-06-14 00:00:00.000000000 Z
11
+ date: 2020-11-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: savon
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: wasabi
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 3.5.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 3.5.0
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: bundler
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -206,6 +220,7 @@ files:
206
220
  - lib/identikey/authentication.rb
207
221
  - lib/identikey/base.rb
208
222
  - lib/identikey/error.rb
223
+ - lib/identikey/provisioning.rb
209
224
  - lib/identikey/unsigned.rb
210
225
  - lib/identikey/version.rb
211
226
  - log/.keep