rapid 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +66 -0
- data/lib/rad/http_controller/acts_as/authenticated.rb +131 -0
- data/lib/rad/http_controller/acts_as/authenticated_master_domain.rb +119 -0
- data/lib/rad/http_controller/acts_as/authorized.rb +83 -0
- data/lib/rad/http_controller/acts_as/localized.rb +27 -0
- data/lib/rad/http_controller/acts_as/multitenant.rb +53 -0
- data/lib/rad/http_controller/helpers/service_mix_helper.rb +50 -0
- data/lib/rad/http_controller.rb +15 -0
- data/lib/rad/lib/text_utils.rb +334 -0
- data/lib/rad/locales/en.yml +80 -0
- data/lib/rad/locales/ru.yml +83 -0
- data/lib/rad/locales.rb +2 -0
- data/lib/rad/models/account.rb +88 -0
- data/lib/rad/models/default_permissions.yml +26 -0
- data/lib/rad/models/micelaneous.rb +1 -0
- data/lib/rad/models/role.rb +88 -0
- data/lib/rad/models/secure_token.rb +33 -0
- data/lib/rad/models/space.rb +184 -0
- data/lib/rad/models/user.rb +158 -0
- data/lib/rad/models.rb +41 -0
- data/lib/rad/mongo_mapper/acts_as/authenticated_by_open_id.rb +29 -0
- data/lib/rad/mongo_mapper/acts_as/authenticated_by_password.rb +120 -0
- data/lib/rad/mongo_mapper/acts_as/authorized.rb +197 -0
- data/lib/rad/mongo_mapper/acts_as/authorized_object.rb +171 -0
- data/lib/rad/mongo_mapper/multitenant.rb +34 -0
- data/lib/rad/mongo_mapper/rad_micelaneous.rb +43 -0
- data/lib/rad/mongo_mapper/space_keys.rb +62 -0
- data/lib/rad/mongo_mapper/text_processor.rb +47 -0
- data/lib/rad/mongo_mapper.rb +20 -0
- data/lib/rad/paperclip/callbacks.rb +40 -0
- data/lib/rad/paperclip/extensions.rb +64 -0
- data/lib/rad/paperclip/integration.rb +165 -0
- data/lib/rad/paperclip/mime.rb +5 -0
- data/lib/rad/paperclip/validations.rb +64 -0
- data/lib/rad/paperclip.rb +11 -0
- data/lib/rad/spec/controller.rb +51 -0
- data/lib/rad/spec/model/factories.rb +65 -0
- data/lib/rad/spec/model.rb +85 -0
- data/lib/rad/spec/rem_helper.rb +145 -0
- data/lib/rad/spec.rb +4 -0
- data/lib/rad/tasks/backup.rake +64 -0
- data/lib/rad/tasks/initialize.rake +35 -0
- data/lib/rad.rb +32 -0
- data/readme.md +3 -0
- data/spec/controller/authorization_spec.rb +146 -0
- data/spec/controller/helper.rb +14 -0
- data/spec/lib/helper.rb +7 -0
- data/spec/lib/text_utils_spec.rb +238 -0
- data/spec/models/authorization_spec.rb +93 -0
- data/spec/models/authorized_object_spec.rb +258 -0
- data/spec/models/file_audit_spec/100.txt +1 -0
- data/spec/models/file_audit_spec/302.txt +3 -0
- data/spec/models/file_audit_spec.rb +168 -0
- data/spec/models/helper.rb +11 -0
- data/spec/models/space_key_spec.rb +68 -0
- data/spec/models/user_spec.rb +80 -0
- data/spec/mongo_mapper/basic_spec.rb +41 -0
- data/spec/mongo_mapper/helper.rb +10 -0
- data/spec/spec.opts +4 -0
- metadata +138 -0
@@ -0,0 +1,88 @@
|
|
1
|
+
class Role
|
2
|
+
ORDERED_ROLES = %w{manager member user}
|
3
|
+
SYSTEM_ROLES = %w{admin anonymous manager member owner registered user}.sort.freeze
|
4
|
+
PRESERVED_USER_NAMES = (SYSTEM_ROLES + ['admin']).sort.freeze
|
5
|
+
|
6
|
+
class << self
|
7
|
+
|
8
|
+
def normalize_roles roles
|
9
|
+
ordinary_roles, ordered_roles = split roles
|
10
|
+
ordinary_roles << lower_role(ordered_roles)
|
11
|
+
ordinary_roles.sort
|
12
|
+
end
|
13
|
+
|
14
|
+
def denormalize_to_higher_roles roles
|
15
|
+
ordinary_roles, ordered_roles = split roles
|
16
|
+
ordinary_roles.push *higher_roles(lower_role(ordered_roles))
|
17
|
+
ordinary_roles.sort
|
18
|
+
end
|
19
|
+
|
20
|
+
def denormalize_to_lower_roles roles
|
21
|
+
ordinary_roles, ordered_roles = split roles
|
22
|
+
ordinary_roles.push *lower_roles(higher_role(ordered_roles))
|
23
|
+
ordinary_roles.sort
|
24
|
+
end
|
25
|
+
|
26
|
+
def higher_role roles
|
27
|
+
ORDERED_ROLES.each do |role|
|
28
|
+
return role if roles.include? role
|
29
|
+
end
|
30
|
+
nil
|
31
|
+
end
|
32
|
+
|
33
|
+
def lower_role roles
|
34
|
+
ORDERED_ROLES.reverse.each do |role|
|
35
|
+
return role if roles.include? role
|
36
|
+
end
|
37
|
+
nil
|
38
|
+
end
|
39
|
+
|
40
|
+
def major_roles roles
|
41
|
+
major_roles = roles.select{|role| !SYSTEM_ROLES.include?(role)}
|
42
|
+
if higher_role = higher_role(roles)
|
43
|
+
major_roles << higher_role
|
44
|
+
end
|
45
|
+
major_roles.sort
|
46
|
+
end
|
47
|
+
|
48
|
+
def minor_roles roles
|
49
|
+
minor_roles = roles.select{|role| !SYSTEM_ROLES.include?(role)}
|
50
|
+
if lower_role = lower_role(roles)
|
51
|
+
minor_roles << lower_role
|
52
|
+
end
|
53
|
+
minor_roles.sort
|
54
|
+
end
|
55
|
+
|
56
|
+
protected
|
57
|
+
def split roles
|
58
|
+
ordinary_roles = []
|
59
|
+
ordered_roles = []
|
60
|
+
|
61
|
+
roles.collect do |role|
|
62
|
+
if ORDERED_ROLES.include? role
|
63
|
+
ordered_roles << role
|
64
|
+
else
|
65
|
+
ordinary_roles << role
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
[ordinary_roles, ordered_roles]
|
70
|
+
end
|
71
|
+
|
72
|
+
def lower_roles role
|
73
|
+
return [] if role.nil?
|
74
|
+
|
75
|
+
role.must_be.in ORDERED_ROLES
|
76
|
+
index = ORDERED_ROLES.index role
|
77
|
+
ORDERED_ROLES[index..-1]
|
78
|
+
end
|
79
|
+
|
80
|
+
def higher_roles role
|
81
|
+
return [] if role.nil?
|
82
|
+
|
83
|
+
role.must_be.in ORDERED_ROLES
|
84
|
+
index = ORDERED_ROLES.index role
|
85
|
+
ORDERED_ROLES[0..index]
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
class SecureToken
|
2
|
+
include MongoMapper::Document
|
3
|
+
|
4
|
+
connect_to_global_database
|
5
|
+
|
6
|
+
key :_type, String
|
7
|
+
|
8
|
+
key :values, Hash
|
9
|
+
key :token, String, :default => lambda{String.secure_token}
|
10
|
+
key :expires_at, Time, :default => lambda{30.minutes.from_now}
|
11
|
+
timestamps!
|
12
|
+
|
13
|
+
validates_presence_of :token, :expires_at
|
14
|
+
|
15
|
+
def expired?
|
16
|
+
expires_at >= Time.now.utc
|
17
|
+
end
|
18
|
+
|
19
|
+
# defer do
|
20
|
+
ensure_index :token, :unique => true
|
21
|
+
ensure_index :expires_at
|
22
|
+
ensure_index :user_id
|
23
|
+
# end
|
24
|
+
|
25
|
+
def self.by_token token
|
26
|
+
return nil if token.blank?
|
27
|
+
first :token => token, :expires_at.gte => Time.now.utc
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.by_token! token
|
31
|
+
return by_token(token) || raise(MongoMapper::DocumentNotFound)
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
class Space
|
2
|
+
include MongoMapper::Document
|
3
|
+
|
4
|
+
#
|
5
|
+
# Multitenant
|
6
|
+
#
|
7
|
+
connect_to_global_database
|
8
|
+
|
9
|
+
|
10
|
+
key :name, String, :protected => true
|
11
|
+
key :title, String
|
12
|
+
key :account_id, ObjectId, :protected => true
|
13
|
+
|
14
|
+
timestamps!
|
15
|
+
|
16
|
+
|
17
|
+
#
|
18
|
+
# Indexes
|
19
|
+
#
|
20
|
+
# defer do
|
21
|
+
ensure_index :name
|
22
|
+
ensure_index :account_id
|
23
|
+
# end
|
24
|
+
|
25
|
+
def default?; name == 'default' end
|
26
|
+
def self.default? name; name == 'default' end
|
27
|
+
|
28
|
+
|
29
|
+
belongs_to :account
|
30
|
+
|
31
|
+
validates_presence_of :name, :account
|
32
|
+
validates_uniqueness_of :name, :scope => :account_id
|
33
|
+
validates_format_of :name, :with => STRONG_NAME
|
34
|
+
|
35
|
+
def self.current= space
|
36
|
+
Thread.current['current_space'] = space
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.current
|
40
|
+
Thread.current['current_space'].must_be.defined
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.current?
|
44
|
+
!!Thread.current['current_space']
|
45
|
+
end
|
46
|
+
|
47
|
+
#
|
48
|
+
# Validation
|
49
|
+
#
|
50
|
+
validate :validate_default
|
51
|
+
def validate_default
|
52
|
+
errors.add :base, t(:forbiden_to_change_default_space) if name_changed? and name_was == 'default'
|
53
|
+
end
|
54
|
+
protected :validate_default
|
55
|
+
|
56
|
+
|
57
|
+
#
|
58
|
+
# Roles and Permissions
|
59
|
+
#
|
60
|
+
key :custom_roles, Array
|
61
|
+
def custom_roles_as_string
|
62
|
+
custom_roles.join("\n")
|
63
|
+
end
|
64
|
+
def custom_roles_as_string= str
|
65
|
+
self.custom_roles = str.strip.split("\n")
|
66
|
+
end
|
67
|
+
|
68
|
+
SPECIAL_PERMISSIONS = {
|
69
|
+
'global_administration' => ['admin'],
|
70
|
+
'account_administration' => ['admin'],
|
71
|
+
'view' => ['owner', 'manager']
|
72
|
+
}
|
73
|
+
|
74
|
+
def permissions
|
75
|
+
self.class.permissions
|
76
|
+
end
|
77
|
+
|
78
|
+
@@permissions = nil
|
79
|
+
def self.permissions
|
80
|
+
unless @@permissions
|
81
|
+
@@permissions = YAML.load_file("#{File.dirname __FILE__}/default_permissions.yml")
|
82
|
+
@@permissions.merge!(SPECIAL_PERMISSIONS)
|
83
|
+
end
|
84
|
+
@@permissions
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
#
|
89
|
+
# Links
|
90
|
+
#
|
91
|
+
key :default_url, String
|
92
|
+
key :menu, Array
|
93
|
+
|
94
|
+
def menu_as_string
|
95
|
+
menu.to_a.collect{|name, url| "#{name}:#{url}"}.join("\n")
|
96
|
+
end
|
97
|
+
|
98
|
+
def menu_as_string=(str)
|
99
|
+
self.menu = []
|
100
|
+
lines = str.split("\n")
|
101
|
+
lines.each do |line|
|
102
|
+
name, url = line.split(':').collect(&:strip)
|
103
|
+
menu << [name, url] unless name.blank? or url.blank?
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
#
|
109
|
+
# Language
|
110
|
+
#
|
111
|
+
AVAILABLE_LANGUAGES = %w{en ru}
|
112
|
+
crystal.after :config do |config|
|
113
|
+
key :language, String, :default => config.default_language('en')
|
114
|
+
end
|
115
|
+
|
116
|
+
|
117
|
+
#
|
118
|
+
# Files audit
|
119
|
+
#
|
120
|
+
key :max_user_files_size, Integer, :default => 0
|
121
|
+
|
122
|
+
|
123
|
+
#
|
124
|
+
# Other
|
125
|
+
#
|
126
|
+
plugin MongoMapper::Plugins::TextProcessor
|
127
|
+
markup_key :bottom_text
|
128
|
+
|
129
|
+
|
130
|
+
#
|
131
|
+
# Theme support
|
132
|
+
#
|
133
|
+
key :theme, String, :default => 'default'
|
134
|
+
def self.available_themes
|
135
|
+
config.available_themes(['default'])
|
136
|
+
end
|
137
|
+
# defer do
|
138
|
+
validates_inclusion_of :theme, :within => Space.available_themes
|
139
|
+
# end
|
140
|
+
|
141
|
+
include Paperclip
|
142
|
+
has_attached_file :logo
|
143
|
+
validates_maximum_file_size :logo
|
144
|
+
|
145
|
+
def slug; name end
|
146
|
+
|
147
|
+
|
148
|
+
#
|
149
|
+
# Wigets
|
150
|
+
#
|
151
|
+
# has_many :resources #, :dependent => :destroy
|
152
|
+
# has_many :votes #, :dependent => :destroy
|
153
|
+
|
154
|
+
|
155
|
+
# def self.account_inheritable_key key, type, options = {}
|
156
|
+
# key = key.to_s
|
157
|
+
#
|
158
|
+
# self.key key, type, options
|
159
|
+
# Account.send :key, key, type, options
|
160
|
+
#
|
161
|
+
# define_method key do
|
162
|
+
# unless merged_value = cache[key]
|
163
|
+
# account_value = account.send akey
|
164
|
+
# value = send(key)
|
165
|
+
# merged_value = merge account_value, value
|
166
|
+
# cache[key] = merged_value
|
167
|
+
# end
|
168
|
+
# merged_value
|
169
|
+
# end
|
170
|
+
# end
|
171
|
+
#
|
172
|
+
# protected
|
173
|
+
# def self.merge parent_value, value
|
174
|
+
# return value || parent_value if value.nil? or parent_value.nil?
|
175
|
+
#
|
176
|
+
# if parent_value.is_a? Hash
|
177
|
+
# parent_value.merge(value)
|
178
|
+
# elsif parent_value.is_a? Array
|
179
|
+
# (parent_value + value).uniq
|
180
|
+
# else
|
181
|
+
# value
|
182
|
+
# end
|
183
|
+
# end
|
184
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
class User
|
2
|
+
include MongoMapper::Document
|
3
|
+
|
4
|
+
connect_to_global_database
|
5
|
+
|
6
|
+
set_collection_name 'users'
|
7
|
+
|
8
|
+
key :_type, String
|
9
|
+
|
10
|
+
key :name, String, :protected => true
|
11
|
+
key :email, String, :protected => true
|
12
|
+
key :state, String, :protected => true
|
13
|
+
timestamps!
|
14
|
+
|
15
|
+
def name= value
|
16
|
+
# write_attribute :name, (value ? value.downcase : nil)
|
17
|
+
super(value ? value.downcase : nil)
|
18
|
+
end
|
19
|
+
|
20
|
+
def email= value
|
21
|
+
# write_attribute :email, (value ? value.downcase : nil)
|
22
|
+
super(value ? value.downcase : nil)
|
23
|
+
end
|
24
|
+
|
25
|
+
# key :remember_token, String, :protected => true
|
26
|
+
# key :remember_token_expires_at, Time, :protected => true
|
27
|
+
# key :secure_token, String, :protected => true
|
28
|
+
# key :secure_token_expires_at, Time, :protected => true
|
29
|
+
|
30
|
+
#
|
31
|
+
# Validations
|
32
|
+
#
|
33
|
+
validates_presence_of :name
|
34
|
+
validates_length_of :name, :within => 4..40
|
35
|
+
validates_uniqueness_of :name
|
36
|
+
validates_format_of :name, :with => STRONG_NAME
|
37
|
+
|
38
|
+
EMAIL_NAME_REGEX = '[\w\.%\+\-]+'.freeze
|
39
|
+
DOMAIN_HEAD_REGEX = '(?:[A-Z0-9\-]+\.)+'.freeze
|
40
|
+
DOMAIN_TLD_REGEX = '(?:[A-Z]{2}|com|org|net|edu|gov|mil|biz|info|mobi|name|aero|jobs|museum)'.freeze
|
41
|
+
EMAIL_REGEX = /\A#{EMAIL_NAME_REGEX}@#{DOMAIN_HEAD_REGEX}#{DOMAIN_TLD_REGEX}\z/i
|
42
|
+
|
43
|
+
validates_length_of :email, :within => 6..100, :allow_blank => true
|
44
|
+
validates_uniqueness_of :email, :allow_blank => true
|
45
|
+
validates_format_of :email, :with => EMAIL_REGEX, :allow_blank => true
|
46
|
+
|
47
|
+
|
48
|
+
#
|
49
|
+
# Indexes
|
50
|
+
#
|
51
|
+
# defer do
|
52
|
+
ensure_index :name, :unique => true
|
53
|
+
ensure_index :email
|
54
|
+
# ensure_index :remember_token #, :unique => true
|
55
|
+
ensure_index :state
|
56
|
+
ensure_index :created_at
|
57
|
+
ensure_index :updated_at
|
58
|
+
# end
|
59
|
+
|
60
|
+
|
61
|
+
#
|
62
|
+
# Autentication
|
63
|
+
#
|
64
|
+
plugin MongoMapper::Plugins::OpenIdAuthentication
|
65
|
+
acts_as_authenticated_by_open_id
|
66
|
+
|
67
|
+
plugin MongoMapper::Plugins::PasswordAuthentication
|
68
|
+
acts_as_authenticated_by_password
|
69
|
+
|
70
|
+
def validate_authentication
|
71
|
+
if crypted_password.blank? and open_ids.blank?
|
72
|
+
errors.add :password, t(:should_not_be_blank)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
protected :validate_authentication
|
76
|
+
validate :validate_authentication
|
77
|
+
|
78
|
+
|
79
|
+
#
|
80
|
+
# Lifecycle
|
81
|
+
#
|
82
|
+
state_machine :state, :initial => :inactive do
|
83
|
+
|
84
|
+
# after_transition :on => :wait_for_email_confirmation do |_self, trans|
|
85
|
+
# _self.generate_secure_token!
|
86
|
+
# UserStatusMailer.deliver_signup_notification _self
|
87
|
+
# end
|
88
|
+
|
89
|
+
# after_transition :on => :activate do |_self, trans|
|
90
|
+
# _self.clear_secure_token!
|
91
|
+
# UserStatusMailer.deliver_activation_notification _self
|
92
|
+
# end
|
93
|
+
|
94
|
+
# on :wait_for_email_confirmation do
|
95
|
+
# transition any => :inactive
|
96
|
+
# end
|
97
|
+
|
98
|
+
on :activate do
|
99
|
+
transition all => :active
|
100
|
+
end
|
101
|
+
|
102
|
+
on :inactivate do
|
103
|
+
transition all => :inactive
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
#
|
110
|
+
# Authorization
|
111
|
+
#
|
112
|
+
plugin MongoMapper::Plugins::SpaceKeys
|
113
|
+
plugin MongoMapper::Plugins::Authorized
|
114
|
+
acts_as_authorized
|
115
|
+
|
116
|
+
|
117
|
+
#
|
118
|
+
# Profile
|
119
|
+
#
|
120
|
+
key :first_name, String
|
121
|
+
key :last_name, String
|
122
|
+
|
123
|
+
|
124
|
+
#
|
125
|
+
# Helpers
|
126
|
+
#
|
127
|
+
class << self
|
128
|
+
|
129
|
+
def [] name
|
130
|
+
find_by_name name.to_s
|
131
|
+
end
|
132
|
+
|
133
|
+
def current= current
|
134
|
+
Thread.current['current_user'] = current
|
135
|
+
end
|
136
|
+
|
137
|
+
def current
|
138
|
+
Thread.current['current_user'].must_be.defined
|
139
|
+
end
|
140
|
+
|
141
|
+
def current?
|
142
|
+
Thread.current['current_user'] != nil
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
|
147
|
+
|
148
|
+
#
|
149
|
+
# Other
|
150
|
+
#
|
151
|
+
space_key :files_size, Integer, :default => 0
|
152
|
+
def to_param; name end
|
153
|
+
validate do |u|
|
154
|
+
u.space_keys_containers.size.must == 0 if u.anonymous?
|
155
|
+
end
|
156
|
+
|
157
|
+
def slug; name end
|
158
|
+
end
|
data/lib/rad/models.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# support
|
2
|
+
require 'state_machine'
|
3
|
+
require 'crystal/environment'
|
4
|
+
require 'rad/mongo_mapper'
|
5
|
+
require 'rad/paperclip'
|
6
|
+
|
7
|
+
# config
|
8
|
+
# use database in config if provided
|
9
|
+
if crystal.include? :config and crystal.config.database?
|
10
|
+
config = crystal.config
|
11
|
+
db_config_for_current_environment = config.database!.send("#{config.environment}!").to_hash
|
12
|
+
MongoMapper.db_config = db_config_for_current_environment
|
13
|
+
|
14
|
+
|
15
|
+
# hide index creation from logging
|
16
|
+
# MongoMapper.logger.info "Checking and creating MongoMapper indexes"
|
17
|
+
# MongoMapper.temporary_silince_logger do
|
18
|
+
# MongoMapper.call_deferred
|
19
|
+
# end
|
20
|
+
end
|
21
|
+
|
22
|
+
MongoMapper.logger = crystal.logger
|
23
|
+
|
24
|
+
# models
|
25
|
+
module Rad
|
26
|
+
def self.multitenant_mode?
|
27
|
+
Space.current?
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
%w(
|
32
|
+
micelaneous
|
33
|
+
role
|
34
|
+
|
35
|
+
secure_token
|
36
|
+
user
|
37
|
+
account
|
38
|
+
space
|
39
|
+
).each{|n| require "rad/models/#{n}"}
|
40
|
+
|
41
|
+
require 'rad/locales'
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module MongoMapper
|
2
|
+
module Plugins
|
3
|
+
module OpenIdAuthentication
|
4
|
+
|
5
|
+
module InstanceMethods
|
6
|
+
def authenticated_by_open_id? open_id
|
7
|
+
self.open_id == open_id
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
def acts_as_authenticated_by_open_id
|
13
|
+
key :open_ids, Array
|
14
|
+
|
15
|
+
# defer do
|
16
|
+
ensure_index :open_ids
|
17
|
+
# end
|
18
|
+
|
19
|
+
validates_uniqueness_of :open_ids, :allow_blank => true
|
20
|
+
end
|
21
|
+
|
22
|
+
def authenticate_by_open_id open_id
|
23
|
+
return nil if open_id.blank?
|
24
|
+
User.first :conditions => {:state => 'active', :open_ids => open_id}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
module MongoMapper
|
2
|
+
module Plugins
|
3
|
+
module PasswordAuthentication
|
4
|
+
SITE_KEY = '3eed5a60c1bf8d43de5d0560e9fc2442fe74fdad'
|
5
|
+
DIGEST_STRETCHES = 10
|
6
|
+
PASSWORD_LENGTH = 3..40
|
7
|
+
|
8
|
+
|
9
|
+
module InstanceMethods
|
10
|
+
def authenticated_by_password? password
|
11
|
+
return false if crypted_password.blank? or password.blank?
|
12
|
+
self.crypted_password == self.class.encrypt_password(password, salt)
|
13
|
+
end
|
14
|
+
|
15
|
+
# def reset_password password, password_confirmation
|
16
|
+
# self.password, self.password_confirmation = password, password_confirmation
|
17
|
+
# self.secure_token = nil
|
18
|
+
# end
|
19
|
+
|
20
|
+
def update_password password, password_confirmation, old_password
|
21
|
+
if crypted_password.blank?
|
22
|
+
self.password, self.password_confirmation = password, password_confirmation
|
23
|
+
elsif authenticated_by_password? old_password
|
24
|
+
self.password, self.password_confirmation = password, password_confirmation
|
25
|
+
true
|
26
|
+
else
|
27
|
+
errors.add :base, t(:invalid_old_password)
|
28
|
+
false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# def update_password password, confirmation
|
33
|
+
# self.password, self.password_confirmation = password, confirmation
|
34
|
+
# encrypt_password
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# def generate_secure_token!
|
38
|
+
# self.secure_token = AuthStrategy.generate_token
|
39
|
+
# self.secure_token_expires_at = AuthStrategy::SECURE_TOKEN_EXPIRATION.from_now
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# def clear_secure_token!
|
43
|
+
# self.secure_token = nil
|
44
|
+
# self.secure_token_expires_at = nil
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# def forgot_password
|
48
|
+
# generate_secure_token!
|
49
|
+
# UserStatusMailer.deliver_forgot_password self
|
50
|
+
# end
|
51
|
+
|
52
|
+
def password= password
|
53
|
+
@password = password
|
54
|
+
encrypt_password!
|
55
|
+
end
|
56
|
+
|
57
|
+
protected
|
58
|
+
def encrypt_password!
|
59
|
+
if password.blank?
|
60
|
+
self.crypted_password = ""
|
61
|
+
else
|
62
|
+
self.salt ||= self.class.generate_token
|
63
|
+
self.crypted_password = self.class.encrypt_password password, salt
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def validate_password?
|
68
|
+
!password.nil?
|
69
|
+
# crypted_password.blank? or !password.blank?
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
module ClassMethods
|
74
|
+
def acts_as_authenticated_by_password
|
75
|
+
key :crypted_password, String, :protected => true
|
76
|
+
key :salt, String, :protected => true
|
77
|
+
|
78
|
+
attr_reader :password
|
79
|
+
validates_confirmation_of :password, :if => :validate_password?
|
80
|
+
validates_length_of :password, :within => PASSWORD_LENGTH, :if => :validate_password?
|
81
|
+
end
|
82
|
+
|
83
|
+
def authenticate_by_password name, password
|
84
|
+
return nil if name.blank? or password.blank?
|
85
|
+
u = User.first :conditions => {:state => 'active', :name => name}
|
86
|
+
u && u.authenticated_by_password?(password) ? u : nil
|
87
|
+
end
|
88
|
+
|
89
|
+
# def by_secure_token token
|
90
|
+
# first :conditions => {
|
91
|
+
# :secure_token => token,
|
92
|
+
# :secure_token_expires_at => {:$gt => Time.now.utc}
|
93
|
+
# }
|
94
|
+
# end
|
95
|
+
#
|
96
|
+
# def by_open_id id
|
97
|
+
# return nil if id.blank?
|
98
|
+
# first :open_ids => id
|
99
|
+
# end
|
100
|
+
|
101
|
+
def encrypt_password password, salt
|
102
|
+
digest = SITE_KEY
|
103
|
+
DIGEST_STRETCHES.times do
|
104
|
+
digest = secure_digest(digest, salt, password, SITE_KEY)
|
105
|
+
end
|
106
|
+
digest
|
107
|
+
end
|
108
|
+
|
109
|
+
def generate_token
|
110
|
+
secure_digest Time.now, (1..10).map{ rand.to_s }
|
111
|
+
end
|
112
|
+
|
113
|
+
protected
|
114
|
+
def secure_digest *args
|
115
|
+
Digest::SHA1.hexdigest(args.flatten.join('--'))
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|