cnfs-iam 0.0.1.alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +75 -0
  4. data/Rakefile +24 -0
  5. data/app/controllers/concerns/is_tenant_scoped.rb +21 -0
  6. data/app/controllers/credentials_controller.rb +23 -0
  7. data/app/controllers/groups_controller.rb +4 -0
  8. data/app/controllers/iam/application_controller.rb +6 -0
  9. data/app/controllers/iam/confirmations_controller.rb +51 -0
  10. data/app/controllers/iam/passwords_controller.rb +56 -0
  11. data/app/controllers/iam/sessions_controller.rb +21 -0
  12. data/app/controllers/policies_controller.rb +4 -0
  13. data/app/controllers/public_keys_controller.rb +4 -0
  14. data/app/controllers/roots/sessions_controller.rb +16 -0
  15. data/app/controllers/roots_controller.rb +4 -0
  16. data/app/controllers/users/confirmations_controller.rb +21 -0
  17. data/app/controllers/users/passwords_controller.rb +25 -0
  18. data/app/controllers/users/sessions_controller.rb +19 -0
  19. data/app/controllers/users_controller.rb +17 -0
  20. data/app/mailers/account_mailer.rb +74 -0
  21. data/app/models/action.rb +6 -0
  22. data/app/models/credential.rb +47 -0
  23. data/app/models/group.rb +15 -0
  24. data/app/models/group_policy_join.rb +25 -0
  25. data/app/models/iam/application_record.rb +7 -0
  26. data/app/models/policy.rb +10 -0
  27. data/app/models/policy_action.rb +6 -0
  28. data/app/models/public_key.rb +17 -0
  29. data/app/models/role.rb +11 -0
  30. data/app/models/role_policy_join.rb +6 -0
  31. data/app/models/root.rb +26 -0
  32. data/app/models/root_credential.rb +7 -0
  33. data/app/models/tenant.rb +68 -0
  34. data/app/models/user.rb +69 -0
  35. data/app/models/user_credential.rb +8 -0
  36. data/app/models/user_group.rb +25 -0
  37. data/app/models/user_policy_join.rb +21 -0
  38. data/app/models/user_role.rb +6 -0
  39. data/app/operations/blackcomb_user_create.rb +49 -0
  40. data/app/operations/user_create.rb +53 -0
  41. data/app/policies/action_policy.rb +3 -0
  42. data/app/policies/credential_policy.rb +3 -0
  43. data/app/policies/group_policy.rb +3 -0
  44. data/app/policies/iam/application_policy.rb +6 -0
  45. data/app/policies/policy_policy.rb +3 -0
  46. data/app/policies/public_key_policy.rb +4 -0
  47. data/app/policies/root_policy.rb +3 -0
  48. data/app/policies/tenant_policy.rb +5 -0
  49. data/app/policies/user_policy.rb +33 -0
  50. data/app/resources/action_resource.rb +16 -0
  51. data/app/resources/credential_resource.rb +13 -0
  52. data/app/resources/group_resource.rb +8 -0
  53. data/app/resources/iam/application_resource.rb +7 -0
  54. data/app/resources/policy_resource.rb +9 -0
  55. data/app/resources/public_key_resource.rb +6 -0
  56. data/app/resources/root_resource.rb +14 -0
  57. data/app/resources/tenant_resource.rb +21 -0
  58. data/app/resources/user_resource.rb +25 -0
  59. data/app/views/layouts/mailer.html.erb +4 -0
  60. data/app/views/user_mailer/confirmation_instructions.html.erb +5 -0
  61. data/app/views/user_mailer/email_changed.html.erb +7 -0
  62. data/app/views/user_mailer/password_change.html.erb +3 -0
  63. data/app/views/user_mailer/reset_password_instructions.html.erb +106 -0
  64. data/app/views/user_mailer/team_welcome.html.erb +107 -0
  65. data/app/views/user_mailer/unlock_instructions.html.erb +7 -0
  66. data/config/environment.rb +0 -0
  67. data/config/initializers/devise.rb +311 -0
  68. data/config/locales/devise.en.yml +65 -0
  69. data/config/routes.rb +17 -0
  70. data/config/sidekiq.yml +5 -0
  71. data/config/spring.rb +3 -0
  72. data/db/migrate/20190101000001_create_policies.rb +11 -0
  73. data/db/migrate/20190101000002_create_actions.rb +13 -0
  74. data/db/migrate/20190101000003_create_policy_actions.rb +13 -0
  75. data/db/migrate/20190215214352_create_roots.rb +43 -0
  76. data/db/migrate/20190215214353_update_tenants.rb +10 -0
  77. data/db/migrate/20190215214355_create_credentials.rb +14 -0
  78. data/db/migrate/20190215214407_create_users.rb +50 -0
  79. data/db/migrate/20190215214409_create_user_credentials.rb +12 -0
  80. data/db/migrate/20190215214410_create_user_policy_joins.rb +12 -0
  81. data/db/migrate/20190215214411_create_groups.rb +11 -0
  82. data/db/migrate/20190215214412_create_user_groups.rb +12 -0
  83. data/db/migrate/20190215214413_create_group_policy_joins.rb +12 -0
  84. data/db/migrate/20190215214415_create_roles.rb +11 -0
  85. data/db/migrate/20190215214416_create_user_roles.rb +12 -0
  86. data/db/migrate/20190215214421_create_role_policy_joins.rb +12 -0
  87. data/db/migrate/20190924091536_add_display_properties_to_tenants.rb +5 -0
  88. data/db/migrate/20191021220135_create_public_keys.rb +10 -0
  89. data/db/migrate/20191120083154_add_confirmable_email_to_user.rb +9 -0
  90. data/db/seeds/development/tenants.seeds.rb +41 -0
  91. data/db/seeds/development/users.seeds.rb +67 -0
  92. data/lib/ros/api_token_strategy.rb +24 -0
  93. data/lib/ros/iam.rb +18 -0
  94. data/lib/ros/iam/console.rb +13 -0
  95. data/lib/ros/iam/engine.rb +51 -0
  96. data/lib/ros/iam/version.rb +7 -0
  97. data/lib/tasks/ros/iam_tasks.rake +51 -0
  98. metadata +209 -0
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AccountMailer < Devise::Mailer
4
+ default template_path: 'user_mailer',
5
+ from: Settings.smtp.from
6
+
7
+ layout 'mailer'
8
+
9
+ def confirmation_instructions(resource, devise_token, _opts = {})
10
+ @resource = resource
11
+ @devise_token = devise_token
12
+ @confirmation_url = user_confirmation_url
13
+
14
+ mail to: resource.email
15
+ end
16
+
17
+ def reset_password_instructions(resource, devise_token, _opts = {})
18
+ @resource = resource
19
+ @devise_token = devise_token
20
+ @reset_url = user_reset_password_url
21
+
22
+ mail to: resource.email
23
+ end
24
+
25
+ def team_welcome(resource, devise_token, _opts = {})
26
+ @resource = resource
27
+ @devise_token = devise_token
28
+ @account_name = Tenant.current_tenant&.alias
29
+ @reset_url = new_user_password_url
30
+
31
+ mail to: resource.email
32
+ end
33
+
34
+ private
35
+
36
+ def token
37
+ # the token here is whatever we get from devise which can be (at present) an
38
+ # account confirmation token or a password confirmation token
39
+ Ros::Jwt.new(token: @devise_token,
40
+ account_id: Tenant.current_tenant&.alias,
41
+ username: @resource.username).encode(:confirmation)
42
+ end
43
+
44
+ def user_confirmation_url
45
+ account_url :confirm
46
+ end
47
+
48
+ def user_reset_password_url
49
+ account_url :reset
50
+ end
51
+
52
+ def new_user_password_url
53
+ account_url :new
54
+ end
55
+
56
+ def account_url(kind)
57
+ base_url + "/password/#{kind}?#{token_name kind}=#{token}"
58
+ end
59
+
60
+ def token_name(kind)
61
+ case kind
62
+ when :confirm
63
+ 'confirmation_token'
64
+ when :reset, :new
65
+ 'reset_password_token'
66
+ else
67
+ raise ArgumentError, "Unknown token type: #{kind}"
68
+ end
69
+ end
70
+
71
+ def base_url
72
+ Tenant.current_tenant.properties['base_url'] || 'http://localhost:4200'
73
+ end
74
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Action < Iam::ApplicationRecord
4
+ end
5
+
6
+ # class ReadAction < Action; end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Credential < Iam::ApplicationRecord
4
+ # NOTE: to manually authenticate a password from the console
5
+ # credential.authenticate_secret_access_key(secret_access_key_plaintext)
6
+ # See: https://blog.bigbinary.com/2019/04/23/rails-6-allows-configurable-attribute-name-on-has_secure_password.html
7
+ has_secure_password :secret_access_key, validations: false
8
+
9
+ belongs_to :owner, polymorphic: true
10
+
11
+ before_validation :generate_values, on: :create
12
+
13
+ # validates :access_key_id, length: { is: 20 }
14
+
15
+ def self.access_key_id_to_schema_name(access_key_id)
16
+ Ros::AccessKey.decode(access_key_id)[:schema_name]
17
+ end
18
+
19
+ def self.validate(access_key_id, secret_access_key)
20
+ access_key = to_access_key(access_key_id)
21
+ Apartment::Tenant.switch(access_key[:schema_name]) do
22
+ return unless (credential = find_by(access_key_id: access_key_id))
23
+
24
+ credential.authenticate_secret_access_key(secret_access_key).try(:owner)
25
+ end
26
+ end
27
+
28
+ def generate_values
29
+ # TODO: Refactor to get from RequestStore
30
+ self.access_key_id = Ros::AccessKey.generate(owner)
31
+ self.secret_access_key = SecureRandom.urlsafe_base64(40)
32
+ end
33
+
34
+ def to_access_key
35
+ Ros.access_key(access_key_id)
36
+ end
37
+
38
+ def token
39
+ return unless secret_access_key # only available when the object is just created
40
+
41
+ "Basic #{access_key_id}:#{secret_access_key}"
42
+ end
43
+
44
+ def self.urn_id
45
+ :access_key_id
46
+ end
47
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Group < Iam::ApplicationRecord
4
+ has_many :group_policies, class_name: 'GroupPolicyJoin'
5
+ has_many :policies, through: :group_policies
6
+ has_many :actions, through: :policies
7
+
8
+ has_many :user_groups
9
+ has_many :users, through: :user_groups
10
+ has_many :user_actions, through: :users, source: :actions
11
+
12
+ def urn_id
13
+ "group/#{name}"
14
+ end
15
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ class GroupPolicyJoin < Iam::ApplicationRecord
4
+ belongs_to :group
5
+ belongs_to :policy
6
+
7
+ after_create :add_policy_to_users
8
+ after_destroy :remove_policy_from_users
9
+
10
+ def add_policy_to_users
11
+ group.users.each do |user|
12
+ user.attached_policies[policy.name] ||= 0
13
+ user.attached_policies[policy.name] += 1
14
+ user.save
15
+ end
16
+ end
17
+
18
+ def remove_policy_from_users
19
+ group.users.each do |user|
20
+ user.attached_policies[policy.name] -= 1
21
+ user.attached_policies.delete(policy.name) if user.attached_policies[policy.name].zero?
22
+ user.save
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Iam
4
+ class ApplicationRecord < ::ApplicationRecord
5
+ self.abstract_class = true
6
+ end
7
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Policy < Iam::ApplicationRecord
4
+ has_many :policy_actions
5
+ has_many :actions, through: :policy_actions
6
+
7
+ has_many :user_policy_joins
8
+ has_many :group_policy_joins
9
+ has_many :role_policy_joins
10
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ class PolicyAction < Iam::ApplicationRecord
4
+ belongs_to :policy
5
+ belongs_to :action
6
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ class PublicKey < Iam::ApplicationRecord
4
+ belongs_to :user
5
+
6
+ after_commit :enqueue
7
+
8
+ def enqueue
9
+ Ros::PlatformEventProcessJob.set(queue: job_queue).perform_later(job_payload.to_json)
10
+ end
11
+
12
+ def job_queue; 'storage_default' end
13
+
14
+ def job_payload
15
+ { operation: 'IamPublicKeyProcess', id: user.id }
16
+ end
17
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Role < Iam::ApplicationRecord
4
+ has_many :role_policies, class_name: 'RolePolicyJoin'
5
+ has_many :policies, through: :role_policies
6
+ has_many :actions, through: :policies
7
+
8
+ has_many :user_roles
9
+ has_many :users, through: :user_roles
10
+ has_many :user_actions, through: :users, source: :actions
11
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RolePolicyJoin < Iam::ApplicationRecord
4
+ belongs_to :role
5
+ belongs_to :policy
6
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Root < Iam::ApplicationRecord
4
+ has_one :tenant
5
+ has_many :credentials, as: :owner
6
+ # has_many :ssh_keys
7
+
8
+ def to_urn
9
+ "#{self.class.urn_base}:#{tenant.account_id}:root/#{id}"
10
+ end
11
+
12
+ # Include default devise modules. Others available are:
13
+ # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
14
+ devise :database_authenticatable, :registerable,
15
+ :recoverable, :rememberable, :validatable
16
+ # :jwt_authenticatable, # jwt_revocation_strategy: self
17
+ # jwt_revocation_strategy: Devise::JWT::RevocationStrategies::Null
18
+
19
+ def jwt_payload
20
+ @jwt_payload ||= { sub: to_urn, scope: '*' }
21
+ end
22
+
23
+ def current_tenant
24
+ tenant
25
+ end
26
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RootCredential < Credential
4
+ def current_tenant
5
+ Tenant.find(tenant_id)
6
+ end
7
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Tenant < Iam::ApplicationRecord
4
+ include Ros::TenantConcern
5
+
6
+ belongs_to :root
7
+
8
+ before_validation :generate_values, on: :create
9
+ validate :fixed_values_unchanged_x, if: :persisted?
10
+
11
+ # after_commit :create_service_tenants, on: :create
12
+
13
+ def enabled?
14
+ state.eql? 'active'
15
+ end
16
+
17
+ def create_service_tenants
18
+ Ros::Sdk.configured_services.each do |name, service|
19
+ next if name.eql? 'iam'
20
+
21
+ service::Tenant.create(schema_name: schema_name)
22
+ # TODO: Log a warning if any call fails
23
+ end
24
+ end
25
+
26
+ def fixed_values_unchanged_x
27
+ errors.add(:root_id, 'root_id cannot be changed') if root_id_changed?
28
+ end
29
+
30
+ def generate_values
31
+ self.state = 'active'
32
+ self.schema_name ||= rand(100_000_000..999_999_999).to_s.scan(/.{3}/).join('_')
33
+ end
34
+ end
35
+
36
+ # after_commit :seed_tenant, on: :create, unless: -> { Rails.env.production? }
37
+ # after_create :seed_tenant #, on: :create, unless: -> { Rails.env.production? }
38
+
39
+ # def seed_tenant
40
+ # Action.count
41
+ # Apartment::Tenant.switch(schema_name) do
42
+ # service = Service.create(name: 'User')
43
+ # action = ReadAction.create(service: service, name: 'DescribeUser')
44
+
45
+ # # Create a few Policies
46
+ # policy_admin = Policy.create(name: 'AdministratorAccess')
47
+ # policy_user_full = Policy.create(name: 'PerxUserFullAccess')
48
+ # policy_user_read_only = Policy.create(name: 'PerxUserReadOnlyAccess')
49
+
50
+ # # Attach the Action to the Admin Policy
51
+ # policy_admin.actions << action
52
+
53
+ # # Create a Group
54
+ # group_admin = Group.create(name: 'Administrators')
55
+
56
+ # # Attach the Admin Policy to the Group
57
+ # group_admin.policies << policy_admin
58
+
59
+ # # Create a User
60
+ # user_admin = User.create(email: 'admin@example.com', password: 'abc123za')
61
+
62
+ # # Assign the User to the Admin Group
63
+ # group_admin.users << user_admin
64
+
65
+ # # Role.create(name: 'PerxServiceRoleForIAM')
66
+ # # Role.create(name: 'PerxUserReadOnlyAccess')
67
+ # end
68
+ # end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ class User < Iam::ApplicationRecord
4
+ has_many :credentials, as: :owner
5
+ has_many :public_keys
6
+
7
+ has_many :user_policies, class_name: 'UserPolicyJoin'
8
+ has_many :policies, through: :user_policies
9
+ has_many :actions, through: :policies
10
+
11
+ has_many :user_groups, dependent: :delete_all
12
+ has_many :groups, through: :user_groups
13
+ has_many :group_policies, through: :groups, source: :policies
14
+ has_many :group_actions, through: :groups, source: :actions
15
+
16
+ has_many :user_roles
17
+ has_many :roles, through: :user_roles
18
+ has_many :role_policies, through: :roles, source: :policies
19
+ has_many :role_actions, through: :roles, source: :actions
20
+
21
+ # TODO: we need to support an empty username when an email is provided
22
+ validates :username, presence: true
23
+ validates :username, uniqueness: true
24
+ # store_accessor :permissions, :authorized_policies, :authorized_actions
25
+
26
+ # TODO: validate locales inclusion in list and time_zone in available time zones
27
+ # validates :locale, :time_zone, presence: true
28
+
29
+ def self.urn_id
30
+ :username
31
+ end
32
+
33
+ # We allow users being created without a password so they can choose one
34
+ # themselves
35
+ #
36
+ # Devise calls this
37
+ def password_required?
38
+ confirmed?
39
+ end
40
+
41
+ # Devise calls this
42
+ def password_update!(params)
43
+ update!(password: params[:password]) if params[:password] == params[:password_confirmation]
44
+ end
45
+
46
+ # Send an email using ActiveJob. This is used for password resets and
47
+ # when a new users needs to set his initial password
48
+ #
49
+ # Devise calls this
50
+ def send_devise_notification(notification, *args)
51
+ devise_mailer.send(notification, self, *args).deliver_later
52
+ end
53
+
54
+ # Include default devise modules. Others available are:
55
+ # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
56
+ devise :database_authenticatable,
57
+ :confirmable,
58
+ :recoverable,
59
+ # :registerable,
60
+ # :rememberable, :validatable,
61
+ # :jwt_authenticatable, # jwt_revocation_strategy: self
62
+ # authentication_keys: [:username],
63
+ authentication_keys: [:username]
64
+ # jwt_revocation_strategy: Devise::JWT::RevocationStrategies::Null
65
+
66
+ def jwt_payload
67
+ @jwt_payload ||= { sub: to_urn }
68
+ end
69
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ class UserCredential < Iam::ApplicationRecord
4
+ belongs_to :user
5
+ belongs_to :credential
6
+
7
+ before_validation :create_credential, unless: :credential
8
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ class UserGroup < Iam::ApplicationRecord
4
+ belongs_to :user
5
+ belongs_to :group
6
+
7
+ after_create :add_policies_to_user
8
+ after_destroy :remove_policies_from_user
9
+
10
+ def add_policies_to_user
11
+ group.policies.each do |policy|
12
+ user.attached_policies[policy.name] ||= 0
13
+ user.attached_policies[policy.name] += 1
14
+ end
15
+ user.save
16
+ end
17
+
18
+ def remove_policies_from_user
19
+ group.policies.each do |policy|
20
+ user.attached_policies[policy.name] -= 1
21
+ user.attached_policies.delete(policy.name) if user.attached_policies[policy.name].zero?
22
+ end
23
+ user.save
24
+ end
25
+ end