ixtlan 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/Manifest.txt +64 -0
  2. data/README.txt +86 -0
  3. data/Rakefile +38 -0
  4. data/generators/ixtlan_datamapper_model/ixtlan_datamapper_model_generator.rb +20 -0
  5. data/generators/ixtlan_datamapper_model/templates/model.rb +17 -0
  6. data/generators/ixtlan_datamapper_rspec_model/ixtlan_datamapper_rspec_model_generator.rb +20 -0
  7. data/generators/ixtlan_datamapper_rspec_model/templates/model_spec.rb +58 -0
  8. data/generators/ixtlan_datamapper_rspec_scaffold/ixtlan_datamapper_rspec_scaffold_generator.rb +37 -0
  9. data/generators/ixtlan_datamapper_rspec_scaffold/templates/controller.rb +99 -0
  10. data/generators/ixtlan_datamapper_rspec_scaffold/templates/controller_spec.rb +206 -0
  11. data/generators/ixtlan_datamapper_rspec_scaffold/templates/guard.rb +8 -0
  12. data/generators/ixtlan_datamapper_rspec_scaffold/templates/i18n.rb +11 -0
  13. data/lib/dm-serializer/common.rb +28 -0
  14. data/lib/dm-serializer/to_xml.rb +99 -0
  15. data/lib/dm-serializer/xml_serializers/libxml.rb +42 -0
  16. data/lib/dm-serializer/xml_serializers/nokogiri.rb +36 -0
  17. data/lib/dm-serializer/xml_serializers/rexml.rb +30 -0
  18. data/lib/dm-serializer/xml_serializers.rb +17 -0
  19. data/lib/dm-serializer.rb +1 -0
  20. data/lib/ixtlan/audit_config.rb +35 -0
  21. data/lib/ixtlan/cms_script.rb +29 -0
  22. data/lib/ixtlan/controllers/texts_controller.rb +65 -0
  23. data/lib/ixtlan/digest.rb +15 -0
  24. data/lib/ixtlan/error_notifier/error_notification.rhtml +1 -0
  25. data/lib/ixtlan/guard.rb +125 -0
  26. data/lib/ixtlan/logger_config.rb +65 -0
  27. data/lib/ixtlan/models/authentication.rb +30 -0
  28. data/lib/ixtlan/models/configuration.rb +74 -0
  29. data/lib/ixtlan/models/configuration_locale.rb +19 -0
  30. data/lib/ixtlan/models/group.rb +69 -0
  31. data/lib/ixtlan/models/group_locale_user.rb +22 -0
  32. data/lib/ixtlan/models/group_user.rb +39 -0
  33. data/lib/ixtlan/models/locale.rb +37 -0
  34. data/lib/ixtlan/models/permission.rb +29 -0
  35. data/lib/ixtlan/models/phrase.rb +71 -0
  36. data/lib/ixtlan/models/role.rb +35 -0
  37. data/lib/ixtlan/models/text.rb +140 -0
  38. data/lib/ixtlan/models/translation.rb +40 -0
  39. data/lib/ixtlan/models/user.rb +112 -0
  40. data/lib/ixtlan/models/word.rb +12 -0
  41. data/lib/ixtlan/models.rb +13 -0
  42. data/lib/ixtlan/modified_by.rb +80 -0
  43. data/lib/ixtlan/monkey_patches.rb +38 -0
  44. data/lib/ixtlan/optimistic_persistence.rb +20 -0
  45. data/lib/ixtlan/optimistic_persistence_module.rb +22 -0
  46. data/lib/ixtlan/optimistic_persistence_validation.rb +21 -0
  47. data/lib/ixtlan/passwords.rb +19 -0
  48. data/lib/ixtlan/rails/audit.rb +22 -0
  49. data/lib/ixtlan/rails/error_handling.rb +130 -0
  50. data/lib/ixtlan/rails/guard.rb +11 -0
  51. data/lib/ixtlan/rails/session_timeout.rb +93 -0
  52. data/lib/ixtlan/rails/timestamps_modified_by_filter.rb +33 -0
  53. data/lib/ixtlan/rails/unrestful_authentication.rb +137 -0
  54. data/lib/ixtlan/rolling_file.rb +61 -0
  55. data/lib/ixtlan/session.rb +18 -0
  56. data/lib/ixtlan/user_logger.rb +49 -0
  57. data/lib/ixtlan/version.rb +3 -0
  58. data/lib/ixtlan.rb +2 -0
  59. data/lib/models.rb +14 -0
  60. data/spec/authentication_spec.rb +30 -0
  61. data/spec/guard_spec.rb +125 -0
  62. data/spec/guards/samples.rb +12 -0
  63. data/spec/spec.opts +1 -0
  64. data/spec/spec_helper.rb +170 -0
  65. metadata +210 -0
@@ -0,0 +1,74 @@
1
+ require 'dm-core'
2
+ require 'ixtlan/modified_by'
3
+ module Ixtlan
4
+ module Models
5
+ class Configuration
6
+ include DataMapper::Resource
7
+
8
+ def self.default_storage_name
9
+ "Configuration"
10
+ end
11
+
12
+ property :id, Serial
13
+
14
+ property :session_idle_timeout, Integer, :nullable => false
15
+
16
+ property :keep_audit_logs, Integer, :nullable => false
17
+
18
+ property :notification_sender_email, String, :format => :email, :nullable => true, :length => 64
19
+
20
+ property :notification_recipient_emails, String, :format => Proc.new { |email| emails = email.split(','); emails.find_all { |e| e =~ DataMapper::Validate::Format::Email::EmailAddress }.size == emails.size}, :nullable => true, :length => 254 #honour mysql max varchar length
21
+
22
+ timestamps :updated_at
23
+
24
+ modified_by ::Ixtlan::Models::USER, :updated_by
25
+
26
+ def locales
27
+ if @locales.nil?
28
+ # TODO spec the empty array to make sure new relations are stored
29
+ # in the database or the locales collection is empty before filling it
30
+ @locales = ::DataMapper::Collection.new(::DataMapper::Query.new(self.repository, Object.full_const_get(::Ixtlan::Models::LOCALE)), [])
31
+ ConfigurationLocale.all(:configuration_id => id).each{ |cl| @locales << cl.locale }
32
+ def @locales.configuration=(configuration)
33
+ @configuration = configuration
34
+ end
35
+ @locales.configuration = self
36
+ def @locales.<<(locale)
37
+ unless member? locale
38
+ ConfigurationLocale.create(:configuration_id => @configuration.id, :locale_code => locale.code)
39
+ super
40
+ end
41
+
42
+ self
43
+ end
44
+
45
+ def @locales.delete(locale)
46
+ cl = ConfigurationLocale.first(:configuration_id => @configuration.id, :user_id => @user.id, :locale_code => locale.code)
47
+ if cl
48
+ cl.destroy
49
+ end
50
+ super
51
+ end
52
+ end
53
+ @locales
54
+ end
55
+
56
+ def self.instance
57
+ # HACK: return a new object in case there is none in the database
58
+ # to allow rails rake tasks to work with an empty database
59
+ begin
60
+ get(1) || new(:id => 1, :session_idle_timeout => 5, :keep_audit_logs => 7)
61
+ rescue
62
+ auto_migrate!
63
+ new(:id => 1, :session_idle_timeout => 5, :keep_audit_logs => 7)
64
+ end
65
+ end
66
+
67
+ alias :to_x :to_xml_document
68
+ def to_xml_document(opts, doc = nil)
69
+ opts.merge!({:methods => [:updated_by, :locales], :exclude => [:updated_by_id, :id]})
70
+ to_x(opts, doc)
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,19 @@
1
+ module Ixtlan
2
+ module Models
3
+ class ConfigurationLocale
4
+
5
+ include DataMapper::Resource
6
+
7
+ def self.default_storage_name
8
+ "ConfigurationLocale"
9
+ end
10
+
11
+ property :configuration_id, Integer, :key => true
12
+
13
+ property :locale_code, String, :key => true
14
+
15
+ belongs_to :configuration, :model => Models::CONFIGURATION
16
+ belongs_to :locale, :model => Models::LOCALE
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,69 @@
1
+ module Ixtlan
2
+ module Models
3
+ class Group
4
+
5
+ include DataMapper::Resource
6
+
7
+ def self.default_storage_name
8
+ "Group"
9
+ end
10
+
11
+ property :id, Serial, :field => "gidnumber"
12
+
13
+ property :name, String, :nullable => false , :format => /^[^<'&">]*$/, :length => 32, :field => "cn", :unique_index => true
14
+
15
+ timestamps :created_at
16
+
17
+ modified_by Models::USER
18
+
19
+ def locales(user = nil)
20
+ # return ::DataMapper::Collection.new(::DataMapper::Query.new(self.repository, Locale), [])
21
+ if @locales.nil? or not user.nil?
22
+
23
+ # TODO spec the empty array to make sure new relations are stored
24
+ # in the database or the locales collection is empty before filling it
25
+ @locales = ::DataMapper::Collection.new(::DataMapper::Query.new(self.repository, Models::Locale), [])
26
+
27
+ return @locales if user.nil?
28
+
29
+ GroupLocaleUser.all(:group_id => id, :user_id => user.id).each{ |glu| @locales << glu.locale }
30
+ def @locales.group=(group)
31
+ @group = group
32
+ end
33
+ @locales.group = self
34
+ def @locales.user=(user)
35
+ @user = user
36
+ end
37
+ @locales.user = user
38
+ def @locales.<<(locale)
39
+ unless member? locale
40
+ GroupLocaleUser.create(:group_id => @group.id, :user_id => @user.id, :locale_code => locale.code)
41
+ super
42
+ end
43
+
44
+ self
45
+ end
46
+ def @locales.delete(locale)
47
+ glu = GroupLocaleUser.first(:group_id => @group.id, :user_id => @user.id, :locale_code => locale.code)
48
+ if glu
49
+ glu.destroy
50
+ end
51
+ super
52
+ end
53
+ end
54
+ @locales
55
+ end
56
+
57
+ if protected_instance_methods.find {|m| m == 'to_x'}.nil?
58
+
59
+ protected
60
+
61
+ alias :to_x :to_xml_document
62
+ def to_xml_document(opts, doc = nil)
63
+ opts.merge!({:methods => [:locales]})
64
+ to_x(opts, doc)
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,22 @@
1
+ module Ixtlan
2
+ module Models
3
+ class GroupLocaleUser
4
+
5
+ include DataMapper::Resource
6
+
7
+ def self.default_storage_name
8
+ "GroupLocaleUser"
9
+ end
10
+
11
+ property :group_id, Integer, :key => true
12
+
13
+ property :user_id, Integer, :key => true
14
+
15
+ property :locale_code, String, :key => true
16
+
17
+ belongs_to :group, :model => Models::GROUP
18
+ belongs_to :user, :model => Models::USER
19
+ belongs_to :locale, :model => Models::LOCALE
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,39 @@
1
+ module Ixtlan
2
+ module Models
3
+ class GroupUser
4
+ include DataMapper::Resource
5
+
6
+ def self.default_storage_name
7
+ "GroupUser"
8
+ end
9
+
10
+ # dn_prefix { |group_user| "cn=#{group_user.group.name}" }
11
+
12
+ #treebase "ou=groups"
13
+
14
+ # multivalue_field :memberuid
15
+
16
+ #ldap_properties do |group_user|
17
+ # {:cn=>"#{group_user.group.name}", :objectclass => "posixGroup"}
18
+ # end
19
+ property :memberuid, String, :key => true
20
+ property :gidnumber, Integer, :key => true
21
+
22
+ def group
23
+ Object.full_const_get(Models::GROUP).get!(gidnumber)
24
+ end
25
+
26
+ def group=(group)
27
+ gidnumber = group.id
28
+ end
29
+
30
+ def user
31
+ Object.full_const_get(Models::USER).first(:login => memberuid)
32
+ end
33
+
34
+ def user=(user)
35
+ memberuid = user.login
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,37 @@
1
+ module Ixtlan
2
+ module Models
3
+ class Locale
4
+ include DataMapper::Resource
5
+
6
+ def self.default_storage_name
7
+ "Locale"
8
+ end
9
+
10
+ unless const_defined? "DEFAULT"
11
+ DEFAULT = "DEFAULT"
12
+ ALL = "ALL"
13
+ end
14
+
15
+ property :code, String, :nullable => false , :format => /^[a-z][a-z](_[A-Z][A-Z])?$|^#{DEFAULT}$|^#{ALL}$/, :length => 7, :key => true
16
+
17
+ timestamps :created_at
18
+
19
+ def self.default
20
+ get(DEFAULT) || create(:code => DEFAULT)
21
+ end
22
+
23
+ def self.every
24
+ get(ALL) || create(:code => ALL)
25
+ end
26
+
27
+ def hash
28
+ attribute_get(:code).hash
29
+ end
30
+
31
+ alias :eql? :==
32
+ def ==(other)
33
+ attribute_get(:code).eql?(other.attribute_get(:code))
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,29 @@
1
+ module Ixtlan
2
+ module Models
3
+ class Permission
4
+
5
+ include DataMapper::Resource
6
+
7
+ def self.default_storage_name
8
+ "Permission"
9
+ end
10
+
11
+ property :resource, String,:format => /^[a-zA-Z0-9_.]*$/, :key => true
12
+
13
+ property :action, String, :format => /^[a-zA-Z0-9_.]*$/, :key => true
14
+
15
+ has n, :roles, :model => Models::ROLE
16
+
17
+ if protected_instance_methods.find {|m| m == 'to_x'}.nil?
18
+
19
+ protected
20
+
21
+ alias :to_x :to_xml_document
22
+ def to_xml_document(opts, doc = nil)
23
+ opts.merge!({:methods => [:roles]})
24
+ to_x(opts, doc)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,71 @@
1
+ module Ixtlan
2
+ module Models
3
+ class Phrase
4
+ include DataMapper::Resource
5
+
6
+ def self.default_storage_name
7
+ "Phrase"
8
+ end
9
+
10
+ property :id, Serial
11
+
12
+ property :code, String, :nullable => false, :length => 64
13
+
14
+ property :text, String, :nullable => false, :length => 256
15
+
16
+ property :current_text, String, :nullable => true, :length => 256
17
+
18
+ property :updated_at, DateTime, :nullable => false, :auto_validation => false
19
+
20
+ belongs_to :updated_by, :model => Models::USER
21
+
22
+ belongs_to :locale, :model => Models::LOCALE
23
+
24
+ belongs_to :default_translation, :model => Models::TRANSLATION, :nullable => true
25
+
26
+ belongs_to :parent_translation, :model => Models::TRANSLATION, :nullable => true
27
+
28
+ alias :to_x :to_xml_document
29
+ def to_xml_document(opts = {}, doc = nil)
30
+ opts.merge!({:exclude => [:id, :parent_translation_id, :default_translation_id, :locale_code], :element_name => 'phrase', :methods => [:default_translation, :parent_translation, :locale]})
31
+ to_x(opts, doc)
32
+ end
33
+
34
+ def self.all(args = {})
35
+ phrases = ::DataMapper::Collection.new(::DataMapper::Query.new(self.repository, Ixtlan::Models::Phrase), [])
36
+ map = {}
37
+ locale = (args[:locale] ||= Locale.default)
38
+ Text.not_approved(args.dup).each do |text|
39
+ phrase = Phrase.new(:code => text.code, :text => text.text, :current_text => text.text, :locale => locale, :updated_at => text.updated_at, :updated_by => text.updated_by)
40
+ map[phrase.code] = phrase
41
+ end
42
+ Text.latest_approved(args.dup).each do |text|
43
+ if (phrase = map[text.code])
44
+ phrase.current_text = text.text
45
+ else
46
+ phrase = Phrase.new(:code => text.code, :text => text.text, :current_text => text.text, :locale => locale, :updated_at => text.updated_at, :updated_by => text.updated_by)
47
+ map[phrase.code] = phrase
48
+ end
49
+ end
50
+ case locale.code.size
51
+ when 2
52
+ params = args.dup
53
+ params[:locale] = Locale.default
54
+ Translation.map_for(params).each do |code, trans|
55
+ ph = map[code]
56
+ if(ph.nil?)
57
+ map[code] = Phrase.new(:code => code, :text => trans.text, :current_text => trans.text, :locale => locale, :updated_at => trans.approved_at, :updated_by => trans.approved_by, :default_translation => trans)
58
+ else
59
+ ph.default_translation = trans
60
+ end
61
+ end
62
+ when 5
63
+ raise "not implemented yet"
64
+ end
65
+ map.values.each { |ph| phrases << ph}
66
+ phrases
67
+ end
68
+ end
69
+ end
70
+ end
71
+
@@ -0,0 +1,35 @@
1
+ module Ixtlan
2
+ module Models
3
+ class Role
4
+
5
+ include DataMapper::Resource
6
+
7
+ def self.default_storage_name
8
+ "Role"
9
+ end
10
+
11
+ property :name, String, :nullable => false , :format => /^[a-zA-Z0-9\-_.]*$/, :length => 32, :key => true
12
+
13
+ def hash
14
+ attribute_get(:name).hash
15
+ end
16
+
17
+ alias :eql? :==
18
+ def ==(other)
19
+ attribute_get(:name).eql?(other.attribute_get(:name))
20
+ end
21
+
22
+ if protected_instance_methods.find {|m| m == 'to_x'}.nil?
23
+
24
+ protected
25
+
26
+ alias :to_x :to_xml_document
27
+ def to_xml_document(opts, doc = nil)
28
+ opts.merge!({:exclude => [:permission_resource,:permission_action]})
29
+ to_x(opts, doc)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+
@@ -0,0 +1,140 @@
1
+ module Ixtlan
2
+ module Models
3
+ class Text
4
+ include DataMapper::Resource
5
+
6
+ def self.default_storage_name
7
+ "Text"
8
+ end
9
+
10
+ property :id, Serial
11
+
12
+ property :code, String, :nullable => false, :length => 64
13
+
14
+ property :text, String, :nullable => false, :length => 256
15
+
16
+ property :version, Integer, :nullable => true
17
+
18
+ property :current, Boolean, :nullable => false, :auto_validation => false
19
+
20
+ property :previous, Boolean, :nullable => false, :auto_validation => false
21
+
22
+ property :updated_at, DateTime, :nullable => false, :auto_validation => false
23
+
24
+ belongs_to :updated_by, :model => Models::USER
25
+
26
+ property :approved_at, DateTime, :nullable => true
27
+
28
+ belongs_to :approved_by, :model => Models::USER, :nullable => true
29
+
30
+ belongs_to :locale, :model => Models::LOCALE
31
+
32
+ validates_with_method :invariant
33
+
34
+ def invariant
35
+ no_version = original_attributes[:version].nil? && attribute_get(:version).nil?
36
+ if no_version
37
+ if (!attribute_get(:version).nil? && attribute_dirty?(:version) && attribute_dirty?(:text))
38
+ [false, 'can not approve and change text at the same time']
39
+ elsif new?
40
+ [true]
41
+ elsif attribute_dirty?(:code)
42
+ [false, 'code is invariant']
43
+ else
44
+ [true]
45
+ end
46
+ else
47
+ if (!attribute_get(:version).nil? && attribute_dirty?(:version))
48
+ if (attribute_dirty?(:text))
49
+ [false, 'can not approve and change text at the same time']
50
+ elsif !original_attributes[properties[:version]].nil?
51
+ [false, 'can not change version']
52
+ else
53
+ [true]
54
+ end
55
+ elsif attribute_dirty?(:code)
56
+ [false, 'can not change code']
57
+ elsif attribute_dirty?(:text)
58
+ [false, 'can not change text']
59
+ else
60
+ [true]
61
+ end
62
+ end
63
+ end
64
+
65
+ validates_with_method :cascade
66
+
67
+ def cascade
68
+ size =
69
+ case locale.code.size
70
+ when 2
71
+ self.model.latest_approved(:code => code, :locale => Locale.default).size
72
+ when 5
73
+ self.model.latest_approved(:code => code, :locale => Locale.get(locale.code[0,1])).size
74
+ else
75
+ 1
76
+ end
77
+ if(size == 0)
78
+ [false, "parent text for '#{code}' does not exists"]
79
+ else
80
+ [true]
81
+ end
82
+ end
83
+
84
+ before :save do
85
+ if(new? or attribute_get(:version).nil?)
86
+ attribute_set(:version, nil)
87
+ attribute_set(:current, false)
88
+ attribute_set(:previous, false)
89
+ attribute_set(:approved_at, nil)
90
+ approved_by = nil
91
+ end
92
+ end
93
+
94
+ def approve(params = {})
95
+ latest = self.class.latest_approved(:code => attribute_get(:code),
96
+ :locale => locale).first
97
+ previous = self.class.second_latest_approved(:code => attribute_get(:code),
98
+ :locale => locale).first
99
+ params[:version] = latest.nil? ? 1 : latest.version + 1
100
+ params[:current] = true
101
+ params[:approved_at] = attribute_get(:updated_at)
102
+ params[:approved_by] = params[:current_user]
103
+
104
+ p = (previous.nil? ? true : previous.update(:previous => false,
105
+ :current_user => params[:current_user]))
106
+ l = (latest.nil? ? true : latest.update(:current => false,
107
+ :previous => true,
108
+ :current_user => params[:current_user]))
109
+ u = update(params)
110
+
111
+ u && l && p
112
+ end
113
+
114
+ def approved?
115
+ !attribute_get(:version).nil?
116
+ end
117
+
118
+ def self.latest_approved(args = {})
119
+ args[:current] = true
120
+ all(args)
121
+ end
122
+
123
+ def self.second_latest_approved(args = {})
124
+ args[:previous] = true
125
+ all(args)
126
+ end
127
+
128
+ def self.approved(args = {})
129
+ args[:version.not] = nil
130
+ all(args)
131
+ end
132
+
133
+ def self.not_approved(args = {})
134
+ args[:version] = nil
135
+ all(args)
136
+ end
137
+ end
138
+ end
139
+ end
140
+
@@ -0,0 +1,40 @@
1
+ module Ixtlan
2
+ module Models
3
+ class Translation
4
+ include DataMapper::Resource
5
+
6
+ def self.default_storage_name
7
+ "Translation"
8
+ end
9
+
10
+ property :id, Serial
11
+
12
+ property :text, String, :nullable => false, :length => 256
13
+
14
+ property :previous_text, String, :nullable => true, :length => 256
15
+
16
+ property :approved_at, DateTime, :nullable => false, :auto_validation => false
17
+
18
+ belongs_to :approved_by, :model => Models::USER
19
+
20
+ def self.map_for(args = {})
21
+ map = {}
22
+ Text.latest_approved(args.dup).each do |text|
23
+ map[text.code] = Translation.create(:text => text.text, :approved_at => text.approved_at, :approved_by => text.approved_by)
24
+ end
25
+ Text.second_latest_approved(args.dup).each do |text|
26
+ translation = map[text.code]
27
+ translation.previous_text = text.text
28
+ end
29
+ map
30
+ end
31
+
32
+ alias :to_x :to_xml_document
33
+ def to_xml_document(opts = {}, doc = nil)
34
+ opts.merge!({:exclude => [:id]})
35
+ to_x(opts, doc)
36
+ end
37
+ end
38
+ end
39
+ end
40
+
@@ -0,0 +1,112 @@
1
+ require 'digest'
2
+ require 'base64'
3
+ require 'ixtlan/modified_by'
4
+ require 'dm-serializer'
5
+ module Ixtlan
6
+ module Models
7
+ class User
8
+ include DataMapper::Resource
9
+
10
+ def self.default_storage_name
11
+ "User"
12
+ end
13
+
14
+ property :id, Serial, :field => "uidnumber"
15
+
16
+ property :login, String, :nullable => false , :length => 4..32, :index => :unique_index, :format => /^[a-zA-z0-9]*$/, :writer => :private, :field => "uid"
17
+
18
+ property :name, String, :nullable => false, :format => /^[^<">]*$/, :length => 2..64, :field => "cn"
19
+
20
+ property :email, String, :nullable => false, :format => :email_address, :nullable => false, :length => 8..64, :index => :unique_index, :field => "mail"
21
+
22
+ property :language, String, :nullable => false, :format => /[a-z][a-z]/, :length => 2, :field => "preferredlanguage"
23
+
24
+ property :hashed_password, String, :nullable => true, :length => 128, :accessor => :private, :field => "userpassword"
25
+
26
+ timestamps :at
27
+
28
+ modified_by ::Ixtlan::Models::USER
29
+
30
+ # Virtual attribute for the plaintext password
31
+ attr_reader :password
32
+
33
+ validates_is_unique :login
34
+ validates_is_unique :email
35
+
36
+ def reset_password(len = 12)
37
+ @password = Ixtlan::Passwords.generate(len)
38
+ attribute_set(:hashed_password, Ixtlan::Digest.ssha(@password, "--#{Time.now}--#{login}--"))
39
+ @password
40
+ end
41
+
42
+ def digest
43
+ ::Base64.decode64(@hashed_password[6,200])
44
+ end
45
+
46
+ def self.authenticate(login, password)
47
+ user = first(:login => login) # need to get the salt
48
+ if user
49
+ digest = user.digest
50
+ salt = digest[20,147]
51
+ if ::Digest::SHA1.digest("#{password}" + salt) == digest[0,20]
52
+ user
53
+ else
54
+ "wrong password"
55
+ end
56
+ else
57
+ "unknown login"
58
+ end
59
+ end
60
+
61
+ def groups
62
+ if @groups.nil?
63
+ # TODO spec the empty array to make sure new relations are stored
64
+ # in the database or the groups collection is empty before filling it
65
+ @groups = ::DataMapper::Collection.new(::DataMapper::Query.new(self.repository, Models::Group), [])
66
+ GroupUser.all(:memberuid => login).each do |gu|
67
+ @groups << gu.group
68
+ end
69
+ def @groups.user=(user)
70
+ @user = user
71
+ end
72
+ @groups.user = self
73
+ def @groups.<<(group)
74
+ group.locales(@user)
75
+ unless member? group
76
+ gu = GroupUser.create(:memberuid => @user.login, :gidnumber => group.id)
77
+ super
78
+ end
79
+
80
+ self
81
+ end
82
+
83
+ def @groups.delete(group)
84
+ gu = GroupUser.first(:memberuid => @user.login, :gidnumber => group.id)
85
+ if gu
86
+ gu.destroy
87
+ end
88
+ super
89
+ end
90
+ @groups.each {|g| g.locales(self) }
91
+ end
92
+ @groups
93
+ end
94
+
95
+ # make sure login is immutable
96
+ def login=(new_login)
97
+ attribute_set(:login, new_login) if (login.nil? or login == "'NULL'" or login == "NULL")
98
+ end
99
+
100
+ if protected_instance_methods.find {|m| m == 'to_x'}.nil?
101
+ alias :to_x :to_xml_document
102
+
103
+ protected
104
+
105
+ def to_xml_document(opts={}, doc = nil)
106
+ opts.merge!({:exclude => [:hashed_password], :methods => [:groups]})
107
+ to_x(opts, doc)
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end