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,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ class UserPolicyJoin < ApplicationRecord
4
+ belongs_to :user
5
+ belongs_to :policy
6
+
7
+ after_create :add_policy_to_user
8
+ after_destroy :remove_policy_from_user
9
+
10
+ def add_policy_to_user
11
+ user.attached_policies[policy.name] ||= 0
12
+ user.attached_policies[policy.name] += 1
13
+ user.save
14
+ end
15
+
16
+ def remove_policy_from_user
17
+ user.attached_policies[policy.name] -= 1
18
+ user.attached_policies.delete(policy.name) if user.attached_policies[policy.name].zero?
19
+ user.save
20
+ end
21
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ class UserRole < Iam::ApplicationRecord
4
+ belongs_to :user
5
+ belongs_to :role
6
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ class BlackcombUserCreate < Ros::ActivityBase
6
+ step :valdidate_root_owner
7
+ failed :invalid_user, Output(:success) => End(:failure)
8
+ step :switch_tenant
9
+ failed :invalid_schema, Output(:success) => End(:failure)
10
+ step :create_or_find_blackcomb_user
11
+ failed :failed_to_create_user
12
+
13
+ private
14
+
15
+ def valdidate_root_owner(_ctx, params:, **)
16
+ Apartment::Tenant.current == 'public' && params[:current_user].root?
17
+ end
18
+
19
+ def invalid_user(_ctx, errors:, **)
20
+ errors.add(:user, 'Not a owner root')
21
+ end
22
+
23
+ def switch_tenant(_ctx, params:, **)
24
+ tenant = Tenant.find_by_schema_or_alias(params[:account_id])
25
+ return false unless tenant
26
+
27
+ tenant.switch!
28
+ true
29
+ end
30
+
31
+ def invalid_schema(_ctx, errors:, **)
32
+ errors.add(:account_id, 'Invalid account id')
33
+ end
34
+
35
+ def create_or_find_blackcomb_user(ctx, **)
36
+ ctx[:model] = User.find_or_initialize_by(username: 'blackcomb')
37
+
38
+ return true if ctx[:model].persisted?
39
+
40
+ password = SecureRandom.hex
41
+ ctx[:model].update(password: password, password_confirmation: password,
42
+ confirmed_at: Time.zone.today, attached_policies: { AdministratorAccess: 1 })
43
+ ctx[:model].save
44
+ end
45
+
46
+ def failed_to_create_user(ctx, model:, **)
47
+ ctx[:errors] = model.errors
48
+ end
49
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ class UserCreate < Ros::ActivityBase
4
+ step :check_permission
5
+ failed :not_permitted, Output(:success) => End(:failure)
6
+ step :init
7
+ step :initialize_user
8
+ step :skip_confirmation_notification
9
+ step :generate_reset_passowrd_token
10
+ step :save__model, Output(:failure) => End(:failure)
11
+ step :create_relationships
12
+ step :send_welcome_email
13
+
14
+ private
15
+
16
+ def check_permission(_ctx, user:, **)
17
+ UserPolicy.new(user, User.new).create?
18
+ end
19
+
20
+ def not_permitted(_ctx, errors:, **)
21
+ errors.add(:user, 'not permitted to create a user')
22
+ end
23
+
24
+ def init(ctx, params:, **)
25
+ ctx[:relationships] = params.delete(:relationships)
26
+ true
27
+ end
28
+
29
+ def initialize_user(ctx, params:, **)
30
+ ctx[:model] = User.new(params)
31
+ end
32
+
33
+ def skip_confirmation_notification(_ctx, model:, **)
34
+ model.skip_confirmation_notification!
35
+ end
36
+
37
+ def generate_reset_passowrd_token(ctx, model:, **)
38
+ ctx[:reset_password_token], enc = Devise.token_generator.generate(model.class, :reset_password_token)
39
+
40
+ model.reset_password_token = enc
41
+ model.reset_password_sent_at = Time.now.utc
42
+ end
43
+
44
+ def create_relationships(_ctx, model:, relationships:, **)
45
+ return true if relationships&.dig(:groups, :data).blank?
46
+
47
+ model.groups << Group.where(id: relationships[:groups][:data].pluck(:id)).all
48
+ end
49
+
50
+ def send_welcome_email(_ctx, model:, reset_password_token:, **)
51
+ AccountMailer.team_welcome(model, reset_password_token).deliver_later
52
+ end
53
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ActionPolicy < Iam::ApplicationPolicy; end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CredentialPolicy < Iam::ApplicationPolicy; end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ class GroupPolicy < Iam::ApplicationPolicy; end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Iam
4
+ class ApplicationPolicy < ::ApplicationPolicy
5
+ end
6
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ class PolicyPolicy < Iam::ApplicationPolicy; end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class PublicKeyPolicy < Iam::ApplicationPolicy
4
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RootPolicy < Iam::ApplicationPolicy; end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class TenantPolicy < Iam::ApplicationPolicy
4
+ include Ros::TenantPolicyConcern
5
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ class UserPolicy < Iam::ApplicationPolicy
4
+ # def index?
5
+ # user.action_permitted?(requested_action(__method__))
6
+ # # user.admin? || !record.published?
7
+ # end
8
+
9
+ # def self.zactions
10
+ # {
11
+ # Write: [
12
+ # 'AddUserToGroup'
13
+ # ]
14
+ # }
15
+ # end
16
+
17
+ # def update?
18
+ # super
19
+ # # does current_user have permission 'UpdateUser'
20
+ # end
21
+
22
+ # NOTE: Iam is a class in the api-client as are all namespaced models
23
+ # requested_action = Iam::Service.find_by(name: 'User').actions.find_by(name: 'DescribeUser')
24
+ # NOTE: The result should be cached as this value will not change frequently
25
+ # NOTE: There should be a way to break the cache in case the value does change
26
+ # def requested_action(method_name)
27
+ # Service.find_by(name: service_name).actions.find_by(name: action_name(method_name))
28
+ # end
29
+
30
+ # def action_name(method_name)
31
+ # method_map[method_name] + service_name
32
+ # end
33
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ActionResource < Iam::ApplicationResource
4
+ # caching
5
+ attributes :name, :resource, :action_type
6
+
7
+ def action_type
8
+ @model.type
9
+ end
10
+ end
11
+
12
+ # class ListActionResource < ActionResource; end
13
+
14
+ # class ReadActionResource < ActionResource; end
15
+
16
+ # class WriteActionResource < ActionResource; end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CredentialResource < Iam::ApplicationResource
4
+ attributes :access_key_id, :owner_type, :owner_id, :secret_access_key
5
+
6
+ filter :access_key_id
7
+
8
+ def self.descriptions
9
+ {
10
+ access_key_id: 'The access key'
11
+ }
12
+ end
13
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ class GroupResource < Iam::ApplicationResource
4
+ attributes :name
5
+ has_many :users
6
+
7
+ filter :name
8
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Iam
4
+ class ApplicationResource < ::ApplicationResource
5
+ abstract
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class PolicyResource < Iam::ApplicationResource
4
+ # caching
5
+ attributes :name
6
+ filter :name
7
+
8
+ has_many :actions
9
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ class PublicKeyResource < Iam::ApplicationResource
4
+ attributes :content, :user_id
5
+ has_one :user
6
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RootResource < Iam::ApplicationResource
4
+ attributes :email, :jwt_payload
5
+ attributes :attached_policies, :attached_actions
6
+
7
+ has_many :credentials
8
+
9
+ filter :email
10
+
11
+ def attached_policies; {} end
12
+
13
+ def attached_actions; {} end
14
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ class TenantResource < Iam::ApplicationResource
4
+ attributes :account_id, :root_id, :alias, :name, :display_properties # :locale
5
+
6
+ filter :schema_name
7
+
8
+ def self.descriptions
9
+ {
10
+ schema_name: 'The name of the <h1>Schema</h1>'
11
+ }
12
+ end
13
+
14
+ def self.updatable_fields(context)
15
+ super - [:root_id]
16
+ end
17
+
18
+ def self.creatable_fields(context)
19
+ super - [:root_id]
20
+ end
21
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ class UserResource < Iam::ApplicationResource
4
+ attributes :username, :api, :console, :time_zone, :properties,
5
+ :display_properties, :jwt_payload, :attached_policies,
6
+ :attached_actions, :email, :password, :password_confirmation, :unconfirmed_email
7
+
8
+ has_many :groups
9
+ has_many :credentials
10
+ has_many :public_keys
11
+
12
+ filters :username, :groups
13
+
14
+ def fetchable_fields
15
+ super - %i[password password_confirmation]
16
+ end
17
+
18
+ def self.creatable_fields(context)
19
+ super - %i[attached_policies attached_actions jwt_payload]
20
+ end
21
+
22
+ def self.updatable_fields(context)
23
+ super - %i[attached_policies attached_actions jwt_payload]
24
+ end
25
+ end
@@ -0,0 +1,4 @@
1
+ <html>
2
+ <head></head>
3
+ <body><%= yield %></body>
4
+ </html>
@@ -0,0 +1,5 @@
1
+ <p>Welcome <%= @resource.email %>!</p>
2
+
3
+ <p>You can confirm your account email through the link below:</p>
4
+
5
+ <p> <%= link_to 'Confirm my account', @confirmation_url %> </p>
@@ -0,0 +1,7 @@
1
+ <p>Hello <%= @email %>!</p>
2
+
3
+ <% if @resource.try(:unconfirmed_email?) %>
4
+ <p>We're contacting you to notify you that your email is being changed to <%= @resource.unconfirmed_email %>.</p>
5
+ <% else %>
6
+ <p>We're contacting you to notify you that your email has been changed to <%= @resource.email %>.</p>
7
+ <% end %>
@@ -0,0 +1,3 @@
1
+ <p>Hello <%= @resource.email %>!</p>
2
+
3
+ <p>We're contacting you to notify you that your password has been changed.</p>
@@ -0,0 +1,106 @@
1
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
2
+ <html lang="en">
3
+ <head>
4
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
7
+
8
+ <title>PERX Reset Password</title>
9
+
10
+ <link href="https://fonts.googleapis.com/css?family=Roboto:400,500" rel="stylesheet" type="text/css">
11
+ <style type="text/css">
12
+ </style>
13
+ </head>
14
+ <body style="margin:0; padding:0; background-color:#F2F6FC;">
15
+ <center>
16
+ <table width="100%" border="0" cellpadding="0" cellspacing="0" bgcolor="#F2F6FC">
17
+ <tr>
18
+ <td align="center" height="100%" valign="top" width="100%">
19
+ <!--[if (gte mso 9)|(IE)]>
20
+ <table align="center" border="0" cellspacing="24" cellpadding="0" width="600">
21
+ <tr>
22
+ <td align="center" valign="top" width="600">
23
+ <![endif]-->
24
+ <table align="center" border="0" cellpadding="0" cellspacing="24" width="100%" style="max-width:600px;" bgcolor="#ffffff">
25
+ <tr>
26
+ <td align="left">
27
+ <img alt="Perx" src="https://cdn.uat.whistler.perxtech.io/dev1/global/assets/email/logo.png" width="96" height="34" style="width: 100%; max-width: 96px; font-family: 'Roboto', Helvetica, Arial, sans-serif; font-weight: 500; font-size: 24px; line-height: 24px; letter-spacing: 0.18px; color: #2664ed; text-transform: lowercase; display: block; border: 0px;" border="0">
28
+ </td>
29
+ </tr>
30
+ <tr>
31
+ <td align="center">
32
+ <img alt="Illustration of user unlocking account" src="https://cdn.uat.whistler.perxtech.io/dev1/global/assets/email/email-reset.png" width="268" style="width: 100%; max-width: 268px; font-family: 'Roboto', Helvetica, Arial, sans-serif; font-weight: 400; font-size: 12px; line-height: 16px; letter-spacing: 0.4px; color: #7b7b7b; display: block; border: 0px;" border="0">
33
+ </td>
34
+ </tr>
35
+ <tr>
36
+ <td align="left" valign="top" style="font-family: 'Roboto', Helvetica, Arial, sans-serif; font-weight: 400; font-size: 24px; line-height: 24px; letter-spacing: 0.18px; color: #333333;">
37
+ Hi <%= @resource.username %>,
38
+ </td>
39
+ </tr>
40
+ <tr>
41
+ <td align="left" valign="top" style="font-family: 'Roboto', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 24px; letter-spacing: 0.15px; color: #7b7b7b;">
42
+ A request has been made to reset your password. Simply click on the button below to reset it.
43
+ <b style="font-family: 'Roboto', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 24px; letter-spacing: 0.15px; color: #333333; font-weight: 500;">
44
+ This password reset is only valid for the next 24 hours.
45
+ </b>
46
+ </td>
47
+ </tr>
48
+ <tr>
49
+ <td align="center">
50
+ <table width="100%" border="0" cellspacing="0" cellpadding="0">
51
+ <tr>
52
+ <td align="center">
53
+ <table border="0" cellspacing="0" cellpadding="0" width="268" style="width: 100%; max-width: 268px;">
54
+ <tr>
55
+ <td align="center" width="100%" style="width: 100%; border-radius: 4px;" bgcolor="#2664ED"><a href="<%= @reset_url %>" target="_blank" style="font-size: 14px; letter-spacing: 0.75px; font-family: 'Roboto', Helvetica, Arial, sans-serif; color: #ffffff; font-weight: 500; text-decoration: none; border-radius: 4px; padding: 12px 24px; border: 1px solid #2664ED; display: block;"><!--[if mso]>&nbsp;<![endif]-->Reset Password<!--[if mso]>&nbsp;<![endif]--></a></td>
56
+ </tr>
57
+ </table>
58
+ </td>
59
+ </tr>
60
+ </table>
61
+ </td>
62
+ </tr>
63
+ <tr>
64
+ <td align="left" valign="top" style="font-family: 'Roboto', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 24px; letter-spacing: 0.15px; color: #7b7b7b;">
65
+ If you did not make this request, you can safely ignore this email or
66
+ <a href="#" target="_blank" style="font-family: 'Roboto', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 24px; letter-spacing: 0.15px; color: #2664ED; font-weight: 400; display: inline-block; text-decoration: none;">
67
+ contact support
68
+ </a>
69
+ if you have questions.
70
+ </td>
71
+ </tr>
72
+ <tr>
73
+ <td align="left" valign="top" style="font-family: 'Roboto', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 24px; letter-spacing: 0.15px; color: #7b7b7b;">
74
+ Thank you, <br />
75
+ Team Perx
76
+ </td>
77
+ </tr>
78
+ <tr>
79
+ <td height="1" style="font-size:1px; line-height:1px;">&nbsp;</td>
80
+ </tr>
81
+ <tr>
82
+ <td height="1" style="font-size:1px; line-height:1px;" bgcolor="#e8e8e8">&nbsp;</td>
83
+ </tr>
84
+ <tr>
85
+ <td height="1" style="font-size:1px; line-height:1px;">&nbsp;</td>
86
+ </tr>
87
+ <tr>
88
+ <td align="left" valign="top" style="font-family: 'Roboto', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 16px; letter-spacing: 0.4px; color: #7b7b7b;">
89
+ &copy; 2019 Perx Technologies. All rights reserved.<br /><br />
90
+ 20 Maxwell Road #02-01 <br />
91
+ Maxwell House, 069113
92
+ </td>
93
+ </tr>
94
+
95
+ </table>
96
+ <!--[if (gte mso 9)|(IE)]>
97
+ </td>
98
+ </tr>
99
+ </table>
100
+ <![endif]-->
101
+ </td>
102
+ </tr>
103
+ </table>
104
+ </center>
105
+ </body>
106
+ </html>