sinatra-authentication-dmeiz 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|