cnfs-iam 0.0.1.alpha
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +75 -0
- data/Rakefile +24 -0
- data/app/controllers/concerns/is_tenant_scoped.rb +21 -0
- data/app/controllers/credentials_controller.rb +23 -0
- data/app/controllers/groups_controller.rb +4 -0
- data/app/controllers/iam/application_controller.rb +6 -0
- data/app/controllers/iam/confirmations_controller.rb +51 -0
- data/app/controllers/iam/passwords_controller.rb +56 -0
- data/app/controllers/iam/sessions_controller.rb +21 -0
- data/app/controllers/policies_controller.rb +4 -0
- data/app/controllers/public_keys_controller.rb +4 -0
- data/app/controllers/roots/sessions_controller.rb +16 -0
- data/app/controllers/roots_controller.rb +4 -0
- data/app/controllers/users/confirmations_controller.rb +21 -0
- data/app/controllers/users/passwords_controller.rb +25 -0
- data/app/controllers/users/sessions_controller.rb +19 -0
- data/app/controllers/users_controller.rb +17 -0
- data/app/mailers/account_mailer.rb +74 -0
- data/app/models/action.rb +6 -0
- data/app/models/credential.rb +47 -0
- data/app/models/group.rb +15 -0
- data/app/models/group_policy_join.rb +25 -0
- data/app/models/iam/application_record.rb +7 -0
- data/app/models/policy.rb +10 -0
- data/app/models/policy_action.rb +6 -0
- data/app/models/public_key.rb +17 -0
- data/app/models/role.rb +11 -0
- data/app/models/role_policy_join.rb +6 -0
- data/app/models/root.rb +26 -0
- data/app/models/root_credential.rb +7 -0
- data/app/models/tenant.rb +68 -0
- data/app/models/user.rb +69 -0
- data/app/models/user_credential.rb +8 -0
- data/app/models/user_group.rb +25 -0
- data/app/models/user_policy_join.rb +21 -0
- data/app/models/user_role.rb +6 -0
- data/app/operations/blackcomb_user_create.rb +49 -0
- data/app/operations/user_create.rb +53 -0
- data/app/policies/action_policy.rb +3 -0
- data/app/policies/credential_policy.rb +3 -0
- data/app/policies/group_policy.rb +3 -0
- data/app/policies/iam/application_policy.rb +6 -0
- data/app/policies/policy_policy.rb +3 -0
- data/app/policies/public_key_policy.rb +4 -0
- data/app/policies/root_policy.rb +3 -0
- data/app/policies/tenant_policy.rb +5 -0
- data/app/policies/user_policy.rb +33 -0
- data/app/resources/action_resource.rb +16 -0
- data/app/resources/credential_resource.rb +13 -0
- data/app/resources/group_resource.rb +8 -0
- data/app/resources/iam/application_resource.rb +7 -0
- data/app/resources/policy_resource.rb +9 -0
- data/app/resources/public_key_resource.rb +6 -0
- data/app/resources/root_resource.rb +14 -0
- data/app/resources/tenant_resource.rb +21 -0
- data/app/resources/user_resource.rb +25 -0
- data/app/views/layouts/mailer.html.erb +4 -0
- data/app/views/user_mailer/confirmation_instructions.html.erb +5 -0
- data/app/views/user_mailer/email_changed.html.erb +7 -0
- data/app/views/user_mailer/password_change.html.erb +3 -0
- data/app/views/user_mailer/reset_password_instructions.html.erb +106 -0
- data/app/views/user_mailer/team_welcome.html.erb +107 -0
- data/app/views/user_mailer/unlock_instructions.html.erb +7 -0
- data/config/environment.rb +0 -0
- data/config/initializers/devise.rb +311 -0
- data/config/locales/devise.en.yml +65 -0
- data/config/routes.rb +17 -0
- data/config/sidekiq.yml +5 -0
- data/config/spring.rb +3 -0
- data/db/migrate/20190101000001_create_policies.rb +11 -0
- data/db/migrate/20190101000002_create_actions.rb +13 -0
- data/db/migrate/20190101000003_create_policy_actions.rb +13 -0
- data/db/migrate/20190215214352_create_roots.rb +43 -0
- data/db/migrate/20190215214353_update_tenants.rb +10 -0
- data/db/migrate/20190215214355_create_credentials.rb +14 -0
- data/db/migrate/20190215214407_create_users.rb +50 -0
- data/db/migrate/20190215214409_create_user_credentials.rb +12 -0
- data/db/migrate/20190215214410_create_user_policy_joins.rb +12 -0
- data/db/migrate/20190215214411_create_groups.rb +11 -0
- data/db/migrate/20190215214412_create_user_groups.rb +12 -0
- data/db/migrate/20190215214413_create_group_policy_joins.rb +12 -0
- data/db/migrate/20190215214415_create_roles.rb +11 -0
- data/db/migrate/20190215214416_create_user_roles.rb +12 -0
- data/db/migrate/20190215214421_create_role_policy_joins.rb +12 -0
- data/db/migrate/20190924091536_add_display_properties_to_tenants.rb +5 -0
- data/db/migrate/20191021220135_create_public_keys.rb +10 -0
- data/db/migrate/20191120083154_add_confirmable_email_to_user.rb +9 -0
- data/db/seeds/development/tenants.seeds.rb +41 -0
- data/db/seeds/development/users.seeds.rb +67 -0
- data/lib/ros/api_token_strategy.rb +24 -0
- data/lib/ros/iam.rb +18 -0
- data/lib/ros/iam/console.rb +13 -0
- data/lib/ros/iam/engine.rb +51 -0
- data/lib/ros/iam/version.rb +7 -0
- data/lib/tasks/ros/iam_tasks.rake +51 -0
- 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,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
|
data/app/models/group.rb
ADDED
@@ -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,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
|
data/app/models/role.rb
ADDED
@@ -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
|
data/app/models/root.rb
ADDED
@@ -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,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
|
data/app/models/user.rb
ADDED
@@ -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,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
|