rapid 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. data/Rakefile +66 -0
  2. data/lib/rad/http_controller/acts_as/authenticated.rb +131 -0
  3. data/lib/rad/http_controller/acts_as/authenticated_master_domain.rb +119 -0
  4. data/lib/rad/http_controller/acts_as/authorized.rb +83 -0
  5. data/lib/rad/http_controller/acts_as/localized.rb +27 -0
  6. data/lib/rad/http_controller/acts_as/multitenant.rb +53 -0
  7. data/lib/rad/http_controller/helpers/service_mix_helper.rb +50 -0
  8. data/lib/rad/http_controller.rb +15 -0
  9. data/lib/rad/lib/text_utils.rb +334 -0
  10. data/lib/rad/locales/en.yml +80 -0
  11. data/lib/rad/locales/ru.yml +83 -0
  12. data/lib/rad/locales.rb +2 -0
  13. data/lib/rad/models/account.rb +88 -0
  14. data/lib/rad/models/default_permissions.yml +26 -0
  15. data/lib/rad/models/micelaneous.rb +1 -0
  16. data/lib/rad/models/role.rb +88 -0
  17. data/lib/rad/models/secure_token.rb +33 -0
  18. data/lib/rad/models/space.rb +184 -0
  19. data/lib/rad/models/user.rb +158 -0
  20. data/lib/rad/models.rb +41 -0
  21. data/lib/rad/mongo_mapper/acts_as/authenticated_by_open_id.rb +29 -0
  22. data/lib/rad/mongo_mapper/acts_as/authenticated_by_password.rb +120 -0
  23. data/lib/rad/mongo_mapper/acts_as/authorized.rb +197 -0
  24. data/lib/rad/mongo_mapper/acts_as/authorized_object.rb +171 -0
  25. data/lib/rad/mongo_mapper/multitenant.rb +34 -0
  26. data/lib/rad/mongo_mapper/rad_micelaneous.rb +43 -0
  27. data/lib/rad/mongo_mapper/space_keys.rb +62 -0
  28. data/lib/rad/mongo_mapper/text_processor.rb +47 -0
  29. data/lib/rad/mongo_mapper.rb +20 -0
  30. data/lib/rad/paperclip/callbacks.rb +40 -0
  31. data/lib/rad/paperclip/extensions.rb +64 -0
  32. data/lib/rad/paperclip/integration.rb +165 -0
  33. data/lib/rad/paperclip/mime.rb +5 -0
  34. data/lib/rad/paperclip/validations.rb +64 -0
  35. data/lib/rad/paperclip.rb +11 -0
  36. data/lib/rad/spec/controller.rb +51 -0
  37. data/lib/rad/spec/model/factories.rb +65 -0
  38. data/lib/rad/spec/model.rb +85 -0
  39. data/lib/rad/spec/rem_helper.rb +145 -0
  40. data/lib/rad/spec.rb +4 -0
  41. data/lib/rad/tasks/backup.rake +64 -0
  42. data/lib/rad/tasks/initialize.rake +35 -0
  43. data/lib/rad.rb +32 -0
  44. data/readme.md +3 -0
  45. data/spec/controller/authorization_spec.rb +146 -0
  46. data/spec/controller/helper.rb +14 -0
  47. data/spec/lib/helper.rb +7 -0
  48. data/spec/lib/text_utils_spec.rb +238 -0
  49. data/spec/models/authorization_spec.rb +93 -0
  50. data/spec/models/authorized_object_spec.rb +258 -0
  51. data/spec/models/file_audit_spec/100.txt +1 -0
  52. data/spec/models/file_audit_spec/302.txt +3 -0
  53. data/spec/models/file_audit_spec.rb +168 -0
  54. data/spec/models/helper.rb +11 -0
  55. data/spec/models/space_key_spec.rb +68 -0
  56. data/spec/models/user_spec.rb +80 -0
  57. data/spec/mongo_mapper/basic_spec.rb +41 -0
  58. data/spec/mongo_mapper/helper.rb +10 -0
  59. data/spec/spec.opts +4 -0
  60. 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