virtuatable-core 1.5.0 → 1.6.0.dev0

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