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.
- 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
|