sync_attr_with_auth0 0.0.25 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/sync_attr_with_auth0/adapters/active_record/sync.rb +198 -0
- data/lib/sync_attr_with_auth0/adapters/active_record/validation.rb +31 -0
- data/lib/sync_attr_with_auth0/adapters/active_record.rb +161 -0
- data/lib/sync_attr_with_auth0/auth0.rb +55 -10
- data/lib/sync_attr_with_auth0/configuration.rb +56 -0
- data/lib/sync_attr_with_auth0.rb +6 -2
- metadata +7 -4
- data/lib/sync_attr_with_auth0/model.rb +0 -285
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 472500ed61386a758384a9f866359b1a6e600eb2
|
4
|
+
data.tar.gz: 6b8c4af3b271483ac0166386e024083d0627c6dc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2eca8c691f9d505910dd4b559545775ee3341194116b0d31b1157893a2f0e4e953b8406252ec6795aaa92703765445f42bb014a0a85dc4ccf1280dcbfec5a83e
|
7
|
+
data.tar.gz: 5744cf31a52c72770aaa123971c359bd57e3a7b9ac0879b2b548bd73e80165107497c5aa74da62d7c7c2a2bc345ad5b01952960a286b69ddd6c45d96041772ee
|
@@ -0,0 +1,198 @@
|
|
1
|
+
module SyncAttrWithAuth0
|
2
|
+
module Adapters
|
3
|
+
module ActiveRecord
|
4
|
+
module Sync
|
5
|
+
|
6
|
+
def sync_password_with_auth0?
|
7
|
+
!!(auth0_attributes_to_sync.index(auth0_sync_configuration.password_attribute))
|
8
|
+
end # sync_password_with_auth0?
|
9
|
+
|
10
|
+
|
11
|
+
def sync_email_with_auth0?
|
12
|
+
!!(auth0_attributes_to_sync.index(auth0_sync_configuration.email_attribute))
|
13
|
+
end # sync_email_with_auth0?
|
14
|
+
|
15
|
+
|
16
|
+
def sync_with_auth0_on_create?
|
17
|
+
!!((self.respond_to?(:sync_with_auth0_on_create) and !self.sync_with_auth0_on_create.nil?) ? self.sync_with_auth0_on_create : true)
|
18
|
+
end # sync_with_auth0_on_create?
|
19
|
+
|
20
|
+
|
21
|
+
def sync_with_auth0_on_update?
|
22
|
+
!!((self.respond_to?(:sync_with_auth0_on_update) and !self.sync_with_auth0_on_update.nil?) ? self.sync_with_auth0_on_update : true)
|
23
|
+
end # sync_with_auth0_on_update?
|
24
|
+
|
25
|
+
|
26
|
+
def save_to_auth0_on_create
|
27
|
+
return true unless sync_with_auth0_on_create?
|
28
|
+
|
29
|
+
save_to_auth0
|
30
|
+
|
31
|
+
true # don't abort the callback chain
|
32
|
+
end # save_to_auth0_on_create
|
33
|
+
|
34
|
+
|
35
|
+
def save_to_auth0_on_update
|
36
|
+
return true unless sync_with_auth0_on_update?
|
37
|
+
return true unless auth0_dirty?
|
38
|
+
|
39
|
+
save_to_auth0
|
40
|
+
|
41
|
+
true # don't abort the callback chain
|
42
|
+
end # save_to_auth0_on_update
|
43
|
+
|
44
|
+
|
45
|
+
def auth0_dirty?
|
46
|
+
is_dirty = !!(
|
47
|
+
auth0_attributes_to_sync.inject(false) do |memo, attrib|
|
48
|
+
memo || self.try("#{attrib}_changed?")
|
49
|
+
end
|
50
|
+
)
|
51
|
+
|
52
|
+
# If the password was changed, force is_dirty to be true
|
53
|
+
is_dirty = true if auth0_user_password_changed?
|
54
|
+
|
55
|
+
# If the email was changed, force is_dirty to be true
|
56
|
+
is_dirty = true if auth0_user_email_changed?
|
57
|
+
|
58
|
+
return is_dirty
|
59
|
+
end # auth0_dirty?
|
60
|
+
|
61
|
+
|
62
|
+
def save_to_auth0
|
63
|
+
# Determine if the user needs to be created or updated
|
64
|
+
user_uid = auth0_user_uid
|
65
|
+
|
66
|
+
if user_uid.nil? or user_uid.empty?
|
67
|
+
found_user = users_in_auth0_with_matching_email.first
|
68
|
+
|
69
|
+
user_uid = found_user['user_id'] if found_user
|
70
|
+
end
|
71
|
+
|
72
|
+
if user_uid.nil? or user_uid.empty?
|
73
|
+
# The user has no auth0 uid assigned and we can't find a user
|
74
|
+
# with a matching email address, so create.
|
75
|
+
create_in_auth0
|
76
|
+
else
|
77
|
+
# The user already has an auth0 UID assigned or we have a user
|
78
|
+
# with a matching email address, so update.
|
79
|
+
update_in_auth0(user_uid)
|
80
|
+
end
|
81
|
+
end # save_to_auth0
|
82
|
+
|
83
|
+
|
84
|
+
def create_in_auth0
|
85
|
+
params = auth0_create_params
|
86
|
+
|
87
|
+
response = SyncAttrWithAuth0::Auth0.create_user(auth0_user_name, params, auth0_sync_configuration)
|
88
|
+
|
89
|
+
# Update the record with the uid after_commit
|
90
|
+
@auth0_uid = response['user_id']
|
91
|
+
end # create_in_auth0
|
92
|
+
|
93
|
+
|
94
|
+
def update_in_auth0(user_uid)
|
95
|
+
return unless user_uid
|
96
|
+
|
97
|
+
params = auth0_update_params
|
98
|
+
|
99
|
+
begin
|
100
|
+
SyncAttrWithAuth0::Auth0.patch_user(user_uid, params, auth0_sync_configuration)
|
101
|
+
|
102
|
+
# Update the record with the uid after_commit (in case it doesn't match what's on file).
|
103
|
+
@auth0_uid = user_uid
|
104
|
+
rescue ::Auth0::NotFound => e
|
105
|
+
# For whatever reason, the passed in uid was invalid,
|
106
|
+
# determine how to proceed.
|
107
|
+
found_user = users_in_auth0_with_matching_email.first
|
108
|
+
|
109
|
+
if found_user.nil?
|
110
|
+
# We could not find a user with that email address, so create
|
111
|
+
# instead.
|
112
|
+
create_in_auth0
|
113
|
+
else
|
114
|
+
# The uid was incorrect, so re-attempt with the new uid
|
115
|
+
# and update the one on file.
|
116
|
+
SyncAttrWithAuth0::Auth0.patch_user(found_user['user_id'], params, auth0_sync_configuration)
|
117
|
+
|
118
|
+
# Update the record with the uid after_commit
|
119
|
+
@auth0_uid = found_user['user_id']
|
120
|
+
end
|
121
|
+
|
122
|
+
rescue Exception => e
|
123
|
+
::Rails.logger.error e.message
|
124
|
+
::Rails.logger.error e.backtrace.join("\n")
|
125
|
+
|
126
|
+
raise e
|
127
|
+
end
|
128
|
+
end # update_in_auth0
|
129
|
+
|
130
|
+
|
131
|
+
def auth0_create_params
|
132
|
+
user_metadata = auth0_user_metadata
|
133
|
+
|
134
|
+
password = auth0_user_password
|
135
|
+
|
136
|
+
if password.nil? or password.empty?
|
137
|
+
# We MUST include a password on create.
|
138
|
+
password = auth0_default_password
|
139
|
+
end
|
140
|
+
|
141
|
+
email_verified = auth0_email_verified?
|
142
|
+
|
143
|
+
params = {
|
144
|
+
'email' => auth0_user_email,
|
145
|
+
'password' => password,
|
146
|
+
'connection' => auth0_sync_configuration.connection_name,
|
147
|
+
'email_verified' => email_verified,
|
148
|
+
'user_metadata' => user_metadata
|
149
|
+
}
|
150
|
+
|
151
|
+
return params
|
152
|
+
end # auth0_create_params
|
153
|
+
|
154
|
+
|
155
|
+
def auth0_update_params
|
156
|
+
user_metadata = auth0_user_metadata
|
157
|
+
app_metadata = auth0_app_metadata
|
158
|
+
|
159
|
+
params = {
|
160
|
+
'app_metadata' => app_metadata,
|
161
|
+
'user_metadata' => user_metadata
|
162
|
+
}
|
163
|
+
|
164
|
+
if auth0_user_password_changed?
|
165
|
+
# The password needs to be updated.
|
166
|
+
params['password'] = auth0_user_password
|
167
|
+
params['verify_password'] = auth0_verify_password?
|
168
|
+
end
|
169
|
+
|
170
|
+
if auth0_user_email_changed?
|
171
|
+
# The email needs to be updated.
|
172
|
+
params['email'] = auth0_user_email
|
173
|
+
params['verify_email'] = auth0_email_verified?
|
174
|
+
end
|
175
|
+
|
176
|
+
return params
|
177
|
+
end # auth0_update_params
|
178
|
+
|
179
|
+
|
180
|
+
def update_uid_from_auth0
|
181
|
+
if @auth0_uid
|
182
|
+
self.sync_with_auth0_on_update = false if self.respond_to?(:sync_with_auth0_on_update=)
|
183
|
+
self.send("#{auth0_sync_configuration.auth0_uid_attribute}=", @auth0_uid)
|
184
|
+
|
185
|
+
# Nil the instance variable to prevent an infinite loop
|
186
|
+
@auth0_uid = nil
|
187
|
+
|
188
|
+
# Save!
|
189
|
+
self.save
|
190
|
+
end
|
191
|
+
|
192
|
+
true # don't abort the callback chain
|
193
|
+
end # update_uid_from_auth0
|
194
|
+
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module SyncAttrWithAuth0
|
2
|
+
module Adapters
|
3
|
+
module ActiveRecord
|
4
|
+
module Validation
|
5
|
+
|
6
|
+
def validate_with_auth0?
|
7
|
+
!!((self.respond_to?(:validate_with_auth0) and !self.validate_with_auth0.nil?) ? self.validate_with_auth0 : true)
|
8
|
+
end # validate_with_auth0?
|
9
|
+
|
10
|
+
|
11
|
+
def validate_email_with_auth0?
|
12
|
+
email_changed_method_name = "#{auth0_sync_configuration.email_attribute.to_s}_changed?"
|
13
|
+
|
14
|
+
!!(validate_with_auth0? and self.send(email_changed_method_name))
|
15
|
+
end # validate_email_with_auth0?
|
16
|
+
|
17
|
+
|
18
|
+
def validate_email_with_auth0
|
19
|
+
return true unless validate_email_with_auth0?
|
20
|
+
|
21
|
+
return users_in_auth0_with_matching_email.empty?
|
22
|
+
end # validate_email_with_auth0
|
23
|
+
|
24
|
+
def users_in_auth0_with_matching_email
|
25
|
+
return SyncAttrWithAuth0::Auth0.find_users_by_email(auth0_user_email, auth0_sync_configuration)
|
26
|
+
end # users_in_auth0_with_matching_email
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
require 'sync_attr_with_auth0/adapters/active_record/validation'
|
2
|
+
require 'sync_attr_with_auth0/adapters/active_record/sync'
|
3
|
+
|
4
|
+
module SyncAttrWithAuth0
|
5
|
+
module Adapters
|
6
|
+
module ActiveRecord
|
7
|
+
extend ::ActiveSupport::Concern
|
8
|
+
|
9
|
+
include SyncAttrWithAuth0::Adapters::ActiveRecord::Validation
|
10
|
+
include SyncAttrWithAuth0::Adapters::ActiveRecord::Sync
|
11
|
+
|
12
|
+
require "uuidtools"
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
|
16
|
+
def sync_attr_with_auth0(*fields)
|
17
|
+
options = fields.extract_options!
|
18
|
+
|
19
|
+
# Setup methods for accessing fields and options
|
20
|
+
define_method 'auth0_attributes_to_sync' do
|
21
|
+
fields
|
22
|
+
end
|
23
|
+
|
24
|
+
define_method 'setup_auth0_sync_configuration' do
|
25
|
+
config = SyncAttrWithAuth0.configuration.dup
|
26
|
+
|
27
|
+
options.each do |key, value|
|
28
|
+
config.send(:"#{key}=", value)
|
29
|
+
end
|
30
|
+
|
31
|
+
config
|
32
|
+
end
|
33
|
+
|
34
|
+
# Setup callbacks
|
35
|
+
after_validation :validate_email_with_auth0
|
36
|
+
after_create :save_to_auth0_on_create
|
37
|
+
after_update :save_to_auth0_on_update
|
38
|
+
after_commit :update_uid_from_auth0
|
39
|
+
end # sync_attr_with_auth0
|
40
|
+
|
41
|
+
end # ClassMethods
|
42
|
+
|
43
|
+
|
44
|
+
def auth0_sync_configuration
|
45
|
+
@auth0_sync_configuration ||= setup_auth0_sync_configuration
|
46
|
+
end # auth0_sync_configuration
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def auth0_user_email
|
51
|
+
self.send(auth0_sync_configuration.email_attribute) if self.respond_to?(auth0_sync_configuration.email_attribute)
|
52
|
+
end # auth0_user_email
|
53
|
+
|
54
|
+
|
55
|
+
def auth0_user_email_changed?
|
56
|
+
return false unless self.respond_to?(auth0_sync_configuration.email_attribute)
|
57
|
+
# return false unless sync_email_with_auth0? # We don't care if it changed if we aren't syncing it.
|
58
|
+
|
59
|
+
return self.send("#{auth0_sync_configuration.email_attribute}_changed?")
|
60
|
+
end # auth0_email_changed?
|
61
|
+
|
62
|
+
|
63
|
+
def auth0_user_uid
|
64
|
+
self.send(auth0_sync_configuration.auth0_uid_attribute) if self.respond_to?(auth0_sync_configuration.auth0_uid_attribute)
|
65
|
+
end # auth0_user_uid
|
66
|
+
|
67
|
+
|
68
|
+
def auth0_user_name
|
69
|
+
self.send(auth0_sync_configuration.name_attribute) if self.respond_to?(auth0_sync_configuration.name_attribute)
|
70
|
+
end # auth0_user_name
|
71
|
+
|
72
|
+
|
73
|
+
def auth0_user_given_name
|
74
|
+
self.send(auth0_sync_configuration.given_name_attribute) if self.respond_to?(auth0_sync_configuration.given_name_attribute)
|
75
|
+
end # auth0_user_name
|
76
|
+
|
77
|
+
|
78
|
+
def auth0_user_family_name
|
79
|
+
self.send(auth0_sync_configuration.family_name_attribute) if self.respond_to?(auth0_sync_configuration.family_name_attribute)
|
80
|
+
end # auth0_user_name
|
81
|
+
|
82
|
+
|
83
|
+
def auth0_user_password
|
84
|
+
self.send(auth0_sync_configuration.password_attribute) if self.respond_to?(auth0_sync_configuration.password_attribute)
|
85
|
+
end # auth0_user_password
|
86
|
+
|
87
|
+
|
88
|
+
def auth0_user_password_changed?
|
89
|
+
return false unless self.respond_to?(auth0_sync_configuration.password_attribute)
|
90
|
+
# return false unless sync_password_with_auth0? # We don't care if it changed if we aren't syncing it.
|
91
|
+
|
92
|
+
if self.respond_to?(:"#{auth0_sync_configuration.password_attribute.to_s}_changed?")
|
93
|
+
# We have a changed method, use it
|
94
|
+
return self.send(:"#{auth0_sync_configuration.password_attribute.to_s}_changed?")
|
95
|
+
else
|
96
|
+
# We don't have a changed method, check if the attribute was set.
|
97
|
+
return !self.send(auth0_sync_configuration.password_attribute).nil?
|
98
|
+
end
|
99
|
+
end # auth0_user_password_changed?
|
100
|
+
|
101
|
+
|
102
|
+
def auth0_default_password
|
103
|
+
# Need a9 or something similar to guarantee one letter and one number in the password
|
104
|
+
"#{auth0_new_uuid[0..19]}aA9"
|
105
|
+
end # auth0_default_password
|
106
|
+
|
107
|
+
|
108
|
+
def auth0_new_uuid
|
109
|
+
::UUIDTools::UUID.random_create().to_s
|
110
|
+
end # auth0_new_uuid
|
111
|
+
|
112
|
+
|
113
|
+
def auth0_email_verified?
|
114
|
+
# Unless we're explicitly told otherwise, don't consider the email verified.
|
115
|
+
return false unless self.respond_to?(auth0_sync_configuration.email_verified_attribute)
|
116
|
+
|
117
|
+
return self.send(auth0_sync_configuration.email_verified_attribute)
|
118
|
+
end # auth0_email_verified?
|
119
|
+
|
120
|
+
|
121
|
+
def auth0_verify_password?
|
122
|
+
# Unless we're explicitly told otherwise, verify the password changes.
|
123
|
+
return true unless self.respond_to?(auth0_sync_configuration.verify_password_attribute)
|
124
|
+
|
125
|
+
self.send(auth0_sync_configuration.verify_password_attribute)
|
126
|
+
end # auth0_verify_password?
|
127
|
+
|
128
|
+
|
129
|
+
def auth0_user_metadata
|
130
|
+
user_metadata = {}
|
131
|
+
|
132
|
+
non_metadata_keys = [
|
133
|
+
auth0_sync_configuration.name_attribute,
|
134
|
+
auth0_sync_configuration.family_name_attribute,
|
135
|
+
auth0_sync_configuration.given_name_attribute,
|
136
|
+
auth0_sync_configuration.email_attribute,
|
137
|
+
auth0_sync_configuration.password_attribute,
|
138
|
+
auth0_sync_configuration.email_verified_attribute
|
139
|
+
]
|
140
|
+
|
141
|
+
auth0_attributes_to_sync.each do |key|
|
142
|
+
user_metadata[key.to_s] = self.send(key) if self.respond_to?(key) and non_metadata_keys.index(key).nil?
|
143
|
+
end
|
144
|
+
|
145
|
+
return user_metadata
|
146
|
+
end # auth0_user_metadata
|
147
|
+
|
148
|
+
|
149
|
+
def auth0_app_metadata
|
150
|
+
return {
|
151
|
+
'name' => auth0_user_name,
|
152
|
+
'nickname' => auth0_user_name,
|
153
|
+
'given_name' => auth0_user_given_name,
|
154
|
+
'family_name' => auth0_user_family_name
|
155
|
+
}
|
156
|
+
end # auth0_app_metadata
|
157
|
+
|
158
|
+
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
module SyncAttrWithAuth0
|
2
2
|
module Auth0
|
3
|
+
class InvalidAuth0ConfigurationException < StandardError; end
|
4
|
+
|
3
5
|
require "auth0"
|
4
6
|
require "uuidtools"
|
5
7
|
|
@@ -18,26 +20,69 @@ module SyncAttrWithAuth0
|
|
18
20
|
jwt = JWT.encode(payload, JWT.base64url_decode(global_client_secret))
|
19
21
|
|
20
22
|
return jwt
|
21
|
-
end
|
23
|
+
end # ::create_auth0_jwt
|
24
|
+
|
22
25
|
|
23
26
|
def self.create_auth0_client(
|
24
27
|
api_version: 2,
|
25
|
-
|
26
|
-
global_client_secret: ENV['AUTH0_GLOBAL_CLIENT_SECRET'],
|
27
|
-
client_id: ENV['AUTH0_CLIENT_ID'],
|
28
|
-
client_secret: ENV['AUTH0_CLIENT_SECRET'],
|
29
|
-
namespace: ENV['AUTH0_DOMAIN']
|
28
|
+
config: SyncAttrWithAuth0.configuration
|
30
29
|
)
|
30
|
+
validate_auth0_config_for_api(api_version, config)
|
31
|
+
|
31
32
|
case api_version
|
32
33
|
when 1
|
33
|
-
auth0 = Auth0Client.new(client_id:
|
34
|
+
auth0 = Auth0Client.new(client_id: config.auth0_client_id, client_secret: config.auth0_client_secret, namespace: config.auth0_namespace)
|
34
35
|
when 2
|
35
|
-
jwt = SyncAttrWithAuth0::Auth0.create_auth0_jwt(global_client_id:
|
36
|
-
auth0 = Auth0Client.new(api_version: 2, access_token: jwt, namespace:
|
36
|
+
jwt = SyncAttrWithAuth0::Auth0.create_auth0_jwt(global_client_id: config.auth0_global_client_id, global_client_secret: config.auth0_global_client_secret)
|
37
|
+
auth0 = Auth0Client.new(api_version: 2, access_token: jwt, namespace: config.auth0_namespace)
|
37
38
|
end
|
38
39
|
|
39
40
|
return auth0
|
40
|
-
end
|
41
|
+
end # ::create_auth0_client
|
42
|
+
|
43
|
+
|
44
|
+
def self.validate_auth0_config_for_api(api_version, config = SyncAttrWithAuth0.configuration)
|
45
|
+
settings_to_validate = []
|
46
|
+
invalid_settings = []
|
47
|
+
|
48
|
+
case api_version
|
49
|
+
when 1
|
50
|
+
settings_to_validate = [:auth0_client_id, :auth0_client_secret, :auth0_namespace]
|
51
|
+
when 2
|
52
|
+
settings_to_validate = [:auth0_global_client_id, :auth0_global_client_secret, :auth0_namespace]
|
53
|
+
end
|
54
|
+
|
55
|
+
settings_to_validate.each do |setting_name|
|
56
|
+
unless config.send(setting_name)
|
57
|
+
invalid_settings << setting_name
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
if invalid_settings.length > 0
|
62
|
+
raise InvalidAuth0ConfigurationException.new("The following required auth0 settings were invalid: #{invalid_settings.join(', ')}")
|
63
|
+
end
|
64
|
+
end # ::validate_auth0_config_for_api
|
65
|
+
|
66
|
+
|
67
|
+
def self.find_users_by_email(email, config = SyncAttrWithAuth0.configuration)
|
68
|
+
auth0 = SyncAttrWithAuth0::Auth0.create_auth0_client(config: config)
|
69
|
+
|
70
|
+
return auth0.users(q: "email:\"#{email}\"")
|
71
|
+
end # ::find_users_by_email
|
72
|
+
|
73
|
+
|
74
|
+
def self.create_user(name, params, config = SyncAttrWithAuth0.configuration)
|
75
|
+
auth0 = SyncAttrWithAuth0::Auth0.create_auth0_client(config: config)
|
76
|
+
|
77
|
+
return auth0.create_user(name, params)
|
78
|
+
end # ::create_user
|
79
|
+
|
80
|
+
|
81
|
+
def self.patch_user(uid, params, config = SyncAttrWithAuth0.configuration)
|
82
|
+
auth0 = SyncAttrWithAuth0::Auth0.create_auth0_client(config: config)
|
83
|
+
|
84
|
+
return auth0.patch_user(uid, params)
|
85
|
+
end # ::patch_user
|
41
86
|
|
42
87
|
end
|
43
88
|
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'active_support/core_ext/module/attribute_accessors'
|
2
|
+
|
3
|
+
module SyncAttrWithAuth0
|
4
|
+
|
5
|
+
class << self
|
6
|
+
attr_accessor :configuration
|
7
|
+
end
|
8
|
+
|
9
|
+
# Start a SyncAttrWithAuth0 configuration block in an initializer
|
10
|
+
#
|
11
|
+
# example: Provide a default UID for the applicaiton
|
12
|
+
# SyncAttrWithAuth0.configure do |config|
|
13
|
+
# config.auth0_uid_attribute = :auth0_uid
|
14
|
+
# end
|
15
|
+
def self.configure
|
16
|
+
yield configuration
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.configuration
|
20
|
+
@configuration ||= Configuration.new
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.reset_configuration
|
24
|
+
@configuration = Configuration.new
|
25
|
+
end
|
26
|
+
|
27
|
+
# SyncAttrWithAuth0 configuration class.
|
28
|
+
# This is used by SyncAttrWithAuth0 to provide configuration settings.
|
29
|
+
class Configuration
|
30
|
+
attr_accessor :auth0_global_client_id, :auth0_global_client_secret,
|
31
|
+
:auth0_client_id, :auth0_client_secret, :auth0_namespace,
|
32
|
+
:auth0_uid_attribute, :name_attribute, :given_name_attribute,
|
33
|
+
:family_name_attribute, :email_attribute, :password_attribute,
|
34
|
+
:email_verified_attribute, :verify_password_attribute,
|
35
|
+
:connection_name
|
36
|
+
|
37
|
+
|
38
|
+
def initialize
|
39
|
+
@auth0_global_client_id = ENV['AUTH0_GLOBAL_CLIENT_ID']
|
40
|
+
@auth0_global_client_secret = ENV['AUTH0_GLOBAL_CLIENT_SECRET']
|
41
|
+
@auth0_client_id = ENV['AUTH0_CLIENT_ID']
|
42
|
+
@auth0_client_secret = ENV['AUTH0_CLIENT_SECRET']
|
43
|
+
@auth0_namespace = ENV['AUTH0_NAMESPACE']
|
44
|
+
|
45
|
+
@auth0_uid_attribute = :auth0_uid
|
46
|
+
@name_attribute = :name
|
47
|
+
@given_name_attribute = :given_name
|
48
|
+
@family_name_attribute = :family_name
|
49
|
+
@email_attribute = :email
|
50
|
+
@password_attribute = :password
|
51
|
+
@email_verified_attribute = :email_verified
|
52
|
+
@verify_password_attribute = :verify_password
|
53
|
+
@connection_name = 'Username-Password-Authentication'
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/sync_attr_with_auth0.rb
CHANGED
@@ -1,4 +1,8 @@
|
|
1
1
|
require 'sync_attr_with_auth0/auth0'
|
2
|
-
require 'sync_attr_with_auth0/model'
|
2
|
+
# require 'sync_attr_with_auth0/model'
|
3
|
+
require 'sync_attr_with_auth0/configuration'
|
4
|
+
require 'sync_attr_with_auth0/adapters/active_record'
|
3
5
|
|
4
|
-
|
6
|
+
module SyncAttrWithAuth0
|
7
|
+
::ActiveRecord::Base.send :include, ::SyncAttrWithAuth0::Adapters::ActiveRecord
|
8
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sync_attr_with_auth0
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Patrick McGraw
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2016-
|
12
|
+
date: 2016-07-26 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rest-client
|
@@ -145,8 +145,11 @@ extensions: []
|
|
145
145
|
extra_rdoc_files: []
|
146
146
|
files:
|
147
147
|
- lib/sync_attr_with_auth0.rb
|
148
|
+
- lib/sync_attr_with_auth0/adapters/active_record.rb
|
149
|
+
- lib/sync_attr_with_auth0/adapters/active_record/sync.rb
|
150
|
+
- lib/sync_attr_with_auth0/adapters/active_record/validation.rb
|
148
151
|
- lib/sync_attr_with_auth0/auth0.rb
|
149
|
-
- lib/sync_attr_with_auth0/
|
152
|
+
- lib/sync_attr_with_auth0/configuration.rb
|
150
153
|
homepage: http://rubygems.org/gems/sync_attr_with_auth0
|
151
154
|
licenses:
|
152
155
|
- MIT
|
@@ -167,7 +170,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
167
170
|
version: '0'
|
168
171
|
requirements: []
|
169
172
|
rubyforge_project:
|
170
|
-
rubygems_version: 2.
|
173
|
+
rubygems_version: 2.5.1
|
171
174
|
signing_key:
|
172
175
|
specification_version: 4
|
173
176
|
summary: Synchronize attributes on a local ActiveRecord user model with the user metadata
|
@@ -1,285 +0,0 @@
|
|
1
|
-
module SyncAttrWithAuth0
|
2
|
-
module Model
|
3
|
-
extend ::ActiveSupport::Concern
|
4
|
-
|
5
|
-
require "uuidtools"
|
6
|
-
|
7
|
-
module ClassMethods
|
8
|
-
|
9
|
-
def sync_attr_with_auth0(options = {})
|
10
|
-
class_attribute :auth0_sync_options
|
11
|
-
|
12
|
-
verify_environment_variables
|
13
|
-
merge_default_options(options)
|
14
|
-
|
15
|
-
after_validation :validate_email_with_auth0
|
16
|
-
after_create :auth0_create
|
17
|
-
after_update :auth0_update, if: :auth0_dirty?
|
18
|
-
after_commit :auth0_set_uid
|
19
|
-
end
|
20
|
-
|
21
|
-
private
|
22
|
-
|
23
|
-
def verify_environment_variables
|
24
|
-
env_variables = %w(AUTH0_GLOBAL_CLIENT_ID AUTH0_GLOBAL_CLIENT_SECRET AUTH0_CLIENT_ID AUTH0_CLIENT_SECRET AUTH0_DOMAIN)
|
25
|
-
missing_env_variables = []
|
26
|
-
|
27
|
-
env_variables.each do |env_variable_name|
|
28
|
-
unless ENV[env_variable_name]
|
29
|
-
missing_env_variables << env_variable_name
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
if missing_env_variables.size > 0
|
34
|
-
raise Exception.new("Missing the following required environment variables: #{missing_env_variables.join(',')}")
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
def merge_default_options(options)
|
39
|
-
self.auth0_sync_options = {
|
40
|
-
uid_att: :uid,
|
41
|
-
name_att: :name,
|
42
|
-
given_name_att: :given_name,
|
43
|
-
family_name_att: :family_name,
|
44
|
-
email_att: :email,
|
45
|
-
password_att: :password,
|
46
|
-
email_verified_att: :email_verified,
|
47
|
-
verify_password_att: :verify_password,
|
48
|
-
connection_name: 'Username-Password-Authentication',
|
49
|
-
sync_atts: []
|
50
|
-
}
|
51
|
-
|
52
|
-
self.auth0_sync_options.merge!(options)
|
53
|
-
end
|
54
|
-
|
55
|
-
end
|
56
|
-
|
57
|
-
def validate_email_with_auth0
|
58
|
-
# If the email is being modified, verify the new email does not already
|
59
|
-
# exist in auth0.
|
60
|
-
|
61
|
-
ok_to_validate = (self.respond_to?(:validate_with_auth0) and !self.validate_with_auth0.nil? ? self.validate_with_auth0 : true)
|
62
|
-
|
63
|
-
if ok_to_validate and self.email_changed?
|
64
|
-
response = find_user_in_auth0
|
65
|
-
|
66
|
-
return response.empty?
|
67
|
-
end
|
68
|
-
|
69
|
-
return true
|
70
|
-
end
|
71
|
-
|
72
|
-
def auth0_create
|
73
|
-
# When creating a new user, create the user in auth0.
|
74
|
-
|
75
|
-
ok_to_sync = (self.respond_to?(:sync_with_auth0_on_create) and !self.sync_with_auth0_on_create.nil? ? self.sync_with_auth0_on_create : true)
|
76
|
-
|
77
|
-
# Do not create a user in auth0 if the user already has a uid from auth0
|
78
|
-
if ok_to_sync
|
79
|
-
unless self.send(auth0_sync_options[:uid_att]).nil? or self.send(auth0_sync_options[:uid_att]).empty?
|
80
|
-
ok_to_sync = false
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
if ok_to_sync
|
85
|
-
create_user_in_auth0
|
86
|
-
end
|
87
|
-
|
88
|
-
true # don't abort the callback chain
|
89
|
-
end
|
90
|
-
|
91
|
-
def auth0_update
|
92
|
-
ok_to_sync = (self.respond_to?(:sync_with_auth0_on_update) and !self.sync_with_auth0_on_update.nil? ? self.sync_with_auth0_on_update : true)
|
93
|
-
|
94
|
-
if ok_to_sync
|
95
|
-
|
96
|
-
# Get the auth0 uid
|
97
|
-
uid = self.send(auth0_sync_options[:uid_att])
|
98
|
-
|
99
|
-
# TODO: create a user if the uid is nil
|
100
|
-
unless uid.nil?
|
101
|
-
# Update the user in auth0
|
102
|
-
update_user_in_auth0(uid)
|
103
|
-
end
|
104
|
-
|
105
|
-
end
|
106
|
-
|
107
|
-
true # don't abort the callback chain
|
108
|
-
end
|
109
|
-
|
110
|
-
def auth0_set_uid
|
111
|
-
if @auth0_uid
|
112
|
-
self.sync_with_auth0_on_update = false if self.respond_to?(:sync_with_auth0_on_update=)
|
113
|
-
self.send("#{auth0_sync_options[:uid_att]}=", @auth0_uid)
|
114
|
-
|
115
|
-
# Nil the instance variable to prevent an infinite loop
|
116
|
-
@auth0_uid = nil
|
117
|
-
|
118
|
-
# Save!
|
119
|
-
self.save
|
120
|
-
end
|
121
|
-
|
122
|
-
true # don't abort the callback chain
|
123
|
-
end
|
124
|
-
|
125
|
-
def create_user_in_auth0()
|
126
|
-
user_metadata = auth0_user_metadata
|
127
|
-
|
128
|
-
password = auth0_user_password
|
129
|
-
|
130
|
-
if password.nil?
|
131
|
-
password = auth0_default_password
|
132
|
-
end
|
133
|
-
|
134
|
-
email_verified = auth0_email_verified?
|
135
|
-
args = {
|
136
|
-
'email' => self.send(auth0_sync_options[:email_att]),
|
137
|
-
'password' => password,
|
138
|
-
'connection' => auth0_sync_options[:connection_name],
|
139
|
-
'email_verified' => email_verified,
|
140
|
-
'user_metadata' => user_metadata
|
141
|
-
}
|
142
|
-
|
143
|
-
auth0 = SyncAttrWithAuth0::Auth0.create_auth0_client
|
144
|
-
|
145
|
-
response = auth0.create_user(self.send(auth0_sync_options[:name_att]), args)
|
146
|
-
|
147
|
-
# Update the record with the uid after_commit
|
148
|
-
@auth0_uid = response['user_id']
|
149
|
-
end
|
150
|
-
|
151
|
-
def update_user_in_auth0(uid)
|
152
|
-
user_metadata = auth0_user_metadata
|
153
|
-
|
154
|
-
auth0 = SyncAttrWithAuth0::Auth0.create_auth0_client
|
155
|
-
|
156
|
-
args = {
|
157
|
-
'app_metadata' => {
|
158
|
-
'name' => self.send(auth0_sync_options[:name_att]),
|
159
|
-
'nickname' => self.send(auth0_sync_options[:name_att]),
|
160
|
-
'given_name' => self.send(auth0_sync_options[:given_name_att]),
|
161
|
-
'family_name' => self.send(auth0_sync_options[:family_name_att])
|
162
|
-
}
|
163
|
-
}
|
164
|
-
|
165
|
-
if (
|
166
|
-
auth0_sync_options[:sync_atts].index(auth0_sync_options[:password_att]) and
|
167
|
-
auth0_user_password_changed?
|
168
|
-
)
|
169
|
-
# The password should be sync'd and was changed
|
170
|
-
args['password'] = self.send(auth0_sync_options[:password_att])
|
171
|
-
args['verify_password'] = auth0_verify_password?
|
172
|
-
end
|
173
|
-
|
174
|
-
if (
|
175
|
-
auth0_sync_options[:sync_atts].index(auth0_sync_options[:email_att]) and
|
176
|
-
auth0_email_changed?
|
177
|
-
)
|
178
|
-
# The email should be sync'd and was changed
|
179
|
-
args['email'] = self.send(auth0_sync_options[:email_att])
|
180
|
-
args['verify_email'] = auth0_email_verified?
|
181
|
-
end
|
182
|
-
|
183
|
-
args['user_metadata'] = user_metadata
|
184
|
-
|
185
|
-
begin
|
186
|
-
auth0.patch_user(uid, args)
|
187
|
-
|
188
|
-
rescue ::Auth0::NotFound => e
|
189
|
-
response = find_user_in_auth0
|
190
|
-
found_user = response.first
|
191
|
-
|
192
|
-
if found_user.nil?
|
193
|
-
# Could not find the user, create it in auth0
|
194
|
-
create_user_in_auth0
|
195
|
-
else
|
196
|
-
# Update with the new uid and correct the one on file
|
197
|
-
auth0 = SyncAttrWithAuth0::Auth0.create_auth0_client
|
198
|
-
auth0.patch_user(found_user['user_id'], args)
|
199
|
-
|
200
|
-
# Update the record with the uid after_commit
|
201
|
-
@auth0_uid = found_user['user_id']
|
202
|
-
end
|
203
|
-
|
204
|
-
rescue Exception => e
|
205
|
-
::Rails.logger.error e.message
|
206
|
-
::Rails.logger.error e.backtrace.join("\n")
|
207
|
-
|
208
|
-
raise e
|
209
|
-
end
|
210
|
-
end
|
211
|
-
|
212
|
-
def find_user_in_auth0
|
213
|
-
auth0 = SyncAttrWithAuth0::Auth0.create_auth0_client(api_version: 1)
|
214
|
-
|
215
|
-
response = auth0.users("email:#{self.send(auth0_sync_options[:email_att])}")
|
216
|
-
|
217
|
-
return response
|
218
|
-
end
|
219
|
-
|
220
|
-
def auth0_user_password
|
221
|
-
self.respond_to?(auth0_sync_options[:password_att]) ? self.send(auth0_sync_options[:password_att]) : auth0_default_password
|
222
|
-
end
|
223
|
-
|
224
|
-
def auth0_user_password_changed?
|
225
|
-
if self.respond_to?(auth0_sync_options[:password_att])
|
226
|
-
if self.respond_to?(:"#{auth0_sync_options[:password_att].to_s}_changed?")
|
227
|
-
# We have a changed method, use it
|
228
|
-
return self.send(:"#{auth0_sync_options[:password_att].to_s}_changed?")
|
229
|
-
else
|
230
|
-
# We don't have a changed method, check if the attribute was set.
|
231
|
-
return !self.send(auth0_sync_options[:password_att]).nil?
|
232
|
-
end
|
233
|
-
end
|
234
|
-
|
235
|
-
return false
|
236
|
-
end
|
237
|
-
|
238
|
-
def auth0_email_verified?
|
239
|
-
!!(self.respond_to?(auth0_sync_options[:email_verified_att]) ? self.send(auth0_sync_options[:email_verified_att]) : false)
|
240
|
-
end
|
241
|
-
|
242
|
-
def auth0_email_changed?
|
243
|
-
!!(self.respond_to?(auth0_sync_options[:email_att]) ? self.send("#{auth0_sync_options[:email_att]}_changed?") : false)
|
244
|
-
end
|
245
|
-
|
246
|
-
def auth0_verify_password?
|
247
|
-
!!(self.respond_to?(auth0_sync_options[:verify_password_att]) ? self.send(auth0_sync_options[:verify_password_att]) : true)
|
248
|
-
end
|
249
|
-
|
250
|
-
def auth0_default_password
|
251
|
-
# Need a9 or something similar to guarantee one letter and one number in the password
|
252
|
-
"#{auth0_new_uuid[0..19]}aA9"
|
253
|
-
end
|
254
|
-
|
255
|
-
def auth0_new_uuid
|
256
|
-
::UUIDTools::UUID.random_create().to_s
|
257
|
-
end
|
258
|
-
|
259
|
-
def auth0_user_metadata
|
260
|
-
user_metadata = {}
|
261
|
-
app_metadata_keys = [auth0_sync_options[:family_name_att],
|
262
|
-
auth0_sync_options[:given_name_att], auth0_sync_options[:email_att],
|
263
|
-
auth0_sync_options[:password_att],
|
264
|
-
auth0_sync_options[:email_verified_att], auth0_sync_options[:name_att]]
|
265
|
-
|
266
|
-
auth0_sync_options[:sync_atts].each do |key|
|
267
|
-
user_metadata[key] = self.send(key) if self.respond_to?(key) and app_metadata_keys.index(key).nil?
|
268
|
-
end
|
269
|
-
|
270
|
-
return user_metadata
|
271
|
-
end
|
272
|
-
|
273
|
-
def auth0_dirty?
|
274
|
-
is_dirty = !!(auth0_sync_options[:sync_atts].inject(false) do |memo, attrib|
|
275
|
-
memo || try("#{attrib}_changed?")
|
276
|
-
end)
|
277
|
-
|
278
|
-
# If the password was changed, force is_dirty to be true
|
279
|
-
is_dirty = true if auth0_user_password_changed?
|
280
|
-
|
281
|
-
return is_dirty
|
282
|
-
end
|
283
|
-
|
284
|
-
end
|
285
|
-
end
|