rapid 0.0.1

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