virtuatable-core 1.5.0 → 1.6.0.dev0

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: d95c03cd13525c1a5326004d88c62a806c0e991ef8dd0766f0f2465078f2b23e
4
- data.tar.gz: d217b7fd979676aa8b61cd58fb8ad54a3abd352fe105713700c12edc39abfdd6
3
+ metadata.gz: 4ebed77d032ef7111c0bde0982cdfc947a4a1933c1dc22e2cb09a3edeca12af1
4
+ data.tar.gz: '081072a04de88c52c94df498047f6cec83147fccf8c163e2c5856a9bde5398ac'
5
5
  SHA512:
6
- metadata.gz: 4c180ac1d407073cc75d81a8d08f9394ef6d025cb899c4971a94ee6daa213ca5bcf4d240bd491b2374c60c7e2abfab80729226af37b0b7bdc60c0d7c3b2af326
7
- data.tar.gz: d2c695ecb5ad843dc18789f84e6028bde42fb74949351f9f3308018d2c57cd29ef87a9ecc1d2cc8b4cf29a87c6ac73eab12b29177c32c6f0ba915798bc24bbd3
6
+ metadata.gz: db0b0b6d2343f36483293ce34e0e93e6d0cc9a803959f1f84caafaf338309984aaf6b226f33a363a612e4b3724c9b225dc2dfc97139a729d55dfeb2f6a9ef70f
7
+ data.tar.gz: 7fc28600dbd1fa9c0a2cab48fc8431e0778e599a07ab98af8d87d3e9f4dc1e34423d1bfe0daa3fffdaa1744be92097dbab7aa9f32a59961d2e913675ad08b73e
@@ -0,0 +1,15 @@
1
+ module Core
2
+ module Decorators
3
+ class Base < Draper::Decorator
4
+ delegate_all
5
+
6
+ def id
7
+ object.id.to_s
8
+ end
9
+
10
+ def to_json
11
+ to_h.to_json
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,12 @@
1
+ module Core
2
+ module Decorators
3
+ class Token < Core::Decorators::Base
4
+
5
+ def to_h
6
+ {
7
+ token: value
8
+ }
9
+ end
10
+ end
11
+ end
12
+ end
@@ -1,5 +1,7 @@
1
1
  module Core
2
2
  module Decorators
3
+ autoload :Base, 'core/decorators/base'
3
4
  autoload :Campaign, 'core/decorators/campaign'
5
+ autoload :Token, 'core/decorators/token'
4
6
  end
5
7
  end
@@ -12,8 +12,12 @@ module Core
12
12
  super.merge(body_params)
13
13
  end
14
14
 
15
+ def sym_params
16
+ params.map { |k, v| [k.to_sym, v] }.to_h
17
+ end
18
+
15
19
  # The parameters from the JSON body if it is sent.
16
- # @return [Hash] the JSON body parsed as a dictionary.
20
+ # @return [Hash] the JSON body parsed as a dict ionary.
17
21
  def body_params
18
22
  request.body.rewind
19
23
  JSON.parse(request.body.read.to_s)
@@ -13,7 +13,7 @@ module Core
13
13
  def api_list(items)
14
14
  halt 200, {
15
15
  count: items.count,
16
- items: items.map { |item| enhanced_h(item) }
16
+ items: items.map { |item| item.to_h }
17
17
  }.to_json
18
18
  end
19
19
 
@@ -21,13 +21,13 @@ module Core
21
21
  # returning the informations about the created item.
22
22
  # @param item [Object] any object that responds to #to_h to display to the user.
23
23
  def api_created(item)
24
- halt 201, enhanced_json(item)
24
+ halt 201, item.to_json
25
25
  end
26
26
 
27
27
  # Displays an item with the standards of the API.
28
28
  # @param item [Object] the item to display as a JSON formatted hash.
29
29
  def api_item(item)
30
- halt 200, enhanced_json(item)
30
+ halt 200, item.to_json
31
31
  end
32
32
 
33
33
  # Displays a message with a 200 status code
@@ -35,16 +35,6 @@ module Core
35
35
  def api_ok(message)
36
36
  api_item message: message
37
37
  end
38
-
39
- private
40
-
41
- def enhanced_h(item)
42
- (item.respond_to?(:enhance) ? item.enhance : item).to_h
43
- end
44
-
45
- def enhanced_json(item)
46
- enhanced_h(item).to_json
47
- end
48
38
  end
49
39
  end
50
40
  end
@@ -13,44 +13,29 @@ module Core
13
13
  # @!attribute [rw] value
14
14
  # @return [String] the value of the token, returned to the application when built.
15
15
  field :value, type: String, default: ->{ SecureRandom.hex }
16
- # @!attribute [rw] expiration
17
- # @return [Integer] the time, in seconds, after which the token is declared expired, and thus can't be used anymore.
18
- field :expiration, type: Integer, default: 86400
19
16
 
20
17
  # @!attribute [rw] authorization
21
18
  # @return [Core::Models::OAuth::Authorization] the authorization code that issued this token to the application for this user.
22
- belongs_to :authorization, class_name: 'Core::Models::OAuth::Authorization', inverse_of: :tokens
19
+ belongs_to :authorization, class_name: 'Core::Models::OAuth::Authorization', inverse_of: :tokens, optional: true
20
+ # @!attribute [rw] generator
21
+ # @return [Core::Models::Oauth::AccessToken] the token that generated this one.
22
+ belongs_to :generator, class_name: 'Core::Models::OAuth::AccessToken', inverse_of: :generated, optional: true
23
23
 
24
-
25
- # A refresh token is attached to each and every refresh token so that it can be used to deliver a new access token.
26
- # @!attribute [rx] refresh_token
27
- # @return [Core::Models::OAuth::RefreshToken] the refresh token linked to this token
28
- has_one :refresh_token, class_name: 'Core::Models::OAuth::RefreshToken', inverse_of: :token
24
+ # @!attribute [rw] generated
25
+ # @return [Core::Models::OAuth::AccessToken] the token that has been generated by the current one.
26
+ has_one :generated, class_name: 'Core::Models::OAuth::AccessToken', inverse_of: :generator
29
27
 
30
28
  validates :value,
31
29
  presence: {message: 'required'},
32
30
  uniqueness: {message: 'uniq'}
33
31
 
34
- # Checks if the current date is inferior to the creation date + expiration period
35
- # @return [Boolean] TRUE if the token is expired, FALSE otherwise.
36
- def expired?
37
- # Handles the case where the token is given to a premium app (our apps have infinite tokens).
38
- return false if premium?
39
- return true if refresh_token.used?
40
-
41
- created_at.to_time.to_i + expiration < Time.now.to_i
42
- end
43
-
44
32
  # Returns the scopes this access token can use to access the application
45
33
  # @return [Array<Core::Models::OAuth::Scope>] the array of scopes from the linked authorization
46
34
  def scopes
47
- # Premium applications (our applications) have all the rights on the API.
48
- return Core::Models::OAuth::Scope.all.to_a if premium?
49
-
50
- authorization.scopes
35
+ premium ? Core::Models::OAuth::Scope.all.to_a : authorization.scopes
51
36
  end
52
37
 
53
- def premium?
38
+ def premium
54
39
  authorization.application.premium
55
40
  end
56
41
  end
@@ -17,7 +17,7 @@ module Core
17
17
  field :code, type: String, default: ->{ SecureRandom.hex }
18
18
  # @!attribute [rw] expiration
19
19
  # @return [Integer] the time, in seconds, after which the authorization is declared expired.
20
- field :expiration, type: Integer, default: 86400
20
+ field :expiration, type: Integer, default: 60
21
21
 
22
22
  # @!attribute [rw] account
23
23
  # @return [Arkaaan::Account] the account granting the authorization to access its data to the application.
@@ -41,6 +41,10 @@ module Core
41
41
  def expired?
42
42
  created_at.to_time.to_i + expiration < Time.now.to_i
43
43
  end
44
+
45
+ def used?
46
+ tokens.count > 0
47
+ end
44
48
  end
45
49
  end
46
50
  end
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Core
2
4
  module Services
3
5
  class Accounts
4
6
  include Singleton
5
-
7
+
6
8
  def get_by_username(username)
7
9
  account = Core::Models::Account.find_by(username: username)
8
10
  if account.nil?
@@ -15,4 +17,4 @@ module Core
15
17
  end
16
18
  end
17
19
  end
18
- end
20
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Core
4
+ module Services
5
+ # Service managing applications, allowing easy access to them with or without
6
+ # providing client secret for example.
7
+ # @author Vincent Courtois <courtois.vincent@outlook.com>
8
+ class Applications < Core::Services::Base
9
+ include Singleton
10
+
11
+ # Gets an application given its credentials (client UUID and client secret).
12
+ #
13
+ # @param client_id [String] the unique public identifier of the application.
14
+ # @param client_secret [String] the password for the application.
15
+ # @return [Core::Models::OAuth::Application] the application if it has been found.
16
+ #
17
+ # @raise [Core::Helpers::Errors::BadRequest] if a parameter is not correctly given.
18
+ # @raise [Core::Helpers::Errors::Forbidden] if the client secret is wrong for this application.
19
+ # @raise [Core::Helpers::Errors::Unknown] if the application is not found.
20
+ def get_by_credentials(client_id: nil, client_secret: nil, **_ignored)
21
+ require_parameters client_secret: client_secret
22
+ application = get_by_id(client_id: client_id)
23
+ raise forbidden_err(field: 'client_secret', error: 'wrong') if application.client_secret != client_secret
24
+
25
+ application
26
+ end
27
+
28
+ # Gets an application given its client UUID.
29
+ #
30
+ # @param client_id [String] the unique identifier of the application to get
31
+ # @return [Core::Models::OAuth::Application] the application found if no error is raised.
32
+ #
33
+ # @raise [Core::Helpers::Errors::Unknown] if the application is not found.
34
+ def get_by_id(client_id: nil, **_ignored)
35
+ require_parameters client_id: client_id
36
+ application = Core::Models::OAuth::Application.find_by(client_id: client_id)
37
+ raise unknown_err(field: 'client_id') if application.nil?
38
+
39
+ application
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Core
4
+ module Services
5
+ # Service managing authorization codes. These codes represent the access a user
6
+ # is giving to an application on all or part of its data.
7
+ # @author Vincent Courtois <courtois.vincent@outlook.com>
8
+ class Authorizations < Core::Services::Base
9
+ include Singleton
10
+
11
+ # Gets the authorization code corresponding to the provided value if it is linked to the
12
+ # application matching the provided credentials. Otherwise it raises errors.
13
+ #
14
+ # @param client_id [String] the UUID of the application.
15
+ # @param client_secret [String] the password of the application.
16
+ # @param authorization_code [String] the code of the authorization you're trying to get.
17
+ #
18
+ # @return [Core::Models::OAuth::Authorization] the authorization code object if found.
19
+ #
20
+ # @raise [Core::Helpers::Errors::NotFound] if the application or the authorization is unknown
21
+ # @raise [Core::Helpers::Errors::BadRequest] if any parameter is nil.
22
+ # @raise [Core::Helpers::Errors::Forbidden] if the secret does not match the application,
23
+ # or the authorization code does not belong to the application.
24
+ def get_by_credentials(client_id: nil, client_secret: nil, authorization_code: nil, **_ignored)
25
+ require_parameters authorization_code: authorization_code
26
+ application = Core.svc.applications.get_by_credentials(
27
+ client_id: client_id,
28
+ client_secret: client_secret
29
+ )
30
+ authorization = get_by_code(authorization_code: authorization_code)
31
+ raise mismatch_error if authorization.application.id.to_s != application.id.to_s
32
+
33
+ authorization
34
+ end
35
+
36
+ # Gets an authorization code by its corresponding value.
37
+ # @param authorization_code [String] the code value of the authorization object.
38
+ # @return [Core::Models::OAuth::Authorization] the authorization object.
39
+ # @raise [Core::Helpers::Errors::NotFound] if the authorization code is not found.
40
+ def get_by_code(authorization_code: nil, **_ignored)
41
+ require_parameters authorization_code: authorization_code
42
+ authorization = Core::Models::OAuth::Authorization.find_by(code: authorization_code)
43
+ raise unknown_err(field: 'authorization_code') if authorization.nil?
44
+
45
+ authorization
46
+ end
47
+
48
+ private
49
+
50
+ def mismatch_error
51
+ bad_request_err(field: 'client_id', error: 'mismatch')
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Core
4
+ module Services
5
+ class Base
6
+ # Raises an error if any parameter is nil, and nothing if all parameters are found.
7
+ # @raise [Core::Helpers::Errors::BadRequest] an error if any parameter is nil.
8
+ def require_parameters(**parameters)
9
+ parameters.keys.each do |key|
10
+ value = parameters[key]
11
+ raise bad_request_err(field: key.to_s, error: 'required') if value.nil?
12
+ end
13
+ end
14
+
15
+ def bad_request_err(field: nil, error: nil)
16
+ Core::Helpers::Errors::BadRequest.new(field: field, error: error)
17
+ end
18
+
19
+ def unknown_err(field: nil, error: 'unknown')
20
+ Core::Helpers::Errors::NotFound.new(field: field, error: error)
21
+ end
22
+
23
+ def forbidden_err(field: nil, error: 'forbidden')
24
+ Core::Helpers::Errors::Forbidden.new(field: field, error: error)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Core
2
4
  module Services
3
5
  class Campaigns
@@ -10,7 +12,7 @@ module Core
10
12
  # @param per_page [Integer] the number of campaigns per page.
11
13
  #
12
14
  # @return [Array<Hash>] an array of hash representing campaigns.
13
- def list(account, page: 0, per_page: 20, **ignored)
15
+ def list(account, page: 0, per_page: 20, **_ignored)
14
16
  campaigns = campaigns(account).skip(page * per_page).limit(per_page)
15
17
  campaigns.map do |campaign|
16
18
  Core::Decorators::Campaign.new(campaign).to_simple_h
@@ -23,4 +25,4 @@ module Core
23
25
  end
24
26
  end
25
27
  end
26
- end
28
+ end
@@ -1,17 +1,39 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Core
2
4
  module Services
3
5
  # The registry holds references to all the services accessible in the library. To access
4
- # all services and be able to manage resources easily, just instanciate the
6
+ # all services and be able to manage resources easily, just instanciate the
5
7
  class Registry
6
8
  include Singleton
7
9
 
8
- attr_reader :accounts, :sessions, :campaigns
10
+ # @!attribute [r] accounts
11
+ # @return [Core::Services::Accounts] the service managing accounts
12
+ attr_reader :accounts
13
+ # @!attribute [r] sessions
14
+ # @return [Core::Services::Sessions] the service managing sessions
15
+ attr_reader :sessions
16
+ # @!attribute [r] campaigns
17
+ # @return [Core::Services::Campaigns] the service managing campaigns
18
+ attr_reader :campaigns
19
+ # @!attribute [r] applications
20
+ # @return [Core::Services::Applications] the service managing applications
21
+ attr_reader :applications
22
+ # @!attribute [r] authorizations
23
+ # @return [Core::Services::Authorizations] the service managing authorizations
24
+ attr_reader :authorizations
25
+ # @!attribute [r] tokens
26
+ # @return [Core::Services::Tokens] the service managing OAuth access tokens
27
+ attr_reader :tokens
9
28
 
10
29
  def initialize
11
30
  @accounts = Core::Services::Accounts.instance
12
31
  @sessions = Core::Services::Sessions.instance
13
32
  @campaigns = Core::Services::Campaigns.instance
33
+ @applications = Core::Services::Applications.instance
34
+ @authorizations = Core::Services::Authorizations.instance
35
+ @tokens = Core::Services::Tokens.instance
14
36
  end
15
37
  end
16
38
  end
17
- end
39
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'bcrypt'
2
4
  require 'securerandom'
3
5
 
@@ -19,16 +21,14 @@ module Core
19
21
  def create(username, password)
20
22
  account = Core.svc.accounts.get_by_username(username)
21
23
  if BCrypt::Password.new(account.password_digest) != password
22
- raise Core::Helpers::Errors::Forbidden.new(
23
- field: 'password',
24
- error: 'wrong'
25
- )
24
+ raise Core::Helpers::Errors::Forbidden.new(field: 'password', error: 'wrong')
26
25
  end
27
- return Core::Models::Authentication::Session.create(
26
+
27
+ Core::Models::Authentication::Session.create(
28
28
  account: account,
29
29
  token: SecureRandom.uuid
30
30
  )
31
31
  end
32
32
  end
33
33
  end
34
- end
34
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Core
4
+ module Services
5
+ # Service handling every operations concerning access tokens. This should mainly be
6
+ # used in the authentication backend as we should be the only ones to manage tokens.
7
+ # @author Vincent Courtois <courtois.vincent@outlook.com>
8
+ class Tokens < Core::Services::Base
9
+ include Singleton
10
+
11
+ def create_from_authorization(client_id: nil, client_secret: nil, authorization_code: nil, **_ignored)
12
+ authorization = Core.svc.authorizations.get_by_credentials(
13
+ client_id: client_id,
14
+ client_secret: client_secret,
15
+ authorization_code: authorization_code
16
+ )
17
+ raise forbidden_err(field: 'authorization_code', error: 'used') if authorization.used?
18
+
19
+ created = Core::Models::OAuth::AccessToken.create(authorization: authorization)
20
+ Core::Decorators::Token.new(created)
21
+ end
22
+
23
+ # Refreshes the token for the next request the client wants to issue by re-creating it
24
+ # from the previous token to add it to the tokens chain.
25
+ #
26
+ def create_from_token(client_id: nil, client_secret: nil, token: nil, **_ignored)
27
+ token = get_by_value(token: token)
28
+ authorization = Core.svc.authorizations.get_by_credentials(
29
+ client_id: client_id,
30
+ client_secret: client_secret,
31
+ authorization_code: token.authorization.code
32
+ )
33
+ raise forbidden_err(field: 'token', error: 'used') unless token.generated.nil?
34
+
35
+ created = Core::Models::OAuth::AccessToken.create(
36
+ generator: token,
37
+ authorization: authorization
38
+ )
39
+ Core::Decorators::Token.new(created)
40
+ end
41
+
42
+ def get_by_value(token: nil, **_ignored)
43
+ require_parameters token: token
44
+ token = Core::Models::OAuth::AccessToken.find_by(value: token)
45
+ raise unknown_err(field: 'token') if token.nil?
46
+
47
+ Core::Decorators::Token.new(token)
48
+ end
49
+ end
50
+ end
51
+ end
data/lib/core/services.rb CHANGED
@@ -5,8 +5,12 @@ module Core
5
5
  # @author Vincent Courtois <courtois.vincent@outlook.com>
6
6
  module Services
7
7
  autoload :Accounts, 'core/services/accounts'
8
+ autoload :Applications, 'core/services/applications'
9
+ autoload :Authorizations, 'core/services/authorizations'
10
+ autoload :Base, 'core/services/base'
11
+ autoload :Campaigns, 'core/services/campaigns'
8
12
  autoload :Registry, 'core/services/registry'
9
13
  autoload :Sessions, 'core/services/sessions'
10
- autoload :Campaigns, 'core/services/campaigns'
14
+ autoload :Tokens, 'core/services/tokens'
11
15
  end
12
16
  end
data/lib/core/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Core
4
- VERSION = '1.5.0'
4
+ VERSION = '1.6.0.dev0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: virtuatable-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.0
4
+ version: 1.6.0.dev0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vincent Courtois
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-05-09 00:00:00.000000000 Z
11
+ date: 2022-05-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: database_cleaner
@@ -300,7 +300,9 @@ files:
300
300
  - lib/core/controllers.rb
301
301
  - lib/core/controllers/base.rb
302
302
  - lib/core/decorators.rb
303
+ - lib/core/decorators/base.rb
303
304
  - lib/core/decorators/campaign.rb
305
+ - lib/core/decorators/token.rb
304
306
  - lib/core/helpers.rb
305
307
  - lib/core/helpers/accounts.rb
306
308
  - lib/core/helpers/applications.rb
@@ -348,14 +350,17 @@ files:
348
350
  - lib/core/models/oauth/access_token.rb
349
351
  - lib/core/models/oauth/application.rb
350
352
  - lib/core/models/oauth/authorization.rb
351
- - lib/core/models/oauth/refresh_token.rb
352
353
  - lib/core/models/oauth/scope.rb
353
354
  - lib/core/models/ruleset.rb
354
355
  - lib/core/services.rb
355
356
  - lib/core/services/accounts.rb
357
+ - lib/core/services/applications.rb
358
+ - lib/core/services/authorizations.rb
359
+ - lib/core/services/base.rb
356
360
  - lib/core/services/campaigns.rb
357
361
  - lib/core/services/registry.rb
358
362
  - lib/core/services/sessions.rb
363
+ - lib/core/services/tokens.rb
359
364
  - lib/core/version.rb
360
365
  homepage: https://rubygems.org/gems/virtuatable-core
361
366
  licenses:
@@ -372,9 +377,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
372
377
  version: '0'
373
378
  required_rubygems_version: !ruby/object:Gem::Requirement
374
379
  requirements:
375
- - - ">="
380
+ - - ">"
376
381
  - !ruby/object:Gem::Version
377
- version: '0'
382
+ version: 1.3.1
378
383
  requirements: []
379
384
  rubygems_version: 3.2.3
380
385
  signing_key:
@@ -1,29 +0,0 @@
1
- module Core
2
- module Models
3
- module OAuth
4
- # A refresh token is used when an access token is expired, to get a new one. It is then recreated for the next expiration.
5
- # @author Vincent Courtois <courtois.vincent@outlook.com>
6
- class RefreshToken
7
- include Mongoid::Document
8
- include Mongoid::Timestamps
9
-
10
- store_in collection: 'oauth_refresh_tokens'
11
-
12
- # @!attribute [rw] value
13
- # @return [String] the value of the token, returned to the application when built.
14
- field :value, type: String, default: ->{ SecureRandom.hex }
15
- # @!attribute [rw] used_at
16
- # @return [DateTime] the date and time at which this refresh token has been useds to create a new access token.
17
- field :used_at, type: DateTime, default: nil
18
-
19
- # @!attribute [rw] authorization
20
- # @return [Core::Models::OAuth::Authorization] the authorization code that issued this token to the application for this user.
21
- belongs_to :token, class_name: 'Core::Models::OAuth::AccessToken', inverse_of: :refresh_token
22
-
23
- def used?
24
- !used_at.nil? && used_at < DateTime.now
25
- end
26
- end
27
- end
28
- end
29
- end