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