virtuatable-core 1.3.1 → 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: 203433dce36ee2d214216cd18bdb69fcd4e42328a4527678ece54d96c8608383
4
- data.tar.gz: 5864fa51e5c3fb70385f0419485042a23dfd8af8e0de918bcae219026b7e26e8
3
+ metadata.gz: 4ebed77d032ef7111c0bde0982cdfc947a4a1933c1dc22e2cb09a3edeca12af1
4
+ data.tar.gz: '081072a04de88c52c94df498047f6cec83147fccf8c163e2c5856a9bde5398ac'
5
5
  SHA512:
6
- metadata.gz: e8c387a4883b7d4ff02120461895bcadcb4744e3b865326423ac24be062b60d80be5f05d0baac49860f958cc6479012e196e54b540a46d5382313e485570a9b8
7
- data.tar.gz: 3b6429e446fb7fc955999b9bdd4b861bfe889b8d17eb22cb7029318ae52dee81b2b368326ae6cd052e7b91951d1e16ba42313b08740530a76e9e404c18abf377
6
+ metadata.gz: db0b0b6d2343f36483293ce34e0e93e6d0cc9a803959f1f84caafaf338309984aaf6b226f33a363a612e4b3724c9b225dc2dfc97139a729d55dfeb2f6a9ef70f
7
+ data.tar.gz: 7fc28600dbd1fa9c0a2cab48fc8431e0778e599a07ab98af8d87d3e9f4dc1e34423d1bfe0daa3fffdaa1744be92097dbab7aa9f32a59961d2e913675ad08b73e
@@ -13,8 +13,9 @@ module Core
13
13
  # Includes the custom errors throwers and responses helpers.
14
14
  include Core::Helpers::Errors
15
15
  include Core::Helpers::Responses
16
- # Includes the checking methods for sessions.
17
- include Core::Helpers::Sessions
16
+ # Includes the checking methods for access tokens.
17
+ include Core::Helpers::Tokens
18
+ include Core::Helpers::Scopes
18
19
  # Include the checkers and getters for OAuth apps
19
20
  include Core::Helpers::Applications
20
21
  # Include checkers for field requirement and check
@@ -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,20 @@
1
+ module Core
2
+ module Decorators
3
+ class Campaign < Draper::Decorator
4
+ delegate_all
5
+
6
+ def to_simple_h
7
+ {
8
+ id: id.to_s,
9
+ title: title,
10
+ description: description,
11
+ tags: tags,
12
+ players: {
13
+ current: invitations.where(status: :accepted).count,
14
+ max: max_players
15
+ }
16
+ }
17
+ end
18
+ end
19
+ end
20
+ 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
@@ -0,0 +1,7 @@
1
+ module Core
2
+ module Decorators
3
+ autoload :Base, 'core/decorators/base'
4
+ autoload :Campaign, 'core/decorators/campaign'
5
+ autoload :Token, 'core/decorators/token'
6
+ end
7
+ end
@@ -10,8 +10,8 @@ module Core
10
10
  def account
11
11
  return @account unless @account.nil?
12
12
 
13
- session_id_required if !respond_to?(:session) || session.nil?
14
- @account = session.account
13
+ @account = token.authorization.account
14
+ @account
15
15
  end
16
16
 
17
17
  def account_id_not_found
@@ -6,9 +6,6 @@ module Core
6
6
  # to declare routes whithin a service, performing needed checks and filters.
7
7
  # @author Vincent Courtois <courtois.vincent@outlook.com>
8
8
  module Declarators
9
- # @!attribute [r] routes
10
- # @return [Array<Core::Models::Permissions::Route>] the currently declared routes.
11
- attr_reader :api_routes
12
9
 
13
10
  # Main method to declare new routes, persisting them in the database and
14
11
  # declaring it in the Sinatra application with the needed before checks.
@@ -16,68 +13,15 @@ module Core
16
13
  # @param verb [String] the HTTP method for the route.
17
14
  # @param path [String] the whole URI with parameters for the route.
18
15
  # @param options [Hash] the additional options for the route.
19
- def api_route(verb, path, options: {}, &block)
20
- options = default_options.merge(options)
21
- route = add_route(verb: verb, path: path, options: options)
22
-
23
- # TODO : do everything in the #send itself to avoid
24
- # route reload issues when premium is changed. It will
25
- # add some treatments but avoid many problems if route.premium
26
- send(route.verb, route.path) do
27
- application(premium: current_route.premium)
28
- session if current_route.authenticated
16
+ def api_route(verb, path, premium: false, scopes: [], &block)
17
+ send(verb, path) do
18
+ scope_objects = fetch_scopes(scopes + ['data::usage'])
19
+ appli = application(premium: premium)
20
+ check_app_scopes(appli, scope_objects)
21
+ check_token_scopes(token, scope_objects)
29
22
  instance_eval(&block)
30
23
  end
31
24
  end
32
-
33
- # Add a route to the database, then to the routes array.
34
- # @param verb [String] the HTTP method used to request this route.
35
- # @param path [String] the path used to request this route.
36
- # @return [Core::Models::Permissions::Route] the created route.
37
- def add_route(verb:, path:, options:)
38
- route = Core::Models::Permissions::Route.find_or_create_by!(
39
- path: path,
40
- verb: verb.downcase,
41
- premium: options[:premium],
42
- authenticated: options[:authenticated]
43
- )
44
- api_routes.nil? ? @api_routes = [route] : push_route(route)
45
- add_permissions(route)
46
- route
47
- end
48
-
49
- # Pushes the route in the api routes list, by creating it if needed
50
- # @param route [Core::Models::Permissions::Route] the route to push in the list of routes.
51
- def push_route(route)
52
- @api_routes << route if api_routes.none? do |tmp_route|
53
- route.id == tmp_route.id
54
- end
55
- end
56
-
57
- # Add the default access permissions to a route. Any group tagged superuser
58
- # can automatically access any newly declared_route.
59
- # params route [Core::Models::Permissions::Route] the route to add the permissions to.
60
- def add_permissions(route)
61
- groups = Core::Models::Permissions::Group.where(is_superuser: true)
62
- groups.each do |group|
63
- unless route.groups.where(id: group.id).exists?
64
- route.groups << group
65
- route.save!
66
- end
67
- end
68
- end
69
-
70
- # The default options for a route, being the most used value for each key.
71
- # @return [Hash] the default options as a hash.
72
- def default_options
73
- {
74
- # If TRUE the application MUST be premium to access the route.
75
- # Mainly used to protect administration routes against illegal accesses.
76
- premium: false,
77
- # If TRUE the user MUST be authenticated to access the route.
78
- authenticated: true
79
- }
80
- end
81
25
  end
82
26
  end
83
27
  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
@@ -0,0 +1,22 @@
1
+ module Core
2
+ module Helpers
3
+ module Scopes
4
+
5
+ def fetch_scopes(names)
6
+ (names.map { |n| Core::Models::OAuth::Scope.find_by(name: n) }).select { |s| !s.nil? }
7
+ end
8
+
9
+ def check_token_scopes(token, scopes)
10
+ scopes.each do |scope|
11
+ api_forbidden 'scope.forbidden' if !token.scopes.include? scope
12
+ end
13
+ end
14
+
15
+ def check_app_scopes(application, scopes)
16
+ scopes.each do |scope|
17
+ api_forbidden 'scope.forbidden' if !application.scopes.include? scope
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Core
4
+ module Helpers
5
+ # This helper aims at providing vanity methods concerning OAuth tokens.
6
+ # @author Vincent Courtois <courtois.vincent@outlook.com>
7
+ module Tokens
8
+ # Returns the database object representing the current OAuth token, or
9
+ # raises an error if the token seems to be invalid for any reason.
10
+ # @return [Core::Models::Oauth::AccessToken] the token if everything went well.
11
+ # @raise [Core::Helpers::Errors::BadRequest] if the token is not given.
12
+ # @raise [Core::Helpers::Errors::NotFound] if the token is not found in the
13
+ # database searching for the value passed as parameter.
14
+ # @raise [Core::Helpers::Errors::Forbidden] if the token belongs to another
15
+ # application.
16
+ def token
17
+ return @token unless @token.nil?
18
+
19
+ check_presence 'token'
20
+ @token = Core::Models::OAuth::AccessToken.find_by(value: params['token'])
21
+ api_not_found 'token.unknown' if @token.nil?
22
+ token_app_id = token.authorization.application.id.to_s
23
+ api_forbidden 'token.mismatch' if token_app_id != application.id.to_s
24
+ @token
25
+ end
26
+ end
27
+ end
28
+ end
data/lib/core/helpers.rb CHANGED
@@ -13,6 +13,7 @@ module Core
13
13
  autoload :Parameters, 'core/helpers/parameters'
14
14
  autoload :Responses, 'core/helpers/responses'
15
15
  autoload :Routes, 'core/helpers/routes'
16
- autoload :Sessions, 'core/helpers/sessions'
16
+ autoload :Scopes, 'core/helpers/scopes'
17
+ autoload :Tokens, 'core/helpers/tokens'
17
18
  end
18
19
  end
@@ -37,10 +37,6 @@ module Core
37
37
  # @!attribute [w] password_confirmation
38
38
  # @return [String] the confirmation of the password, do not get, just set it ; it must be the same as the password.
39
39
  has_secure_password validations: false
40
-
41
- # @!attribute [rw] groups
42
- # @return [Array<Core::Models::Permissions::Group>] the groups giving their corresponding rights to the current account.
43
- has_and_belongs_to_many :groups, class_name: 'Core::Models::Permissions::Group', inverse_of: :accounts
44
40
 
45
41
  # @!attribute [rw] applications
46
42
  # @return [Array<Core::Models::OAuth::Application] the applications this user has created and owns.
@@ -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
data/lib/core/models.rb CHANGED
@@ -16,7 +16,6 @@ module Core
16
16
  autoload :Files, 'core/models/files'
17
17
  autoload :Notification, 'core/models/notification'
18
18
  autoload :OAuth, 'core/models/oauth'
19
- autoload :Permissions, 'core/models/permissions'
20
19
  autoload :Ruleset, 'core/models/ruleset'
21
20
  end
22
21
  end
@@ -1,6 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Core
2
4
  module Services
3
- class Accounts < Core::Services::Base
5
+ class Accounts
6
+ include Singleton
7
+
4
8
  def get_by_username(username)
5
9
  account = Core::Models::Account.find_by(username: username)
6
10
  if account.nil?
@@ -13,4 +17,4 @@ module Core
13
17
  end
14
18
  end
15
19
  end
16
- 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
@@ -1,11 +1,28 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Core
2
4
  module Services
3
5
  class Base
4
- attr_reader :services
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
5
22
 
6
- def initialize(registry)
7
- @services = registry
23
+ def forbidden_err(field: nil, error: 'forbidden')
24
+ Core::Helpers::Errors::Forbidden.new(field: field, error: error)
8
25
  end
9
26
  end
10
27
  end
11
- end
28
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Core
4
+ module Services
5
+ class Campaigns
6
+ include Singleton
7
+
8
+ # Lists all the campaigns of a user identified by its account.
9
+ #
10
+ # @param account [Core::Models::Account] the user requesting its campaigns.
11
+ # @param page [Integer] the page in the list of campaigns to return to the users.
12
+ # @param per_page [Integer] the number of campaigns per page.
13
+ #
14
+ # @return [Array<Hash>] an array of hash representing campaigns.
15
+ def list(account, page: 0, per_page: 20, **_ignored)
16
+ campaigns = campaigns(account).skip(page * per_page).limit(per_page)
17
+ campaigns.map do |campaign|
18
+ Core::Decorators::Campaign.new(campaign).to_simple_h
19
+ end
20
+ end
21
+
22
+ def campaigns(account)
23
+ invitations = account.invitations.where(enum_status: 'creator')
24
+ Core::Models::Campaign.where(:id.in => invitations.map(&:campaign_id))
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,15 +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
8
+ include Singleton
6
9
 
7
- attr_reader :accounts, :sessions
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
8
28
 
9
29
  def initialize
10
- @accounts = Core::Services::Accounts.new(self)
11
- @sessions = Core::Services::Sessions.new(self)
30
+ @accounts = Core::Services::Accounts.instance
31
+ @sessions = Core::Services::Sessions.instance
32
+ @campaigns = Core::Services::Campaigns.instance
33
+ @applications = Core::Services::Applications.instance
34
+ @authorizations = Core::Services::Authorizations.instance
35
+ @tokens = Core::Services::Tokens.instance
12
36
  end
13
37
  end
14
38
  end
15
- end
39
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'bcrypt'
2
4
  require 'securerandom'
3
5
 
@@ -5,7 +7,8 @@ module Core
5
7
  module Services
6
8
  # Service concerning sessions (log in and log out)
7
9
  # @author Vincent Courtois <courtois.vincent@outlook.com>
8
- class Sessions < Core::Services::Base
10
+ class Sessions
11
+ include Singleton
9
12
  # Creates a new session from the given user credentials. IT will
10
13
  # * check that the user exists in the database
11
14
  # * check that the password matches the user encrypted password
@@ -16,18 +19,16 @@ module Core
16
19
  # @param password [string] the password the user has provided
17
20
  # @return [Core::Models::Authentication::Session] the login session
18
21
  def create(username, password)
19
- account = services.accounts.get_by_username(username)
22
+ account = Core.svc.accounts.get_by_username(username)
20
23
  if BCrypt::Password.new(account.password_digest) != password
21
- raise Core::Helpers::Errors::Forbidden.new(
22
- field: 'password',
23
- error: 'wrong'
24
- )
24
+ raise Core::Helpers::Errors::Forbidden.new(field: 'password', error: 'wrong')
25
25
  end
26
- return Core::Models::Authentication::Session.create(
26
+
27
+ Core::Models::Authentication::Session.create(
27
28
  account: account,
28
29
  token: SecureRandom.uuid
29
30
  )
30
31
  end
31
32
  end
32
33
  end
33
- 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'
8
10
  autoload :Base, 'core/services/base'
11
+ autoload :Campaigns, 'core/services/campaigns'
9
12
  autoload :Registry, 'core/services/registry'
10
13
  autoload :Sessions, 'core/services/sessions'
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.3.1'
4
+ VERSION = '1.6.0.dev0'
5
5
  end
data/lib/core.rb CHANGED
@@ -1,12 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- %w[active_model mongoid active_support].each { |g| require g }
3
+ %w[active_model mongoid active_support draper].each { |g| require g }
4
4
 
5
5
  # Main module of the application, holding all the subsequent classes.
6
6
  # @author Vincent Courtois <courtois.vincent@outlook.com>
7
7
  module Core
8
8
  autoload :Controllers, 'core/controllers'
9
+ autoload :Decorators, 'core/decorators'
9
10
  autoload :Helpers, 'core/helpers'
10
11
  autoload :Models, 'core/models'
11
12
  autoload :Services, 'core/services'
13
+
14
+ # Returns the registry of services for easier access to each of them.
15
+ def self.svc
16
+ Core::Services::Registry.instance
17
+ end
12
18
  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.3.1
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-04-29 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
@@ -276,6 +276,20 @@ dependencies:
276
276
  - - '='
277
277
  - !ruby/object:Gem::Version
278
278
  version: 2.1.0
279
+ - !ruby/object:Gem::Dependency
280
+ name: draper
281
+ requirement: !ruby/object:Gem::Requirement
282
+ requirements:
283
+ - - ">="
284
+ - !ruby/object:Gem::Version
285
+ version: '0'
286
+ type: :runtime
287
+ prerelease: false
288
+ version_requirements: !ruby/object:Gem::Requirement
289
+ requirements:
290
+ - - ">="
291
+ - !ruby/object:Gem::Version
292
+ version: '0'
279
293
  description: This gem holds the model layer for my table-top RPG games application.
280
294
  email: courtois.vincent@outlook.com
281
295
  executables: []
@@ -285,6 +299,10 @@ files:
285
299
  - lib/core.rb
286
300
  - lib/core/controllers.rb
287
301
  - lib/core/controllers/base.rb
302
+ - lib/core/decorators.rb
303
+ - lib/core/decorators/base.rb
304
+ - lib/core/decorators/campaign.rb
305
+ - lib/core/decorators/token.rb
288
306
  - lib/core/helpers.rb
289
307
  - lib/core/helpers/accounts.rb
290
308
  - lib/core/helpers/applications.rb
@@ -298,7 +316,8 @@ files:
298
316
  - lib/core/helpers/parameters.rb
299
317
  - lib/core/helpers/responses.rb
300
318
  - lib/core/helpers/routes.rb
301
- - lib/core/helpers/sessions.rb
319
+ - lib/core/helpers/scopes.rb
320
+ - lib/core/helpers/tokens.rb
302
321
  - lib/core/models.rb
303
322
  - lib/core/models/account.rb
304
323
  - lib/core/models/authentication.rb
@@ -331,19 +350,17 @@ files:
331
350
  - lib/core/models/oauth/access_token.rb
332
351
  - lib/core/models/oauth/application.rb
333
352
  - lib/core/models/oauth/authorization.rb
334
- - lib/core/models/oauth/refresh_token.rb
335
353
  - lib/core/models/oauth/scope.rb
336
- - lib/core/models/permissions.rb
337
- - lib/core/models/permissions/category.rb
338
- - lib/core/models/permissions/group.rb
339
- - lib/core/models/permissions/right.rb
340
- - lib/core/models/permissions/route.rb
341
354
  - lib/core/models/ruleset.rb
342
355
  - lib/core/services.rb
343
356
  - lib/core/services/accounts.rb
357
+ - lib/core/services/applications.rb
358
+ - lib/core/services/authorizations.rb
344
359
  - lib/core/services/base.rb
360
+ - lib/core/services/campaigns.rb
345
361
  - lib/core/services/registry.rb
346
362
  - lib/core/services/sessions.rb
363
+ - lib/core/services/tokens.rb
347
364
  - lib/core/version.rb
348
365
  homepage: https://rubygems.org/gems/virtuatable-core
349
366
  licenses:
@@ -360,9 +377,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
360
377
  version: '0'
361
378
  required_rubygems_version: !ruby/object:Gem::Requirement
362
379
  requirements:
363
- - - ">="
380
+ - - ">"
364
381
  - !ruby/object:Gem::Version
365
- version: '0'
382
+ version: 1.3.1
366
383
  requirements: []
367
384
  rubygems_version: 3.2.3
368
385
  signing_key:
@@ -1,30 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Core
4
- module Helpers
5
- # This helper gives access to methods about user's session on the API.
6
- # @author Vincent Courtois <courtois.vincent@outlook.com>
7
- module Sessions
8
- # Checks the session of the user requesting the API and returns an error
9
- # if it either not exists with the given token, or the token is not given.
10
- #
11
- # @raise [Virtuatable::API::Errors::NotFound] if the session is not found
12
- # or the token not given in the parameters of the request.
13
- # @raise [Virtuatable::API::Errors::BadRequest] if the session token is
14
- # not correctly given in the parameters.
15
- #
16
- # @return [Core::Models::Authentication::Session] the current session of the user.
17
- def session
18
- return @session unless @session.nil?
19
-
20
- check_presence 'session_id'
21
- @session = session_model.find_by(token: params['session_id'])
22
- @session.nil? ? api_not_found('session_id.unknown') : @session
23
- end
24
-
25
- def session_model
26
- Core::Models::Authentication::Session
27
- end
28
- end
29
- end
30
- end
@@ -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
@@ -1,17 +0,0 @@
1
- module Core
2
- module Models
3
- module Permissions
4
- # A category of rights regroups one or several rights for convenience purposes.
5
- # @author Vincent Courtois <courtois.vincent@outlook.com>
6
- class Category
7
- include Mongoid::Document
8
- include Mongoid::Timestamps
9
- include Core::Models::Concerns::Sluggable
10
-
11
- store_in collection: 'categories'
12
-
13
- has_many :rights, class_name: 'Core::Models::Permissions::Right', inverse_of: :category
14
- end
15
- end
16
- end
17
- end
@@ -1,32 +0,0 @@
1
- module Core
2
- module Models
3
- module Permissions
4
- # A group gathers one or several users to give them the same rights for conviniency purposes.
5
- # @author Vincent Courtois <courtois.vincent@outlook.com>
6
- class Group
7
- include Mongoid::Document
8
- include Mongoid::Timestamps
9
- include Core::Models::Concerns::Sluggable
10
-
11
- store_in collection: 'groups'
12
-
13
- # @!attribute [rw] is_default
14
- # @return [Boolean] a boolean indicating whether this group is given when a new user registered or not.
15
- field :is_default, type: Mongoid::Boolean, default: false
16
- # @!attribute [rw] is_superuser
17
- # @return [Boolean] a boolean indicating whether this group should have access to all groups and rights or not.
18
- field :is_superuser, type: Mongoid::Boolean, default: false
19
-
20
- # @!attribute [rw] accounts
21
- # @return [Array<Core::Models::Account>] the accounts having the rights granted by this group.
22
- has_and_belongs_to_many :accounts, class_name: 'Core::Models::Account', inverse_of: :groups
23
- # @!attribute [rw] rights
24
- # @return [Array<Core::Models::Permissions::Right>] the rights granted by belonging to this group.
25
- has_and_belongs_to_many :rights, class_name: 'Core::Models::Permissions::Right', inverse_of: :groups
26
- # @!attribute [rw] routes
27
- # @return [Array<Core::Models::Monitoring::Route>] the routes this group can access in the API.
28
- has_and_belongs_to_many :routes, class_name: 'Core::Models::Permissions::Route', inverse_of: :groups
29
- end
30
- end
31
- end
32
- end
@@ -1,21 +0,0 @@
1
- module Core
2
- module Models
3
- module Permissions
4
- # A right is the access to one or several features in the application. It's applied to a group, and transitively to an account.
5
- # @author Vincent Courtois <courtois;vincent@outlook.com>
6
- class Right
7
- include Mongoid::Document
8
- include Mongoid::Timestamps
9
- include Core::Models::Concerns::Sluggable
10
-
11
- store_in collection: 'rights'
12
-
13
- # @!attribute [rw] groups
14
- # @return [Array<Core::Models::Permissions::Group>] the groups granted with the permission to access features opened by this right.
15
- has_and_belongs_to_many :groups, class_name: 'Core::Models::Permissions::Group', inverse_of: :rights
16
-
17
- belongs_to :category, class_name: 'Core::Models::Permissions::Category', inverse_of: :rights
18
- end
19
- end
20
- end
21
- end
@@ -1,35 +0,0 @@
1
- module Core
2
- module Models
3
- module Permissions
4
- # A route is an endpoint accessible in the API. Each route has to have an associated endpoint in the deployed instances.
5
- # @param Vincent Courtois <courtois.vincent@outlook.com>
6
- class Route
7
- include Mongoid::Document
8
- include Mongoid::Timestamps
9
- include Core::Models::Concerns::Premiumable
10
- include Core::Models::Concerns::Activable
11
-
12
- store_in collection: 'routes'
13
-
14
- # @!attribute [rw] path
15
- # @return [String] the path (URI) of the route in the API.
16
- field :path, type: String, default: '/'
17
- # @!attribute [rw] verb
18
- # @return [String] the verb (HTTP method) of this route in the API.
19
- field :verb, type: String, default: 'get'
20
- # @!attribute [rw] authenticated
21
- # @return [Boolean] if true, the session_id is needed for this route, if false it is not.
22
- field :authenticated, type: Mongoid::Boolean, default: true
23
- # @!attribute [rw] groups
24
- # @return [Array<Core::Models::Permissions::Group>] the groups having permission to access this route.
25
- has_and_belongs_to_many :groups, class_name: 'Core::Models::Permissions::Group', inverse_of: :groups
26
-
27
- validates :path,
28
- format: {with: /\A(\/|((\/:?[a-zA-Z0-9_]+)+))\z/, message: 'pattern', if: :path?}
29
-
30
- validates :verb,
31
- inclusion: {message: 'unknown', in: ['get', 'post', 'put', 'delete', 'patch', 'option']}
32
- end
33
- end
34
- end
35
- end
@@ -1,13 +0,0 @@
1
- module Core
2
- module Models
3
- # This module holds the logic for all the classes concerning the permissions abd rights for the user.
4
- # A permission is restricting the access to one or several features to the users having it.
5
- # @author Vincent Courtois <courtois.vincent@outlook.com>
6
- module Permissions
7
- autoload :Right , 'core/models/permissions/right'
8
- autoload :Group , 'core/models/permissions/group'
9
- autoload :Category, 'core/models/permissions/category'
10
- autoload :Route , 'core/models/permissions/route'
11
- end
12
- end
13
- end