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
checksums.yaml
ADDED
@@ -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
|
data/MIT-LICENSE
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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
|
+
|
data/Rakefile
ADDED
@@ -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,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,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,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
|