sinatra-authentication-dmeiz 0.3.2
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.
- data/.gitignore +4 -0
- data/History.txt +4 -0
- data/Manifest +26 -0
- data/Rakefile +38 -0
- data/TODO +53 -0
- data/example/dm_extend_app.rb +26 -0
- data/example/dm_sinbook.rb +56 -0
- data/example/extend_views/edit.haml +42 -0
- data/example/extend_views/index.haml +31 -0
- data/example/extend_views/login.haml +21 -0
- data/example/extend_views/show.haml +9 -0
- data/example/extend_views/signup.haml +30 -0
- data/example/mm_app.rb +22 -0
- data/example/tc_app.rb +16 -0
- data/example/tc_sinbook.rb +62 -0
- data/lib/models/abstract_user.rb +54 -0
- data/lib/models/datamapper_user.rb +42 -0
- data/lib/models/dm_adapter.rb +50 -0
- data/lib/models/mm_adapter.rb +53 -0
- data/lib/models/mongomapper_user.rb +42 -0
- data/lib/models/rufus_tokyo_user.rb +177 -0
- data/lib/models/tc_adapter.rb +83 -0
- data/lib/sinatra-authentication.rb +290 -0
- data/lib/views/edit.haml +43 -0
- data/lib/views/index.haml +29 -0
- data/lib/views/login.haml +22 -0
- data/lib/views/show.haml +9 -0
- data/lib/views/signup.haml +26 -0
- data/readme.markdown +238 -0
- data/sinatra-authentication.gemspec +119 -0
- data/test/datamapper_test.rb +5 -0
- data/test/lib/dm_app.rb +20 -0
- data/test/lib/dm_extend_app.rb +27 -0
- data/test/lib/dm_sinbook.rb +55 -0
- data/test/lib/extend_views/edit.haml +42 -0
- data/test/lib/extend_views/index.haml +31 -0
- data/test/lib/extend_views/login.haml +21 -0
- data/test/lib/extend_views/show.haml +9 -0
- data/test/lib/extend_views/signup.haml +29 -0
- data/test/lib/helper.rb +9 -0
- data/test/lib/mm_app.rb +24 -0
- data/test/lib/tc_app.rb +16 -0
- data/test/lib/tc_sinbook.rb +62 -0
- data/test/mongomapper_test.rb +39 -0
- data/test/route_tests.rb +29 -0
- data/test/rufus_tokyo_test.rb +5 -0
- metadata +212 -0
@@ -0,0 +1,54 @@
|
|
1
|
+
if Object.const_defined?("DataMapper")
|
2
|
+
#require 'dm-core'
|
3
|
+
require 'dm-timestamps'
|
4
|
+
require 'dm-validations'
|
5
|
+
require Pathname(__FILE__).dirname.expand_path + "datamapper_user.rb"
|
6
|
+
require Pathname(__FILE__).dirname.expand_path + "dm_adapter.rb"
|
7
|
+
elsif Object.const_defined?("Rufus") && Rufus.const_defined?("Tokyo")
|
8
|
+
require Pathname(__FILE__).dirname.expand_path + "rufus_tokyo_user.rb"
|
9
|
+
require Pathname(__FILE__).dirname.expand_path + "tc_adapter.rb"
|
10
|
+
elsif Object.const_defined?("MongoMapper")
|
11
|
+
require Pathname(__FILE__).dirname.expand_path + "mongomapper_user.rb"
|
12
|
+
require Pathname(__FILE__).dirname.expand_path + "mm_adapter.rb"
|
13
|
+
end
|
14
|
+
|
15
|
+
class User
|
16
|
+
if Object.const_defined?("DataMapper")
|
17
|
+
include DmAdapter
|
18
|
+
elsif Object.const_defined?("Rufus")
|
19
|
+
include TcAdapter
|
20
|
+
elsif Object.const_defined?("MongoMapper")
|
21
|
+
include MmAdapter
|
22
|
+
else
|
23
|
+
throw "you need to require either 'dm-core', 'mongo_mapper', or 'rufus-tokyo' for sinatra-authentication to work"
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(interfacing_class_instance)
|
27
|
+
@instance = interfacing_class_instance
|
28
|
+
end
|
29
|
+
|
30
|
+
def id
|
31
|
+
@instance.id
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.authenticate(email, pass)
|
35
|
+
current_user = get(:email => email)
|
36
|
+
return nil if current_user.nil?
|
37
|
+
return current_user if User.encrypt(pass, current_user.salt) == current_user.hashed_password
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
|
43
|
+
def self.encrypt(pass, salt)
|
44
|
+
Digest::SHA1.hexdigest(pass+salt)
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.random_string(len)
|
48
|
+
#generate a random password consisting of strings and digits
|
49
|
+
chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
|
50
|
+
newpass = ""
|
51
|
+
1.upto(len) { |i| newpass << chars[rand(chars.size-1)] }
|
52
|
+
return newpass
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
class DmUser
|
2
|
+
include DataMapper::Resource
|
3
|
+
|
4
|
+
property :id, Serial
|
5
|
+
property :email, String, :length => (5..40), :unique => true, :format => :email_address
|
6
|
+
property :hashed_password, String
|
7
|
+
property :salt, String
|
8
|
+
property :created_at, DateTime
|
9
|
+
property :permission_level, Integer, :default => 1
|
10
|
+
if Sinatra.const_defined?('FacebookObject')
|
11
|
+
property :fb_uid, String
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_accessor :password, :password_confirmation
|
15
|
+
#protected equievelant? :protected => true doesn't exist in dm 0.10.0
|
16
|
+
#protected :id, :salt
|
17
|
+
#doesn't behave correctly, I'm not even sure why I did this.
|
18
|
+
|
19
|
+
validates_presence_of :password_confirmation, :unless => Proc.new { |t| t.hashed_password }
|
20
|
+
validates_presence_of :password, :unless => Proc.new { |t| t.hashed_password }
|
21
|
+
validates_confirmation_of :password
|
22
|
+
|
23
|
+
def password=(pass)
|
24
|
+
@password = pass
|
25
|
+
self.salt = User.random_string(10) if !self.salt
|
26
|
+
self.hashed_password = User.encrypt(@password, self.salt)
|
27
|
+
end
|
28
|
+
|
29
|
+
def admin?
|
30
|
+
self.permission_level == -1 || self.id == 1
|
31
|
+
end
|
32
|
+
|
33
|
+
def site_admin?
|
34
|
+
self.id == 1
|
35
|
+
end
|
36
|
+
|
37
|
+
protected
|
38
|
+
|
39
|
+
def method_missing(m, *args)
|
40
|
+
return false
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module DmAdapter
|
2
|
+
def self.included(base)
|
3
|
+
base.extend ClassMethods
|
4
|
+
base.class_eval { include DmAdapter::InstanceMethods }
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
#pass all args to this
|
9
|
+
def all(*args)
|
10
|
+
result = DmUser.all(*args)
|
11
|
+
result.collect {|instance| self.new instance}
|
12
|
+
end
|
13
|
+
|
14
|
+
def get(hash)
|
15
|
+
if user = DmUser.first(hash)
|
16
|
+
self.new user
|
17
|
+
else
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def set(attributes)
|
23
|
+
user = DmUser.new attributes
|
24
|
+
user.save
|
25
|
+
user
|
26
|
+
end
|
27
|
+
|
28
|
+
def set!(attributes)
|
29
|
+
user = DmUser.new attributes
|
30
|
+
user.save!
|
31
|
+
user
|
32
|
+
end
|
33
|
+
|
34
|
+
def delete(pk)
|
35
|
+
user = DmUser.first(:id => pk)
|
36
|
+
user.destroy
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
module InstanceMethods
|
41
|
+
def update(attributes)
|
42
|
+
@instance.update attributes
|
43
|
+
end
|
44
|
+
|
45
|
+
def method_missing(meth, *args, &block)
|
46
|
+
#cool I just found out * on an array turns the array into a list of args for a function
|
47
|
+
@instance.send(meth, *args, &block)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module MmAdapter
|
2
|
+
def self.included(base)
|
3
|
+
base.extend ClassMethods
|
4
|
+
base.class_eval { include MmAdapter::InstanceMethods }
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def all
|
9
|
+
result = MmUser.all
|
10
|
+
result.collect {|instance| self.new instance}
|
11
|
+
end
|
12
|
+
|
13
|
+
def get(hash)
|
14
|
+
if user = MmUser.first(hash)
|
15
|
+
self.new user
|
16
|
+
else
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def set(attributes)
|
22
|
+
puts attributes.inspect
|
23
|
+
user = MmUser.new attributes
|
24
|
+
puts user.inspect
|
25
|
+
puts user.to_json
|
26
|
+
user.id = nil unless user.save
|
27
|
+
user
|
28
|
+
end
|
29
|
+
|
30
|
+
def set!(attributes)
|
31
|
+
user = MmUser.new attributes
|
32
|
+
user.save!
|
33
|
+
user
|
34
|
+
end
|
35
|
+
|
36
|
+
def delete(pk)
|
37
|
+
user = User.first(:id => pk)
|
38
|
+
user.destroy
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
module InstanceMethods
|
43
|
+
def update(attributes)
|
44
|
+
@instance.update_attributes attributes
|
45
|
+
@instance.save
|
46
|
+
end
|
47
|
+
|
48
|
+
def method_missing(meth, *args, &block)
|
49
|
+
#cool I just found out * on an array turns the array into a list of args for a function
|
50
|
+
@instance.send(meth, *args, &block)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
class MmUser
|
2
|
+
include MongoMapper::Document
|
3
|
+
|
4
|
+
key :email, String, :length => (5..40), :unique => true
|
5
|
+
key :hashed_password, String
|
6
|
+
key :salt, String
|
7
|
+
key :permission_level, Integer, :default => 1
|
8
|
+
if Sinatra.const_defined?('FacebookObject')
|
9
|
+
key :fb_uid, String
|
10
|
+
end
|
11
|
+
|
12
|
+
timestamps!
|
13
|
+
|
14
|
+
attr_accessor :password, :password_confirmation
|
15
|
+
#protected equievelant? :protected => true doesn't exist in dm 0.10.0
|
16
|
+
#protected :id, :salt
|
17
|
+
#doesn't behave correctly, I'm not even sure why I did this.
|
18
|
+
|
19
|
+
#validates_presence_of :password_confirmation, :unless => Proc.new { |t| t.hashed_password }
|
20
|
+
#validates_presence_of :password, :unless => Proc.new { |t| t.hashed_password }
|
21
|
+
#validates_is_confirmed :password
|
22
|
+
|
23
|
+
def password=(pass)
|
24
|
+
@password = pass
|
25
|
+
self.salt = User.random_string(10) if !self.salt
|
26
|
+
self.hashed_password = User.encrypt(@password, self.salt)
|
27
|
+
end
|
28
|
+
|
29
|
+
def admin?
|
30
|
+
self.permission_level == -1 || self.id == 1
|
31
|
+
end
|
32
|
+
|
33
|
+
def site_admin?
|
34
|
+
self.id == 1
|
35
|
+
end
|
36
|
+
|
37
|
+
protected
|
38
|
+
|
39
|
+
def method_missing(m, *args)
|
40
|
+
return false
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
class TcUser
|
2
|
+
#include RufusOrm
|
3
|
+
|
4
|
+
#custom_attribute :salt
|
5
|
+
#custom_attribute :hashed_password
|
6
|
+
#custom_attribute :hashed_permission_level
|
7
|
+
#custom_attribute :created_at
|
8
|
+
#custom_attribute :created_at_i
|
9
|
+
|
10
|
+
#attribute method?
|
11
|
+
#if I'm gonna write all this, I might as well create a tinyyyy
|
12
|
+
#orm, that's more just like a way to define custom attributes for cabinets
|
13
|
+
#something worth noting though is that even datamapper defines custom
|
14
|
+
#attributes by allowing the developer to override setter methods.
|
15
|
+
#and it just calls all the setter methods defined in the model.
|
16
|
+
#the only trouble with this route is it assumes a predefined schema.
|
17
|
+
#and thus it knows what setter methods to call.
|
18
|
+
#I would write a class method that allows you to declare attributes like
|
19
|
+
#attribute :salt, with an optional block (which gets passed a hash of attributes)
|
20
|
+
#if a block isn't defined, it looks in the class for a salt=(attributes) function, calls it and marges the
|
21
|
+
#result into the hash going into the database, like 'attributes.merge{"salt" => result}'
|
22
|
+
#so my 'set' method or whatever I choose to call it, has to somehow look through each
|
23
|
+
#declared attribute, call the method associated with it, and merge the result into the hash going
|
24
|
+
#into the database.
|
25
|
+
#
|
26
|
+
#but what if I don't want an attribute passed in to be stored into the database? What if I just want to
|
27
|
+
#create a virtual attribute, for declaring other attributes?
|
28
|
+
#I might create a class variable that I store all the attributes in, and the I can get to it from any setter,
|
29
|
+
#and then after I've called all the setters, I store that class variable into the database.
|
30
|
+
#or, I do all of this on the instance level, and have a save method.
|
31
|
+
|
32
|
+
def initialize(attributes)
|
33
|
+
@attributes = attributes
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.query(&block)
|
37
|
+
connection = TcUserTable.new
|
38
|
+
result_set = connection.query(&block)
|
39
|
+
output = result_set.collect { |result_hash| TcUser.new(result_hash) }
|
40
|
+
connection.close
|
41
|
+
output
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.get(key)
|
45
|
+
connection = TcUserTable.new
|
46
|
+
result = connection[key]
|
47
|
+
connection.close
|
48
|
+
if result
|
49
|
+
self.new(result.merge({:pk => key}))
|
50
|
+
else
|
51
|
+
false
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.set(attributes)
|
56
|
+
#this way of validating is real crap, replace it with Validator maybe
|
57
|
+
#and maybe replace all this hash merging with setters for the various attributes that update @attributes, and then I can call save to store to the database
|
58
|
+
#or maybe just write a little method that makes hash merger look a little cleaner
|
59
|
+
pk = attributes.delete(:pk) if attributes[:pk]
|
60
|
+
|
61
|
+
email_regexp = /(\A(\s*)\Z)|(\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z)/i
|
62
|
+
if attributes['email'] =~ email_regexp
|
63
|
+
if attributes['password'] == attributes.delete('password_confirmation') && attributes['password'] != nil
|
64
|
+
password = attributes.delete('password')
|
65
|
+
attributes.merge!({'salt' => User.random_string(10)}) if !attributes['salt']
|
66
|
+
attributes.merge!('hashed_password' => User.encrypt(password, attributes['salt']))
|
67
|
+
permission_level = attributes['permission_level'] ? attributes['permission_level'] : '1'
|
68
|
+
attributes.merge!('permission_level' => permission_level)
|
69
|
+
unless attributes['created_at']
|
70
|
+
attributes.merge!('created_at' => Time.now.to_s)
|
71
|
+
attributes.merge!('created_at_i' => Time.now.to_i.to_s)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
existing_user = TcUser.query do |q|
|
76
|
+
q.add 'email', :streq, attributes['email']
|
77
|
+
end[0]
|
78
|
+
|
79
|
+
if existing_user && existing_user['pk'] != attributes['pk']
|
80
|
+
return false
|
81
|
+
else
|
82
|
+
connection = TcUserTable.new
|
83
|
+
pk ||= connection.genuid.to_s
|
84
|
+
#site admin if their first
|
85
|
+
attributes.merge!({'permission_level' => '-2'}) if pk == '1'
|
86
|
+
result = connection[pk] = attributes
|
87
|
+
#might not need this in newer version of rufus
|
88
|
+
result.merge!({:pk => pk})
|
89
|
+
connection.close
|
90
|
+
self.new(result)
|
91
|
+
end
|
92
|
+
else
|
93
|
+
false
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.set!(attributes)
|
98
|
+
connection = TcUserTable.new
|
99
|
+
pk = connection.genuid.to_s
|
100
|
+
result = connection[pk] = attributes
|
101
|
+
result.merge!({:pk => pk})
|
102
|
+
connection.close
|
103
|
+
self.new(result)
|
104
|
+
end
|
105
|
+
|
106
|
+
def self.delete(pk)
|
107
|
+
connection = TcUserTable.new
|
108
|
+
connection.delete(pk)
|
109
|
+
connection.close
|
110
|
+
end
|
111
|
+
|
112
|
+
def update(attributes)
|
113
|
+
new_attributes = @attributes.merge(attributes)
|
114
|
+
TcUser.set(new_attributes)
|
115
|
+
end
|
116
|
+
|
117
|
+
def [](key)
|
118
|
+
@attributes[key]
|
119
|
+
end
|
120
|
+
|
121
|
+
#saves to database and returns self
|
122
|
+
def []=(key, value)
|
123
|
+
@attributes[key] = value
|
124
|
+
#change so that it sets the attributes and then you call save to save to the database?
|
125
|
+
connection = TcUserTable.new
|
126
|
+
connection[@attributes[:pk]] = @attributes.merge!({key => value})
|
127
|
+
connection.close
|
128
|
+
self
|
129
|
+
end
|
130
|
+
|
131
|
+
def id
|
132
|
+
@attributes[:pk]
|
133
|
+
end
|
134
|
+
|
135
|
+
def admin?
|
136
|
+
#-2 is the site admin
|
137
|
+
@attributes['permission_level'] == '-1' || @attributes['permission_level'] == '-2'
|
138
|
+
end
|
139
|
+
|
140
|
+
def site_admin?
|
141
|
+
@attributes['permission_level'] == '-2'
|
142
|
+
end
|
143
|
+
|
144
|
+
#from hash extension for making hashes like javascript objects
|
145
|
+
def method_missing(meth,*args)
|
146
|
+
if /=$/=~(meth=meth.id2name) then
|
147
|
+
self[meth[0...-1]] = (args.length<2 ? args[0] : args)
|
148
|
+
elsif @attributes[meth]
|
149
|
+
@attributes[meth]
|
150
|
+
else
|
151
|
+
false
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
if Rufus::Tokyo.const_defined?('Table')
|
157
|
+
class TokyoTableDad < Rufus::Tokyo::Table
|
158
|
+
end
|
159
|
+
elsif Rufus::Edo.const_defined?('Table')
|
160
|
+
class TokyoTableDad < Rufus::Edo::Table
|
161
|
+
end
|
162
|
+
else
|
163
|
+
throw 'wtf?'
|
164
|
+
end
|
165
|
+
|
166
|
+
class TcUserTable < TokyoTableDad
|
167
|
+
@@path = false
|
168
|
+
def initialize
|
169
|
+
#make this path configurable somehow
|
170
|
+
raise "you need to define a path for the user cabinet to be stored at, like so: TcUserTable.cabinet_path = 'folder/where/you/wanna/store/your/database'" unless @@path
|
171
|
+
super(@@path + '/users.tct')
|
172
|
+
end
|
173
|
+
|
174
|
+
def self.cabinet_path=(path)
|
175
|
+
@@path = path
|
176
|
+
end
|
177
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module TcAdapter
|
2
|
+
def self.included(base)
|
3
|
+
base.extend ClassMethods
|
4
|
+
base.class_eval {
|
5
|
+
include TcAdapter::InstanceMethods
|
6
|
+
alias :class_id :id
|
7
|
+
}
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
#TODO add pagination
|
12
|
+
def all
|
13
|
+
result = TcUser.query do |q|
|
14
|
+
q.order_by 'created_at_i', :numdesc
|
15
|
+
end
|
16
|
+
|
17
|
+
#these will be the same for all adapters, they should be defined in the user class, and these methods should have a different name?
|
18
|
+
result.collect {|instance| self.new instance }
|
19
|
+
end
|
20
|
+
|
21
|
+
def get(hash)
|
22
|
+
if hash[:id]
|
23
|
+
pk = hash[:id]
|
24
|
+
result = TcUser.get(pk)
|
25
|
+
else
|
26
|
+
result = TcUser.query do |q|
|
27
|
+
hash.each do |key, value|
|
28
|
+
q.add key.to_s, :streq, value.to_s
|
29
|
+
end
|
30
|
+
end[0]
|
31
|
+
end
|
32
|
+
#elsif hash[:email]
|
33
|
+
# result = TcUser.query do |q|
|
34
|
+
# q.add 'email', :streq, hash[:email]
|
35
|
+
# end[0]
|
36
|
+
#the zero is because this returns an array but get should return the first result
|
37
|
+
#end
|
38
|
+
|
39
|
+
if result
|
40
|
+
self.new result
|
41
|
+
else
|
42
|
+
nil
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def set(attributes)
|
47
|
+
user = TcUser.query do |q|
|
48
|
+
q.add 'email', :streq, attributes['email']
|
49
|
+
end
|
50
|
+
|
51
|
+
if user == [] #no user
|
52
|
+
self.new TcUser.set(attributes)
|
53
|
+
else
|
54
|
+
false
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def set!(attributes)
|
59
|
+
self.new TcUser.set!(attributes)
|
60
|
+
end
|
61
|
+
|
62
|
+
def delete(pk)
|
63
|
+
#true or false
|
64
|
+
!!TcUser.delete(pk)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
module InstanceMethods
|
69
|
+
def update(attributes)
|
70
|
+
@instance.update attributes
|
71
|
+
end
|
72
|
+
|
73
|
+
def method_missing(meth, *args, &block)
|
74
|
+
#cool I just found out * on an array turn the array into a list of args for a function
|
75
|
+
@instance.send(meth, *args, &block)
|
76
|
+
end
|
77
|
+
|
78
|
+
#this was the only thing that didn't get passed on to method_missing because this is a method of object doh
|
79
|
+
def id
|
80
|
+
@instance.id
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|