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,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: '09c0a33b4d2abc84491e1eb15e1f275c04f634b939404b6987ac7bd266894b57'
4
+ data.tar.gz: d6a28ba8c62e0dc63fb4416e0703220878cc3afd8d91ef9cf9b8dceaac59608a
5
+ SHA512:
6
+ metadata.gz: 1cde288627409018e8492144e8cc390040d0a283f7b762ed49dcf51687b944d77efde5aa024fb0c1baa115e48c4fb9bff388d359ef9d1033231ec3f3548e70ff
7
+ data.tar.gz: ac9e004aec8e9864e31573f543f24da06bdbc872ab5500455560ed03dc831bee53664ef43e6220197afde53eabd635ffd8162b959ee43d36b8e87660401b25f1
@@ -0,0 +1,20 @@
1
+ Copyright 2019 Robert Roach
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,75 @@
1
+
2
+ # IAM
3
+
4
+ User Rob belongs to group Administrators
5
+ Group Administrators has Role FullAccess
6
+
7
+ User Pearl belongs to group DashboardUser
8
+ Group DashboardUser has Role DashboardFullAccess
9
+
10
+ Post is a model
11
+ PostPolicy.update? current_user.has_role?
12
+
13
+ ## Groups
14
+
15
+ Groups have 0 or more attached Policies which give permission for specific actions
16
+ Groups have 0 or more Users
17
+
18
+ ## Users
19
+
20
+ User have 0 or more attached Policies which give permission for specific actions
21
+ Users belong to 0 or more Groups
22
+ Users inherit Group permissions
23
+
24
+ ## Roles
25
+
26
+ Roles have many policies
27
+ A User may have permission to assume a specific Role
28
+ The Role is assumed in order to accomplish a specific Action for which that specific Role has Permission
29
+
30
+
31
+ ## Policies
32
+
33
+ Policies permit or deny certain Actions
34
+
35
+ Example:
36
+
37
+ AlexaForBusinessDeviceSetup
38
+
39
+ ```json
40
+ {
41
+ "Version": "2012-10-17",
42
+ "Statement": [
43
+ {
44
+ "Effect": "Allow",
45
+ "Action": [
46
+ "a4b:RegisterDevice",
47
+ "a4b:CompleteRegistration",
48
+ "a4b:SearchDevices"
49
+ ],
50
+ "Resource": "*"
51
+ }
52
+ ]
53
+ }
54
+ ```
55
+
56
+ ### Other Notes
57
+
58
+ []User, Root and Robot have a policies and actions jsonb column
59
+ []anytime an action or policy changes it calls its users, roles and robots which triggers them to update their properties and actions hashes
60
+
61
+
62
+ Authenticate to get token:
63
+ Root email and password
64
+ User username and password
65
+ Root access_key_id secret_access_key
66
+ User same as above
67
+
68
+ Make requests with JWT
69
+ If the token is present:
70
+ Is it authentic? Is it valid?
71
+ If yes then select the tenant
72
+ Then get the user from the JWT
73
+ Perhaps there should be User and Root objects available to the services. Or use the client based objects
74
+
75
+
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require 'bundler/setup'
5
+ rescue LoadError
6
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
7
+ end
8
+
9
+ require 'rdoc/task'
10
+
11
+ RDoc::Task.new(:rdoc) do |rdoc|
12
+ rdoc.rdoc_dir = 'rdoc'
13
+ rdoc.title = 'Ros::Iam'
14
+ rdoc.options << '--line-numbers'
15
+ rdoc.rdoc_files.include('README.md')
16
+ rdoc.rdoc_files.include('lib/**/*.rb')
17
+ end
18
+
19
+ APP_RAKEFILE = File.expand_path('spec/dummy/Rakefile', __dir__)
20
+ load 'rails/tasks/engine.rake'
21
+
22
+ load 'rails/tasks/statistics.rake'
23
+
24
+ require 'bundler/gem_tasks'
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module IsTenantScoped
4
+ extend ActiveSupport::Concern
5
+
6
+ def login_user!
7
+ false
8
+ end
9
+
10
+ def tenant_schema(params, key = :account_id)
11
+ params_ = HashWithIndifferentAccess.new(params)
12
+
13
+ # this is not a dynamically created method but defined in our tenant concern
14
+ Tenant.find_by_schema_or_alias(params_[key])&.schema_name ||
15
+ Apartment::Tenant.current
16
+ end
17
+
18
+ def user_resource
19
+ "#{resource_name.capitalize}Resource".constantize
20
+ end
21
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CredentialsController < Iam::ApplicationController
4
+ # TODO: Remove this once we support registration of callbacks
5
+ def blackcomb
6
+ res = BlackcombUserCreate.call(params: blackcomb_params)
7
+ if res.success?
8
+ @current_jwt = Ros::Jwt.new(res.model.jwt_payload)
9
+ render json: json_resource(resource_class: UserResource, record: res.model), status: :created
10
+ else
11
+ resource = ApplicationResource.new(res, nil)
12
+ handle_exceptions JSONAPI::Exceptions::ValidationErrors.new(resource)
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def blackcomb_params
19
+ permitted_params = jsonapi_params.permit(:account_id)
20
+
21
+ HashWithIndifferentAccess.new({ current_user: context[:user] }.merge(permitted_params))
22
+ end
23
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class GroupsController < Iam::ApplicationController
4
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Iam
4
+ class ApplicationController < ::ApplicationController
5
+ end
6
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Iam
4
+ class ConfirmationsController < Devise::ConfirmationsController
5
+ include IsTenantScoped
6
+
7
+ skip_before_action :authenticate_it!, only: %i[create show]
8
+
9
+ respond_to :json
10
+
11
+ # POST /resource/confirmation
12
+ def create
13
+ Apartment::Tenant.switch tenant_schema(reset_params) do
14
+ return super unless find_user!
15
+
16
+ @current_user.send_confirmation_instructions
17
+
18
+ if successfully_sent?(@current_user)
19
+ render status: :ok, json: { message: 'ok' }
20
+ else
21
+ render status: :bad_request
22
+ end
23
+ end
24
+ end
25
+
26
+ # Devise v4.7.1 expects this to be a GET request and not PUT which is
27
+ # definitely not what I expected.
28
+ # https://github.com/plataformatec/devise/blob/v4.7.1/app/controllers/devise/confirmations_controller.rb#L21
29
+ #
30
+ # GET /resource/confirmation
31
+ def show
32
+ mail_token = begin
33
+ Ros::Jwt.new(confirmation_params[:token]).decode
34
+ rescue JWT::DecodeError => e
35
+ render status: :bad_request, json: { errors: e }
36
+ return
37
+ end
38
+
39
+ return unless mail_token
40
+
41
+ Apartment::Tenant.switch tenant_schema(mail_token) do
42
+ res = User.confirm_by_token(mail_token[:token])
43
+ if res.confirmed?
44
+ render status: :ok, json: { message: 'ok' }
45
+ else
46
+ render status: :bad_request, json: { errors: res.errors }
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Iam
4
+ class PasswordsController < Devise::PasswordsController
5
+ include IsTenantScoped
6
+
7
+ skip_before_action :authenticate_it!, only: %i[create update]
8
+
9
+ respond_to :json
10
+
11
+ # POST /resource/password
12
+ def create
13
+ Apartment::Tenant.switch tenant_schema(password_params) do
14
+ return super unless find_user!
15
+
16
+ @current_user.send_reset_password_instructions
17
+
18
+ if successfully_sent?(@current_user)
19
+ render status: :ok, json: { message: 'ok' }
20
+ else
21
+ render status: :bad_request
22
+ end
23
+ end
24
+ end
25
+
26
+ # PUT /resource/password
27
+ def update
28
+ mail_token = begin
29
+ Ros::Jwt.new(reset_params[:token]).decode
30
+ rescue JWT::DecodeError => e
31
+ render status: :bad_request, json: { errors: e }
32
+ return
33
+ end
34
+
35
+ return unless mail_token
36
+
37
+ Apartment::Tenant.switch tenant_schema(mail_token) do
38
+ decoded_params = {
39
+ reset_password_token: mail_token[:token],
40
+ password: reset_params[:password],
41
+ password_confirmation: reset_params[:password_confirmation]
42
+ }
43
+
44
+ res = User.reset_password_by_token(decoded_params)
45
+
46
+ if res.persisted?
47
+ res.confirm unless res.confirmed?
48
+ @current_jwt = Ros::Jwt.new(res.jwt_payload)
49
+ render status: :ok, json: { message: 'ok' }
50
+ else
51
+ render status: :bad_request, json: { errors: res.errors }
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Iam
4
+ class SessionsController < Devise::SessionsController
5
+ include IsTenantScoped
6
+
7
+ skip_before_action :authenticate_it!, only: :create
8
+
9
+ respond_to :json
10
+
11
+ # POST /resource/sign_in
12
+ def create
13
+ Apartment::Tenant.switch tenant_schema(sign_in_params) do
14
+ return super unless login_user!
15
+
16
+ @current_jwt = Ros::Jwt.new(current_user.jwt_payload)
17
+ render json: json_resource(resource_class: user_resource, record: current_user)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class PoliciesController < Iam::ApplicationController
4
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class PublicKeysController < Iam::ApplicationController
4
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Roots
4
+ class SessionsController < Iam::SessionsController
5
+ protected
6
+
7
+ def login_user!
8
+ @current_user = Root.find_by(email: sign_in_params[:email])
9
+ current_user&.valid_password? sign_in_params[:password]
10
+ end
11
+
12
+ def sign_in_params
13
+ jsonapi_params.permit(%i[email password])
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RootsController < Iam::ApplicationController
4
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Users
4
+ class ConfirmationsController < Iam::ConfirmationsController
5
+ protected
6
+
7
+ def find_user!
8
+ @current_user = User.find_by(username: reset_params[:username])
9
+ end
10
+
11
+ def reset_params
12
+ jsonapi_params.permit %i[username email account_id]
13
+ end
14
+
15
+ # refer to Users::PasswordController.reset_params for details on why we
16
+ # just allow :token
17
+ def confirmation_params
18
+ jsonapi_params.permit %i[token]
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Users
4
+ class PasswordsController < Iam::PasswordsController
5
+ protected
6
+
7
+ def find_user!
8
+ @current_user = User.find_by(username: password_params[:username])
9
+ end
10
+
11
+ def password_params
12
+ jsonapi_params.permit(%i[email username password password_confirmation account_id])
13
+ end
14
+
15
+ # NOTE: the received token should be the encoded token we sent on the email
16
+ # for password reset. JWT token should have the accountId, username and
17
+ # Devise's reset token. Since the FE does not have the encryption key,
18
+ # if they tamper the token with other params this would fail on validating
19
+ # when we try to use it. Also this generated JWT is not valid for auth
20
+ # as it doesnt have any credentials attached.
21
+ def reset_params
22
+ jsonapi_params.permit(%i[token password password_confirmation])
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Users
4
+ class SessionsController < Iam::SessionsController
5
+ protected
6
+
7
+ def login_user!
8
+ @current_user = User.find_by(username: sign_in_params[:username])
9
+ return if current_user.nil?
10
+ return unless current_user.confirmed?
11
+
12
+ current_user&.valid_password? sign_in_params[:password]
13
+ end
14
+
15
+ def sign_in_params
16
+ jsonapi_params.permit(%i[email username password account_id])
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ class UsersController < Iam::ApplicationController
4
+ def create
5
+ res = UserCreate.call(params: create_params, user: context[:user])
6
+ if res.success?
7
+ render json: json_resource(resource_class: UserResource, record: res.model), status: :created
8
+ else
9
+ resource = ApplicationResource.new(res, nil)
10
+ handle_exceptions JSONAPI::Exceptions::ValidationErrors.new(resource)
11
+ end
12
+ end
13
+
14
+ def create_params
15
+ jsonapi_params.merge(relationships: params.require(:data).fetch(:relationships, {})).permit!
16
+ end
17
+ end