cnfs-cognito 0.0.1.alpha1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +28 -0
  4. data/Rakefile +24 -0
  5. data/app/controllers/chown_requests_controller.rb +4 -0
  6. data/app/controllers/chown_results_controller.rb +4 -0
  7. data/app/controllers/cognito/application_controller.rb +6 -0
  8. data/app/controllers/endpoints_controller.rb +4 -0
  9. data/app/controllers/identifiers_controller.rb +4 -0
  10. data/app/controllers/login_controller.rb +45 -0
  11. data/app/controllers/metabase_cards_controller.rb +4 -0
  12. data/app/controllers/metabase_token_controller.rb +46 -0
  13. data/app/controllers/pools_controller.rb +21 -0
  14. data/app/controllers/users_controller.rb +4 -0
  15. data/app/models/chown_request.rb +13 -0
  16. data/app/models/chown_result.rb +14 -0
  17. data/app/models/cognito/application_record.rb +7 -0
  18. data/app/models/endpoint.rb +6 -0
  19. data/app/models/identifier.rb +5 -0
  20. data/app/models/metabase_card.rb +6 -0
  21. data/app/models/platform_event_consumer.rb +8 -0
  22. data/app/models/pool.rb +6 -0
  23. data/app/models/tenant.rb +5 -0
  24. data/app/models/user.rb +50 -0
  25. data/app/models/user_pool.rb +6 -0
  26. data/app/operations/chown_request_process.rb +27 -0
  27. data/app/operations/pool_create.rb +65 -0
  28. data/app/operations/segments/age.rb +65 -0
  29. data/app/operations/segments/birthday.rb +49 -0
  30. data/app/operations/segments/except_ids.rb +28 -0
  31. data/app/operations/segments/gender.rb +35 -0
  32. data/app/operations/segments_apply.rb +40 -0
  33. data/app/policies/chown_request_policy.rb +4 -0
  34. data/app/policies/chown_result_policy.rb +4 -0
  35. data/app/policies/cognito/application_policy.rb +6 -0
  36. data/app/policies/endpoint_policy.rb +4 -0
  37. data/app/policies/identifier_policy.rb +4 -0
  38. data/app/policies/metabase_card_policy.rb +4 -0
  39. data/app/policies/pool_policy.rb +4 -0
  40. data/app/policies/tenant_policy.rb +5 -0
  41. data/app/policies/user_policy.rb +20 -0
  42. data/app/resources/chown_request_resource.rb +5 -0
  43. data/app/resources/chown_result_resource.rb +8 -0
  44. data/app/resources/cognito/application_resource.rb +7 -0
  45. data/app/resources/endpoint_resource.rb +49 -0
  46. data/app/resources/identifier_resource.rb +6 -0
  47. data/app/resources/metabase_card_resource.rb +5 -0
  48. data/app/resources/pool_resource.rb +30 -0
  49. data/app/resources/user_resource.rb +27 -0
  50. data/app/services/metabase/config.rb +16 -0
  51. data/app/services/metabase/token_generator.rb +36 -0
  52. data/config/environment.rb +0 -0
  53. data/config/routes.rb +14 -0
  54. data/config/sidekiq.yml +3 -0
  55. data/config/spring.rb +3 -0
  56. data/db/migrate/20190310013542_create_endpoints.rb +13 -0
  57. data/db/migrate/20190317072927_create_pools.rb +12 -0
  58. data/db/migrate/20190317073055_create_users.rb +18 -0
  59. data/db/migrate/20190317073139_create_identifiers.rb +14 -0
  60. data/db/migrate/20190317073328_create_user_pools.rb +12 -0
  61. data/db/migrate/20191105100356_create_chown_requests.rb +9 -0
  62. data/db/migrate/20191106023315_create_chown_results.rb +12 -0
  63. data/db/migrate/20191126011832_create_metabase_cards.rb +11 -0
  64. data/db/migrate/20191129055605_add_birthday_to_user.rb +5 -0
  65. data/db/migrate/20191205023159_add_gender_to_user.rb +5 -0
  66. data/db/migrate/20191210050613_add_system_generated_flag_to_pools.rb +5 -0
  67. data/db/seeds/development/data.seeds.rb +0 -0
  68. data/db/seeds/development/tenants.seeds.rb +7 -0
  69. data/db/seeds/development/users.seeds.rb +29 -0
  70. data/lib/ros/cognito.rb +16 -0
  71. data/lib/ros/cognito/engine.rb +53 -0
  72. data/lib/ros/cognito/version.rb +7 -0
  73. data/lib/tasks/ros/cognito_tasks.rake +16 -0
  74. metadata +162 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e2380c60fb607f73af8375a2c5d68f28ce9760e423bfdc52143931342696c29d
4
+ data.tar.gz: 1af8136e3a110ec001a98437257acdb3be0a2cadda59befa99f95992ccf6f609
5
+ SHA512:
6
+ metadata.gz: ee571566b474f16df473fe00029d019a29e30b0da691524199aec6c84db9b50b29c460402636838c7e59415bde774827adce192810c253c5c4aa887b5a6d715e
7
+ data.tar.gz: e1375e02d3f5f77b01eac33b6ecc89c34df931c9bc504dda3fe117dbe4ca5b3e9189f42b842031f5940d888c18cf1879ca36fe6379aa8ec844666e0081658564
@@ -0,0 +1,20 @@
1
+ Copyright 2019 Robert Roach
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,28 @@
1
+ # Ros::Cognito
2
+ Short description and motivation.
3
+
4
+ ## Usage
5
+ How to use my plugin.
6
+
7
+ ## Installation
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'ros-cognito'
12
+ ```
13
+
14
+ And then execute:
15
+ ```bash
16
+ $ bundle
17
+ ```
18
+
19
+ Or install it yourself as:
20
+ ```bash
21
+ $ gem install ros-cognito
22
+ ```
23
+
24
+ ## Contributing
25
+ Contribution directions go here.
26
+
27
+ ## License
28
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require 'bundler/setup'
5
+ rescue LoadError
6
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
7
+ end
8
+
9
+ require 'rdoc/task'
10
+
11
+ RDoc::Task.new(:rdoc) do |rdoc|
12
+ rdoc.rdoc_dir = 'rdoc'
13
+ rdoc.title = 'Ros::Cognito'
14
+ rdoc.options << '--line-numbers'
15
+ rdoc.rdoc_files.include('README.md')
16
+ rdoc.rdoc_files.include('lib/**/*.rb')
17
+ end
18
+
19
+ APP_RAKEFILE = File.expand_path('spec/dummy/Rakefile', __dir__)
20
+ load 'rails/tasks/engine.rake'
21
+
22
+ load 'rails/tasks/statistics.rake'
23
+
24
+ require 'bundler/gem_tasks'
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ChownRequestsController < Cognito::ApplicationController
4
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ChownResultsController < Cognito::ApplicationController
4
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cognito
4
+ class ApplicationController < ::ApplicationController
5
+ end
6
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class EndpointsController < Cognito::ApplicationController
4
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class IdentifiersController < Cognito::ApplicationController
4
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ class LoginController < ApplicationController
4
+ # TODO: Move payload to LoginController Spec
5
+ # {
6
+ # "data": {
7
+ # "type": "login",
8
+ # "attributes": {
9
+ # "primary_identifier": "f92a8503-7b33-42b0-939d-5f948d5422ac"
10
+ # }
11
+ # }
12
+ # }
13
+
14
+ def create
15
+ # Get the user from the payload
16
+ json = request.body.read
17
+ # TODO: use controller concern jsonapi_params
18
+ pi = JSON.parse(json)['data']['attributes']['primary_identifier']
19
+
20
+ unless (user = User.find_by(primary_identifier: pi))
21
+ # TODO: Create a controller concern method and use that to render
22
+ # It should take the status (401), code (:unauthorized) and the title as params
23
+ # title param if nil defaults to code.to_s.capitalize
24
+ render(status: :unauthorized, json: { errors: [{ status: '401', code: :unauthorized, title: 'Unauthorized' }] })
25
+ return
26
+ end
27
+
28
+ # Add the 'sub_cognito' claim to the JWT and set the header
29
+ current_jwt.add_claims('sub_cognito' => user.to_urn)
30
+ current_jwt.add_claims('act_cognito' => user)
31
+
32
+ # Render some body back to the client
33
+ # TODO: Use controller concern serialize_resource to render
34
+ render(status: :ok, json: { 'data': [{
35
+ 'type': 'login',
36
+ 'id': user.id,
37
+ 'attributes': {
38
+ 'jwt': current_jwt.encode
39
+ },
40
+ 'links': {
41
+ 'self': 'http://example.com/things/1'
42
+ }
43
+ }] })
44
+ end
45
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MetabaseCardsController < Cognito::ApplicationController
4
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MetabaseTokenController < Cognito::ApplicationController
4
+ DEFAULT_TYPE = 'question'
5
+
6
+ before_action :identify_resource, only: :show
7
+
8
+ def show
9
+ tokenizer = Metabase::TokenGenerator.new(payload: payload, expiry: params[:expiry])
10
+ if tokenizer.valid?
11
+ render json: { token: tokenizer.token }, root: :data
12
+ else
13
+ render json: { errors: tokenizer.errors.messages }, status: :unprocessable_entity
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def payload
20
+ type = params.delete(:type) || DEFAULT_TYPE
21
+
22
+ {
23
+ resource: { type => params[:id].to_i },
24
+ params: payload_params
25
+ }
26
+ end
27
+
28
+ def payload_params
29
+ options = { tenant: Apartment::Tenant.current }
30
+ return options if params[:params].blank?
31
+
32
+ options.merge(params[:params].to_unsafe_h.deep_symbolize_keys)
33
+ end
34
+
35
+ def identify_resource
36
+ return params if /^\d+$/.match?(params[:id])
37
+
38
+ card = MetabaseCard.find_by(identifier: params[:id])
39
+ if card.nil?
40
+ render json: { errors: 'Card not found' }, status: :not_found
41
+ return
42
+ end
43
+
44
+ params[:id] = card.card_id
45
+ end
46
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ class PoolsController < Cognito::ApplicationController
4
+ def create
5
+ res = PoolCreate.call(params: create_params, user: context[:user])
6
+ if res.success?
7
+ render json: json_resource(resource_class: PoolResource, record: res.model, context: context), status: :created
8
+ else
9
+ resource = ApplicationResource.new(res, nil)
10
+ handle_exceptions JSONAPI::Exceptions::ValidationErrors.new(resource)
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def create_params
17
+ # TODO: Think of the proper pattern for this
18
+ creatable_fields = PoolResource.creatable_fields(context) + [:base_pool_id, { segments: {} }]
19
+ jsonapi_params.permit(creatable_fields)
20
+ end
21
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class UsersController < Cognito::ApplicationController
4
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ChownRequest < Cognito::ApplicationRecord
4
+ has_many :chown_results
5
+
6
+ after_commit :create_chown_results, on: :create
7
+
8
+ private
9
+
10
+ def create_chown_results
11
+ ChownRequestProcess.call(id: id, from_ids: from_ids, to_id: to_id)
12
+ end
13
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ChownResult < Cognito::ApplicationRecord
4
+ belongs_to :chown_request
5
+
6
+ after_commit :spawn_chown_jobs, on: :create
7
+
8
+ private
9
+
10
+ def spawn_chown_jobs
11
+ Ros::ChownJob.set(queue: "#{service_name}_default")
12
+ .perform_later(from_id: from_id, to_id: to_id, chown_result_id: id)
13
+ end
14
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cognito
4
+ class ApplicationRecord < ::ApplicationRecord
5
+ self.abstract_class = true
6
+ end
7
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Endpoint < Cognito::ApplicationRecord
4
+ belongs_to_resource :target, polymorphic: true
5
+ # api_belongs_to :user, class_name: 'Ros::IAM::User'
6
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Identifier < Cognito::ApplicationRecord
4
+ belongs_to :user
5
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MetabaseCard < Cognito::ApplicationRecord
4
+ validates :card_id, presence: true, uniqueness: true
5
+ validates :identifier, presence: true, uniqueness: true
6
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ class PlatformEventConsumer
4
+ # Includes cross service event consumer functionality, e.g. 'process_document'
5
+ # include Ros::PlatformEventConsumerConcern
6
+
7
+ # Here include event consumer functionality specific to this service
8
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Pool < Cognito::ApplicationRecord
4
+ has_many :user_pools
5
+ has_many :users, through: :user_pools
6
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Tenant < Cognito::ApplicationRecord
4
+ include Ros::TenantConcern
5
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ class User < Cognito::ApplicationRecord
4
+ attribute :anonymous, :boolean, default: false
5
+
6
+ enum gender: { male: 'm', female: 'f', other: 'o' }
7
+
8
+ has_many :user_pools
9
+ has_many :pools, through: :user_pools
10
+
11
+ scope :birth_day, ->(date) { where("TO_CHAR(birthday, 'DD-MM') = TO_CHAR(DATE(?), 'DD-MM')", date) }
12
+ scope :birth_month, ->(date) { where("TO_CHAR(birthday, 'MM') = TO_CHAR(DATE(?), 'MM')", date) }
13
+
14
+ def self.reset
15
+ UserPool.delete_all
16
+ Pool.delete_all
17
+ User.delete_all
18
+ end
19
+
20
+ def self.file_fingerprint_attributes
21
+ column_names + %i[pool_name]
22
+ end
23
+
24
+ def self.load_document(file_name, column_map = nil, create = false)
25
+ processed_count = 0
26
+ column_map ||= default_headers
27
+ column_map = column_map.invert.symbolize_keys.invert
28
+ CSV.foreach(file_name, headers: true, header_converters: ->(name) { column_map[name] }) do |row|
29
+ if create
30
+ pool = Pool.find_or_create_by(name: row[:pool_name])
31
+ # row[:phone_number] = "+#{row[:phone_number]}"
32
+ row = row.to_h.except(:pool_name)
33
+ user = User.find_or_create_by(primary_identifier: row[:primary_identifier])
34
+ user.update(row.slice(:title, :last_name, :phone_number, :birthday, :gender))
35
+ processed_count += 1
36
+ pool.users << user
37
+ else
38
+ Rails.logger.debug "title: #{row[:title]} phone_number: #{row[:phone_number]} " \
39
+ "last_name: #{row[:last_name]} id: #{row[:primary_identifier]} pool: #{row[:pool_name]}"
40
+ end
41
+ end
42
+ processed_count
43
+ end
44
+
45
+ def self.default_headers
46
+ { 'Salutation' => :title, 'Last Name' => :last_name, 'Mobile' => :phone_number,
47
+ 'Unique Number' => :primary_identifier, 'Campaign Code' => :pool_name,
48
+ 'Birthday' => :birthday, 'Gender' => :gender }
49
+ end
50
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ class UserPool < ApplicationRecord
4
+ belongs_to :user
5
+ belongs_to :pool
6
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ChownRequestProcess < Ros::ActivityBase
4
+ # - {id} ChownRequest id
5
+ # - {from_ids} list of user ids to merge
6
+ # - {to_id} Final user id to receive all the data
7
+
8
+ # TODO: Needs improvement
9
+ # - Ensure that user id is confirmed while all the other users are not
10
+ # confirmed
11
+ # - Which permissions should this require?
12
+ # - For now, requesting user (identified via token), has to match the
13
+ # id passed in the params
14
+
15
+ step :create_chown_results
16
+
17
+ private
18
+
19
+ def create_chown_results(_ctx, id:, from_ids:, to_id:, **)
20
+ from_ids.each do |from_id|
21
+ %w[game instant-outcome voucher-service outcome].each do |service|
22
+ ChownResult.create(chown_request_id: id, service_name: service,
23
+ from_id: from_id, to_id: to_id, status: 'pending')
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ class PoolCreate < Ros::ActivityBase
4
+ step :check_permission
5
+ failed :not_permitted, Output(:success) => End(:failure)
6
+ step :should_be_segmented?, Output(:failure) => Id(:create_regular_pool), Output(:success) => Id(:find_base_pool)
7
+ step :create_regular_pool, Output(:success) => End(:success), Output(:failure) => End(:failure)
8
+
9
+ step :find_base_pool
10
+ failed :base_pool_not_found, Output(:success) => End(:failure)
11
+ step :apply_segment
12
+ failed :fail_to_apply_segment, Output(:success) => End(:failure)
13
+ step :create_pool
14
+ failed :pool_not_created, Output(:success) => End(:failure)
15
+ step :add_users_to_pool
16
+
17
+ def check_permission(_ctx, user:, **)
18
+ PoolPolicy.new(user, Pool.new).create?
19
+ end
20
+
21
+ def not_permitted(_ctx, errors:, **)
22
+ errors.add(:user, 'not permitted to create a pool')
23
+ end
24
+
25
+ def should_be_segmented?(_ctx, params:, **)
26
+ params.key?(:base_pool_id) && params.key?(:segments)
27
+ end
28
+
29
+ def create_regular_pool(ctx, params:, **)
30
+ ctx[:model] = Pool.create(params)
31
+ end
32
+
33
+ def find_base_pool(ctx, params:, **)
34
+ ctx[:base_pool] = Pool.find(params[:base_pool_id])
35
+ end
36
+
37
+ def base_pool_not_found(_ctx, errors:, params:, **)
38
+ errors.add(:base_pool, "Can't find base pool with id: #{params[:base_pool_id]}")
39
+ end
40
+
41
+ def apply_segment(ctx, base_pool:, params:, **)
42
+ segment_result = SegmentsApply.call(users: base_pool.users, segments: params[:segments])
43
+ return false unless segment_result.success?
44
+
45
+ ctx[:users] = segment_result.model
46
+ end
47
+
48
+ def fail_to_apply_segment(_ctx, errors:, **)
49
+ errors.add(:users, 'Failed to apply segment')
50
+ end
51
+
52
+ def create_pool(ctx, **)
53
+ ctx[:model] = Pool.create(name: "temporary_pool_#{SecureRandom.hex}", system_generated: true)
54
+ end
55
+
56
+ def pool_not_created(_ctx, errors:, **)
57
+ errors.add(:pool, "Can't create temporary pool")
58
+ end
59
+
60
+ def add_users_to_pool(_ctx, model:, users:, **)
61
+ return true if users.empty?
62
+
63
+ model.users << users
64
+ end
65
+ end