cnfs-iam 0.0.1.alpha

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