ixtlan 0.2.0

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