authify-api 0.0.5

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 (44) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +23 -0
  5. data/.travis.yml +13 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +41 -0
  9. data/Rakefile +38 -0
  10. data/authify-api.gemspec +45 -0
  11. data/bin/console +14 -0
  12. data/bin/setup +8 -0
  13. data/config.ru +9 -0
  14. data/db/migrate/20170201213901_create_users.rb +12 -0
  15. data/db/migrate/20170201220029_create_api_keys.rb +12 -0
  16. data/db/migrate/20170202004557_create_identities.rb +12 -0
  17. data/db/migrate/20170203231922_create_organizations_and_organization_memberships.rb +22 -0
  18. data/db/migrate/20170203231929_create_groups.rb +17 -0
  19. data/db/migrate/20170204001405_create_trusted_delegates.rb +13 -0
  20. data/db/schema.rb +95 -0
  21. data/lib/authify/api.rb +51 -0
  22. data/lib/authify/api/controllers/api_key.rb +36 -0
  23. data/lib/authify/api/controllers/group.rb +40 -0
  24. data/lib/authify/api/controllers/organization.rb +48 -0
  25. data/lib/authify/api/controllers/user.rb +72 -0
  26. data/lib/authify/api/helpers/api_user.rb +30 -0
  27. data/lib/authify/api/helpers/jwt_encryption.rb +42 -0
  28. data/lib/authify/api/jsonapi_utils.rb +12 -0
  29. data/lib/authify/api/models/api_key.rb +44 -0
  30. data/lib/authify/api/models/group.rb +17 -0
  31. data/lib/authify/api/models/identity.rb +14 -0
  32. data/lib/authify/api/models/organization.rb +26 -0
  33. data/lib/authify/api/models/organization_membership.rb +16 -0
  34. data/lib/authify/api/models/trusted_delegate.rb +44 -0
  35. data/lib/authify/api/models/user.rb +73 -0
  36. data/lib/authify/api/serializers/api_key_serializer.rb +13 -0
  37. data/lib/authify/api/serializers/group_serializer.rb +15 -0
  38. data/lib/authify/api/serializers/organization_serializer.rb +15 -0
  39. data/lib/authify/api/serializers/user_serializer.rb +17 -0
  40. data/lib/authify/api/service.rb +11 -0
  41. data/lib/authify/api/services/api.rb +58 -0
  42. data/lib/authify/api/services/jwt_provider.rb +61 -0
  43. data/lib/authify/api/version.rb +9 -0
  44. metadata +324 -0
@@ -0,0 +1,16 @@
1
+ module Authify
2
+ module API
3
+ module Models
4
+ # Linkage between Organizations and users
5
+ class OrganizationMembership < ActiveRecord::Base
6
+ include JSONAPIUtils
7
+
8
+ belongs_to :organization,
9
+ class_name: 'Authify::API::Models::Organization'
10
+
11
+ belongs_to :user,
12
+ class_name: 'Authify::API::Models::User'
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,44 @@
1
+ module Authify
2
+ module API
3
+ module Models
4
+ # Trusted Delegates are remote applications that can do anything
5
+ class TrustedDelegate < ActiveRecord::Base
6
+ include Core::SecureHashing
7
+ extend Core::SecureHashing
8
+
9
+ attr_reader :secret_key
10
+
11
+ validates_uniqueness_of :name
12
+ validates_uniqueness_of :access_key
13
+
14
+ def secret_key=(unencrypted_string)
15
+ @secret_key = unencrypted_string
16
+ self.secret_key_digest = salted_sha512(unencrypted_string) if viable(unencrypted_string)
17
+ end
18
+
19
+ def compare_secret(unencrypted_string)
20
+ compare_salted_sha512(unencrypted_string, secret_key_digest)
21
+ end
22
+
23
+ def set_secret!
24
+ self.secret_key = self.class.generate_access_key + self.class.generate_access_key
25
+ end
26
+
27
+ def self.generate_access_key
28
+ to_hex(SecureRandom.gen_random(32))[0...32]
29
+ end
30
+
31
+ def self.from_access_key(access, secret)
32
+ trusted_delegate = find_by_access_key(access)
33
+ trusted_delegate if trusted_delegate && trusted_delegate.compare_secret(secret)
34
+ end
35
+
36
+ private
37
+
38
+ def viable(string)
39
+ string && !string.empty?
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,73 @@
1
+ module Authify
2
+ module API
3
+ module Models
4
+ # A User of the system
5
+ class User < ActiveRecord::Base
6
+ include Core::SecureHashing
7
+ include JSONAPIUtils
8
+
9
+ attr_reader :password
10
+
11
+ validates_uniqueness_of :email
12
+ validates_format_of :email, with: /[-a-z0-9_+\.+]+\@([-a-z0-9]+\.)+[a-z0-9]{2,4}/i
13
+
14
+ validates_presence_of :password_digest
15
+
16
+ has_many :api_keys,
17
+ class_name: 'Authify::API::Models::APIKey',
18
+ dependent: :destroy
19
+
20
+ has_many :identities,
21
+ class_name: 'Authify::API::Models::Identity',
22
+ dependent: :destroy
23
+
24
+ has_many :organization_memberships,
25
+ class_name: 'Authify::API::Models::OrganizationMembership',
26
+ dependent: :destroy
27
+
28
+ has_many :organizations,
29
+ through: :organization_memberships,
30
+ class_name: 'Authify::API::Models::Organization'
31
+
32
+ has_and_belongs_to_many :groups,
33
+ class_name: 'Authify::API::Models::Group'
34
+
35
+ # Encrypts the password into the password_digest attribute.
36
+ def password=(plain_password)
37
+ @password = plain_password
38
+ self.password_digest = salted_sha512(plain_password) if viable(plain_password)
39
+ end
40
+
41
+ def authenticate(unencrypted_password)
42
+ return false unless unencrypted_password && !unencrypted_password.empty?
43
+ compare_salted_sha512(unencrypted_password, password_digest)
44
+ end
45
+
46
+ def admin_for?(organization)
47
+ organization.admins.include?(self)
48
+ end
49
+
50
+ def self.from_api_key(access, secret)
51
+ key = APIKey.find_by_access_key(access)
52
+ key.user if key && key.compare_secret(secret)
53
+ end
54
+
55
+ def self.from_email(email, password)
56
+ found_user = Models::User.find_by_email(email)
57
+ found_user if found_user && found_user.authenticate(password)
58
+ end
59
+
60
+ def self.from_identity(provider, uid)
61
+ provided_identity = Identity.find_by_provider_and_uid(provider, uid)
62
+ provided_identity.user if provided_identity
63
+ end
64
+
65
+ private
66
+
67
+ def viable(string)
68
+ string && !string.empty?
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,13 @@
1
+ module Authify
2
+ module API
3
+ module Serializers
4
+ # JSON API Serializer for APIKey model
5
+ class APIKeySerializer
6
+ include JSONAPI::Serializer
7
+
8
+ attribute :access_key
9
+ has_one :user
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ module Authify
2
+ module API
3
+ module Serializers
4
+ # JSON API Serializer for Group model
5
+ class GroupSerializer
6
+ include JSONAPI::Serializer
7
+
8
+ attribute :name
9
+ attribute :description
10
+ has_one :organization
11
+ has_many :users
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module Authify
2
+ module API
3
+ module Serializers
4
+ # JSON API Serializer for Organization model
5
+ class OrganizationSerializer
6
+ include JSONAPI::Serializer
7
+
8
+ attribute :name
9
+ attribute :description
10
+ has_many :groups
11
+ has_many :users
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,17 @@
1
+ module Authify
2
+ module API
3
+ module Serializers
4
+ # JSON API Serializer for User model
5
+ class UserSerializer
6
+ include JSONAPI::Serializer
7
+
8
+ attribute :email
9
+ attribute :full_name
10
+ has_many :api_keys
11
+ has_many :groups
12
+ has_many :organizations
13
+ has_many :identities
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,11 @@
1
+ module Authify
2
+ module API
3
+ # Base class for building Sinatra apps (web services)
4
+ class Service < Sinatra::Base
5
+ helpers Helpers::JWTEncryption
6
+ register Sinatra::ActiveRecordExtension
7
+
8
+ set :database, CONFIG[:db]
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,58 @@
1
+ module Authify
2
+ module API
3
+ module Services
4
+ # The main API Sinatra App
5
+ class API < Service
6
+ use Middleware::JWTAuth
7
+ register Sinatra::JSONAPI
8
+
9
+ configure do
10
+ set :protection, except: :http_origin
11
+ end
12
+
13
+ # rubocop:disable Metrics/BlockLength
14
+ before '*' do
15
+ headers 'Access-Control-Allow-Origin' => '*',
16
+ 'Access-Control-Allow-Methods' => %w(
17
+ OPTIONS
18
+ DELETE
19
+ GET
20
+ PATCH
21
+ POST
22
+ PUT
23
+ )
24
+
25
+ unless env[:authenticated]
26
+ processed_headers = request.env.dup.each_with_object({}) do |(k, v), acc|
27
+ acc[Regexp.last_match(1).downcase] = v if k =~ /^http_(.*)/i
28
+ end
29
+ if processed_headers.key?('x_authify_access')
30
+ access = processed_headers['x_authify_access']
31
+ secret = processed_headers['x_authify_secret']
32
+ remote_app = Models::TrustedDelegate.from_access_key(access, secret)
33
+ env[:authenticated] = true if remote_app
34
+
35
+ if remote_app && processed_headers.key?('x_authify_on_behalf_of')
36
+ @current_user = Models::User.find_by_email(
37
+ processed_headers['x_authify_on_behalf_of']
38
+ )
39
+ end
40
+ end
41
+ end
42
+ unless env[:authenticated]
43
+ halt 401, env[:authentication_errors].map(&:message).join(', ')
44
+ end
45
+ end
46
+
47
+ helpers Helpers::APIUser
48
+
49
+ resource :api_keys, &Controllers::APIKey
50
+ resource :groups, &Controllers::Group
51
+ resource :organizations, &Controllers::Organization
52
+ resource :users, &Controllers::User
53
+
54
+ freeze_jsonapi
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,61 @@
1
+ module Authify
2
+ module API
3
+ module Services
4
+ # A Sinatra App specifically for managing JWT tokens
5
+ class JWTProvider < Service
6
+ helpers Helpers::APIUser
7
+
8
+ configure do
9
+ set :protection, except: :http_origin
10
+ end
11
+
12
+ before '*' do
13
+ content_type 'application/json'
14
+ headers 'Access-Control-Allow-Origin' => '*',
15
+ 'Access-Control-Allow-Methods' => %w(
16
+ OPTIONS
17
+ GET
18
+ POST
19
+ )
20
+
21
+ begin
22
+ unless request.get? || request.options?
23
+ request.body.rewind
24
+ @parsed_body = JSON.parse(request.body.read)
25
+ end
26
+ rescue => e
27
+ halt(400, { error: "Request must be valid JSON: #{e.message}" }.to_json)
28
+ end
29
+ end
30
+
31
+ post '/token' do
32
+ # For CLI / Typical API clients
33
+ access = @parsed_body['access_key']
34
+ secret = @parsed_body['secret_key']
35
+ # For Web UIs
36
+ email = @parsed_body['email']
37
+ password = @parsed_body['password']
38
+
39
+ found_user = if access
40
+ Models::User.from_api_key(access, secret)
41
+ elsif email
42
+ Models::User.from_email(email, password)
43
+ end
44
+
45
+ if found_user
46
+ update_current_user found_user
47
+ { jwt: jwt_token }.to_json
48
+ else
49
+ halt 401
50
+ end
51
+ end
52
+
53
+ get '/key' do
54
+ content_type 'application/x-pem-file'
55
+ headers['Content-Disposition'] = 'attachment;filename=public_key.pem'
56
+ public_key.export
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,9 @@
1
+ module Authify
2
+ module API
3
+ VERSION = [
4
+ 0, # Major
5
+ 0, # Minor
6
+ 5 # Patch
7
+ ].join('.')
8
+ end
9
+ end
metadata ADDED
@@ -0,0 +1,324 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: authify-api
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.5
5
+ platform: ruby
6
+ authors:
7
+ - Jonathan Gnagy
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-02-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: authify-core
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: authify-middleware
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: connection_pool
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.2'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: sinatra
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.4'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.4'
69
+ - !ruby/object:Gem::Dependency
70
+ name: sinatra-activerecord
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '2.0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '2.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: moneta
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.8'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.8'
97
+ - !ruby/object:Gem::Dependency
98
+ name: mysql2
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.4'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.4'
111
+ - !ruby/object:Gem::Dependency
112
+ name: jsonapi-serializers
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '0.16'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '0.16'
125
+ - !ruby/object:Gem::Dependency
126
+ name: sinja
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '1.2'
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '1.2'
139
+ - !ruby/object:Gem::Dependency
140
+ name: puma
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '3.7'
146
+ type: :runtime
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '3.7'
153
+ - !ruby/object:Gem::Dependency
154
+ name: bundler
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '1.12'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '1.12'
167
+ - !ruby/object:Gem::Dependency
168
+ name: rake
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - "~>"
172
+ - !ruby/object:Gem::Version
173
+ version: '10.0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - "~>"
179
+ - !ruby/object:Gem::Version
180
+ version: '10.0'
181
+ - !ruby/object:Gem::Dependency
182
+ name: rspec
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - "~>"
186
+ - !ruby/object:Gem::Version
187
+ version: '3.1'
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - "~>"
193
+ - !ruby/object:Gem::Version
194
+ version: '3.1'
195
+ - !ruby/object:Gem::Dependency
196
+ name: rubocop
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - "~>"
200
+ - !ruby/object:Gem::Version
201
+ version: '0.35'
202
+ type: :development
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - "~>"
207
+ - !ruby/object:Gem::Version
208
+ version: '0.35'
209
+ - !ruby/object:Gem::Dependency
210
+ name: yard
211
+ requirement: !ruby/object:Gem::Requirement
212
+ requirements:
213
+ - - "~>"
214
+ - !ruby/object:Gem::Version
215
+ version: '0.8'
216
+ type: :development
217
+ prerelease: false
218
+ version_requirements: !ruby/object:Gem::Requirement
219
+ requirements:
220
+ - - "~>"
221
+ - !ruby/object:Gem::Version
222
+ version: '0.8'
223
+ - !ruby/object:Gem::Dependency
224
+ name: travis
225
+ requirement: !ruby/object:Gem::Requirement
226
+ requirements:
227
+ - - "~>"
228
+ - !ruby/object:Gem::Version
229
+ version: '1.8'
230
+ type: :development
231
+ prerelease: false
232
+ version_requirements: !ruby/object:Gem::Requirement
233
+ requirements:
234
+ - - "~>"
235
+ - !ruby/object:Gem::Version
236
+ version: '1.8'
237
+ - !ruby/object:Gem::Dependency
238
+ name: simplecov
239
+ requirement: !ruby/object:Gem::Requirement
240
+ requirements:
241
+ - - "~>"
242
+ - !ruby/object:Gem::Version
243
+ version: '0.13'
244
+ type: :development
245
+ prerelease: false
246
+ version_requirements: !ruby/object:Gem::Requirement
247
+ requirements:
248
+ - - "~>"
249
+ - !ruby/object:Gem::Version
250
+ version: '0.13'
251
+ description:
252
+ email:
253
+ - jgnagy@knuedge.com
254
+ executables: []
255
+ extensions: []
256
+ extra_rdoc_files: []
257
+ files:
258
+ - ".gitignore"
259
+ - ".rspec"
260
+ - ".rubocop.yml"
261
+ - ".travis.yml"
262
+ - Gemfile
263
+ - LICENSE.txt
264
+ - README.md
265
+ - Rakefile
266
+ - authify-api.gemspec
267
+ - bin/console
268
+ - bin/setup
269
+ - config.ru
270
+ - db/migrate/20170201213901_create_users.rb
271
+ - db/migrate/20170201220029_create_api_keys.rb
272
+ - db/migrate/20170202004557_create_identities.rb
273
+ - db/migrate/20170203231922_create_organizations_and_organization_memberships.rb
274
+ - db/migrate/20170203231929_create_groups.rb
275
+ - db/migrate/20170204001405_create_trusted_delegates.rb
276
+ - db/schema.rb
277
+ - lib/authify/api.rb
278
+ - lib/authify/api/controllers/api_key.rb
279
+ - lib/authify/api/controllers/group.rb
280
+ - lib/authify/api/controllers/organization.rb
281
+ - lib/authify/api/controllers/user.rb
282
+ - lib/authify/api/helpers/api_user.rb
283
+ - lib/authify/api/helpers/jwt_encryption.rb
284
+ - lib/authify/api/jsonapi_utils.rb
285
+ - lib/authify/api/models/api_key.rb
286
+ - lib/authify/api/models/group.rb
287
+ - lib/authify/api/models/identity.rb
288
+ - lib/authify/api/models/organization.rb
289
+ - lib/authify/api/models/organization_membership.rb
290
+ - lib/authify/api/models/trusted_delegate.rb
291
+ - lib/authify/api/models/user.rb
292
+ - lib/authify/api/serializers/api_key_serializer.rb
293
+ - lib/authify/api/serializers/group_serializer.rb
294
+ - lib/authify/api/serializers/organization_serializer.rb
295
+ - lib/authify/api/serializers/user_serializer.rb
296
+ - lib/authify/api/service.rb
297
+ - lib/authify/api/services/api.rb
298
+ - lib/authify/api/services/jwt_provider.rb
299
+ - lib/authify/api/version.rb
300
+ homepage: https://github.com/knuedge/authify-api
301
+ licenses:
302
+ - MIT
303
+ metadata: {}
304
+ post_install_message:
305
+ rdoc_options: []
306
+ require_paths:
307
+ - lib
308
+ required_ruby_version: !ruby/object:Gem::Requirement
309
+ requirements:
310
+ - - "~>"
311
+ - !ruby/object:Gem::Version
312
+ version: '2.0'
313
+ required_rubygems_version: !ruby/object:Gem::Requirement
314
+ requirements:
315
+ - - ">="
316
+ - !ruby/object:Gem::Version
317
+ version: '0'
318
+ requirements: []
319
+ rubyforge_project:
320
+ rubygems_version: 2.5.1
321
+ signing_key:
322
+ specification_version: 4
323
+ summary: Authify API Server library
324
+ test_files: []