persistent_cookie_authentication_generator 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.
- data/MIT-LICENCE +20 -0
- data/USAGE +22 -0
- data/persistent_cookie_authentication_generator.rb +52 -0
- data/templates/identities.yml +18 -0
- data/templates/identity.rb +4 -0
- data/templates/login_cookie.rb +254 -0
- data/templates/login_cookies.yml +32 -0
- data/templates/migration.rb +42 -0
- data/templates/smtp_tls.rb +67 -0
- data/templates/user.rb +147 -0
- data/templates/user_change_password.rhtml +22 -0
- data/templates/user_controller.rb +302 -0
- data/templates/user_edit.rhtml +15 -0
- data/templates/user_environment.rb +9 -0
- data/templates/user_forgot_password.rhtml +9 -0
- data/templates/user_integration_test.rb +303 -0
- data/templates/user_login.rhtml +23 -0
- data/templates/user_notify.rb +55 -0
- data/templates/user_notify_change_password.rhtml +10 -0
- data/templates/user_notify_forgot_password.rhtml +8 -0
- data/templates/user_notify_signup.rhtml +10 -0
- data/templates/user_show.rhtml +15 -0
- data/templates/user_signup.rhtml +29 -0
- data/templates/user_system.rb +158 -0
- data/templates/user_test.rb +72 -0
- data/templates/users.yml +46 -0
- metadata +78 -0
@@ -0,0 +1,67 @@
|
|
1
|
+
require "openssl"
|
2
|
+
require "net/smtp"
|
3
|
+
|
4
|
+
Net::SMTP.class_eval do
|
5
|
+
private
|
6
|
+
def do_start(helodomain, user, secret, authtype)
|
7
|
+
raise IOError, 'SMTP session already started' if @started
|
8
|
+
check_auth_args user, secret, authtype if user or secret
|
9
|
+
|
10
|
+
sock = timeout(@open_timeout) { TCPSocket.open(@address, @port) }
|
11
|
+
@socket = Net::InternetMessageIO.new(sock)
|
12
|
+
@socket.read_timeout = 60 #@read_timeout
|
13
|
+
#@socket.debug_output = STDERR #@debug_output
|
14
|
+
|
15
|
+
check_response(critical { recv_response() })
|
16
|
+
do_helo(helodomain)
|
17
|
+
|
18
|
+
if starttls
|
19
|
+
raise 'openssl library not installed' unless defined?(OpenSSL)
|
20
|
+
ssl = OpenSSL::SSL::SSLSocket.new(sock)
|
21
|
+
ssl.sync_close = true
|
22
|
+
ssl.connect
|
23
|
+
@socket = Net::InternetMessageIO.new(ssl)
|
24
|
+
@socket.read_timeout = 60 #@read_timeout
|
25
|
+
#@socket.debug_output = STDERR #@debug_output
|
26
|
+
do_helo(helodomain)
|
27
|
+
end
|
28
|
+
|
29
|
+
authenticate user, secret, authtype if user
|
30
|
+
@started = true
|
31
|
+
ensure
|
32
|
+
unless @started
|
33
|
+
# authentication failed, cancel connection.
|
34
|
+
@socket.close if not @started and @socket and not @socket.closed?
|
35
|
+
@socket = nil
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def do_helo(helodomain)
|
40
|
+
begin
|
41
|
+
if @esmtp
|
42
|
+
ehlo helodomain
|
43
|
+
else
|
44
|
+
helo helodomain
|
45
|
+
end
|
46
|
+
rescue Net::ProtocolError
|
47
|
+
if @esmtp
|
48
|
+
@esmtp = false
|
49
|
+
@error_occured = false
|
50
|
+
retry
|
51
|
+
end
|
52
|
+
raise
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def starttls
|
57
|
+
getok('STARTTLS') rescue return false
|
58
|
+
return true
|
59
|
+
end
|
60
|
+
|
61
|
+
def quit
|
62
|
+
begin
|
63
|
+
getok('QUIT')
|
64
|
+
rescue EOFError, OpenSSL::SSL::SSLError
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/templates/user.rb
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
|
3
|
+
class User < ActiveRecord::Base
|
4
|
+
|
5
|
+
belongs_to :identity
|
6
|
+
|
7
|
+
attr_accessor :new_password
|
8
|
+
|
9
|
+
after_save '@new_password = false'
|
10
|
+
after_validation :crypt_password
|
11
|
+
|
12
|
+
validates_presence_of :login
|
13
|
+
validates_length_of :login, :within => 3..40
|
14
|
+
validates_uniqueness_of :login
|
15
|
+
|
16
|
+
validates_presence_of :email
|
17
|
+
validates_uniqueness_of :email
|
18
|
+
|
19
|
+
validates_presence_of :password, :if => :validate_password?
|
20
|
+
validates_confirmation_of :password, :if => :validate_password?
|
21
|
+
validates_length_of :password, { :minimum => 5, :if => :validate_password? }
|
22
|
+
validates_length_of :password, { :maximum => 40, :if => :validate_password? }
|
23
|
+
|
24
|
+
|
25
|
+
#initialising
|
26
|
+
def initialize(attributes = nil)
|
27
|
+
super
|
28
|
+
@new_password = false
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
|
33
|
+
#authentiacation
|
34
|
+
def self.authenticate(login, password)
|
35
|
+
userFound = find(:first, :conditions => ["login = ? AND verified = 1", login])
|
36
|
+
|
37
|
+
if userFound != nil
|
38
|
+
authenticatedUser = find(:first, :conditions => ["login = ? AND verified = 1 AND salted_password = ?", login, salted_password(userFound.salt, hashed(password)) ])
|
39
|
+
if authenticatedUser != nil
|
40
|
+
return authenticatedUser.identity_id
|
41
|
+
else
|
42
|
+
return 0
|
43
|
+
end
|
44
|
+
else
|
45
|
+
return 0
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
|
51
|
+
#authenticate by token
|
52
|
+
def self.authenticate_by_token(id, token)
|
53
|
+
userFound = find(:first, :conditions => ["id = ? AND security_token = ?", id, token])
|
54
|
+
|
55
|
+
if userFound.nil? or !userFound.update_verified
|
56
|
+
return false
|
57
|
+
else
|
58
|
+
return true
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
|
64
|
+
# update the token
|
65
|
+
def update_verified
|
66
|
+
write_attribute("verified", 1)
|
67
|
+
update_without_callbacks
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
|
72
|
+
#generate a security token
|
73
|
+
def generate_security_token(hours = nil)
|
74
|
+
if not hours.nil? or self.security_token.nil?
|
75
|
+
return new_security_token(hours)
|
76
|
+
else
|
77
|
+
return self.security_token
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
|
83
|
+
# changing the password
|
84
|
+
def change_password(pass, confirm = nil)
|
85
|
+
self.password = pass
|
86
|
+
self.password_confirmation = confirm.nil? ? pass : confirm
|
87
|
+
@new_password = true
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
|
92
|
+
# generate a random password
|
93
|
+
def generate_random_string(password_length)
|
94
|
+
chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
|
95
|
+
newPassword = ""
|
96
|
+
1.upto(password_length) { |i| newPassword << chars[rand(chars.size-1)] }
|
97
|
+
return newPassword
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
|
102
|
+
protected
|
103
|
+
|
104
|
+
attr_accessor :password, :password_confirmation
|
105
|
+
|
106
|
+
|
107
|
+
# validating the password
|
108
|
+
def validate_password?
|
109
|
+
@new_password
|
110
|
+
end
|
111
|
+
|
112
|
+
|
113
|
+
|
114
|
+
# hashing a string
|
115
|
+
def self.hashed(str)
|
116
|
+
return Digest::SHA1.hexdigest("change-me--#{str}--")[0..39]
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
|
121
|
+
# encrypting the password
|
122
|
+
def crypt_password
|
123
|
+
if @new_password
|
124
|
+
write_attribute("salt", self.class.hashed("salt-#{Time.now}"))
|
125
|
+
write_attribute("salted_password", self.class.salted_password(salt, self.class.hashed(@password)))
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
|
131
|
+
# security token
|
132
|
+
def new_security_token(hours = nil)
|
133
|
+
write_attribute('security_token', self.class.hashed(self.salted_password + Time.now.to_i.to_s + rand.to_s))
|
134
|
+
update_without_callbacks
|
135
|
+
return self.security_token
|
136
|
+
end
|
137
|
+
|
138
|
+
|
139
|
+
|
140
|
+
# salting the password. can i add sugar instead?
|
141
|
+
def self.salted_password(salt, hashed_password)
|
142
|
+
hashed(salt + hashed_password)
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
|
147
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
<h1>Change Password</h1>
|
2
|
+
|
3
|
+
<% if flash[:notice] %><div class="notice"><%= flash[:notice] %></div><% end %>
|
4
|
+
|
5
|
+
<% form_for(:user, :url => { :action => 'update_password' }) do |f| %>
|
6
|
+
<table>
|
7
|
+
<tr>
|
8
|
+
<td>Current Password</td>
|
9
|
+
<td><%= password_field_tag 'current_password' %></td>
|
10
|
+
</tr>
|
11
|
+
<tr>
|
12
|
+
<td>New Password</td>
|
13
|
+
<td><%= f.password_field(:password) %></td>
|
14
|
+
</tr>
|
15
|
+
<tr>
|
16
|
+
<td>New Password confirmation</td>
|
17
|
+
<td><%= f.password_field(:password_confirmation) %></td>
|
18
|
+
</tr>
|
19
|
+
</table>
|
20
|
+
|
21
|
+
<p><%= submit_tag 'Change Password' %></p>
|
22
|
+
<% end %>
|
@@ -0,0 +1,302 @@
|
|
1
|
+
class UserController < ApplicationController
|
2
|
+
|
3
|
+
before_filter :login_required, :only => [:logout, :change_password, :update_password, :edit, :update, :welcome, :show]
|
4
|
+
|
5
|
+
# login landing page
|
6
|
+
def login
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
|
11
|
+
# the login part
|
12
|
+
def user_login
|
13
|
+
resetSession
|
14
|
+
userIdentity = User.authenticate(params[:user][:login], params[:user][:password])
|
15
|
+
|
16
|
+
if userIdentity > 0
|
17
|
+
flash[:notice] = "Welcome"
|
18
|
+
setCurrentIdentity(userIdentity, PRIVATE_STATE)
|
19
|
+
setCookie(userIdentity, cookies[COOKIE_NAME])
|
20
|
+
redirect_back_or_default :action => 'show'
|
21
|
+
else
|
22
|
+
flash[:notice] = "Wrong user name or password. Please try again"
|
23
|
+
redirect_to :action => 'login'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
|
29
|
+
# the logout part
|
30
|
+
def logout
|
31
|
+
resetSession
|
32
|
+
flash[:notice] = "You have logged out"
|
33
|
+
redirect_to :action => 'login'
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
|
38
|
+
# sign up page
|
39
|
+
def signup
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
|
44
|
+
# creating the user and sending the sign up email
|
45
|
+
def user_signup
|
46
|
+
#defensive programming
|
47
|
+
if params[:user] == nil
|
48
|
+
raise "null input"
|
49
|
+
end
|
50
|
+
|
51
|
+
@user = User.new(params[:user])
|
52
|
+
@user.new_password = true
|
53
|
+
|
54
|
+
#creating the identity for it
|
55
|
+
@identity = Identity.new
|
56
|
+
|
57
|
+
if !@identity.save
|
58
|
+
raise "cannot create identity"
|
59
|
+
end
|
60
|
+
|
61
|
+
@user.identity = @identity
|
62
|
+
|
63
|
+
if @user.save
|
64
|
+
key = @user.generate_security_token
|
65
|
+
url = url_for(:action => 'welcome')
|
66
|
+
url += "?user[id]=#{@user.id}&key=#{key}"
|
67
|
+
|
68
|
+
UserNotify.deliver_signup(@user, params[:user][:password], url)
|
69
|
+
flash[:notice] = "Sign up has succeeded. Please check your email for activation instructions"
|
70
|
+
redirect_to :action => 'login'
|
71
|
+
else
|
72
|
+
@identity.destroy #clears the identity as the user is not valid
|
73
|
+
|
74
|
+
if @user.errors.empty? == false
|
75
|
+
showErr(@user)
|
76
|
+
redirect_to :action => 'signup'
|
77
|
+
else
|
78
|
+
raise "Problem saving the user"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
|
85
|
+
# change password form
|
86
|
+
def change_password
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
|
91
|
+
# changing the password
|
92
|
+
def update_password
|
93
|
+
@user = getCurrentUser
|
94
|
+
|
95
|
+
if User.authenticate(@user.login, params[:current_password]) != 0
|
96
|
+
@user.change_password(params[:user][:password], params[:user][:password_confirmation])
|
97
|
+
|
98
|
+
if @user.save
|
99
|
+
UserNotify.deliver_change_password(@user, params[:user][:password])
|
100
|
+
flash[:notice] = "Password is changed successfully"
|
101
|
+
redirect_to :action => 'show'
|
102
|
+
else
|
103
|
+
if @user.errors.empty? == false
|
104
|
+
showErr(@user)
|
105
|
+
redirect_to :action => 'change_password'
|
106
|
+
else
|
107
|
+
raise "Problem updating the password"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
else
|
111
|
+
flash[:notice] = "Your previous password is incorrect. Please try again"
|
112
|
+
redirect_to :action => 'change_password'
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
|
117
|
+
|
118
|
+
# forgot your pasword page
|
119
|
+
def forgot_password
|
120
|
+
end
|
121
|
+
|
122
|
+
|
123
|
+
|
124
|
+
# send the user an email with the new password
|
125
|
+
def send_new_password
|
126
|
+
# defensive programming
|
127
|
+
if params[:user] == nil
|
128
|
+
raise "null inputs"
|
129
|
+
end
|
130
|
+
|
131
|
+
@user = User.find_by_email(params[:user][:email])
|
132
|
+
|
133
|
+
if @user != nil
|
134
|
+
randomPassword = @user.generate_random_string(8)
|
135
|
+
@user.change_password(randomPassword, randomPassword)
|
136
|
+
|
137
|
+
if @user.save
|
138
|
+
UserNotify.deliver_forgot_password(@user, randomPassword)
|
139
|
+
flash[:notice] = "Email is sent successfully"
|
140
|
+
redirect_back_or_default :action => 'login'
|
141
|
+
else
|
142
|
+
raise "failed to save password changes"
|
143
|
+
end
|
144
|
+
|
145
|
+
else
|
146
|
+
flash[:notice] = "The email is not valid"
|
147
|
+
redirect_to :action => 'login', :controller => 'user'
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
|
152
|
+
|
153
|
+
# editing the user
|
154
|
+
def edit
|
155
|
+
@user = getCurrentUser
|
156
|
+
end
|
157
|
+
|
158
|
+
|
159
|
+
|
160
|
+
# updating the user details
|
161
|
+
def update
|
162
|
+
@user = getCurrentUser
|
163
|
+
|
164
|
+
if @user.update_attributes(params[:user])
|
165
|
+
flash[:notice] = "Update is successful"
|
166
|
+
redirect_to :action => 'show'
|
167
|
+
else
|
168
|
+
if @user.errors.empty? == false
|
169
|
+
showErr(@user)
|
170
|
+
redirect_to :action => 'edit'
|
171
|
+
else
|
172
|
+
raise "Problem saving the user."
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
|
178
|
+
|
179
|
+
# welcome screen
|
180
|
+
def welcome
|
181
|
+
# defensive programming
|
182
|
+
if params[:user] == nil
|
183
|
+
redirect_to :action => 'login'
|
184
|
+
end
|
185
|
+
|
186
|
+
@user = User.new(params[:user])
|
187
|
+
|
188
|
+
if User.authenticate_by_token(params[:user][:id], params[:key])
|
189
|
+
flash[:notice] = "Verification successful. Please Login"
|
190
|
+
redirect_to :action => 'login'
|
191
|
+
else
|
192
|
+
flash[:notice] = "Verification unsuccessful. Please try again in a moment's time"
|
193
|
+
redirect_to :action => 'login'
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
|
198
|
+
|
199
|
+
# the user home page
|
200
|
+
def show
|
201
|
+
@user = getCurrentUser
|
202
|
+
end
|
203
|
+
|
204
|
+
|
205
|
+
private
|
206
|
+
|
207
|
+
|
208
|
+
|
209
|
+
# showing the errors if there're
|
210
|
+
def showErr(obj)
|
211
|
+
# defensive programming
|
212
|
+
if obj == nil
|
213
|
+
raise "null inputs"
|
214
|
+
end
|
215
|
+
|
216
|
+
errMsg = String.new
|
217
|
+
obj.errors.each_full { |msg| errMsg << msg << ". " }
|
218
|
+
flash[:notice] = "Oops...There're some errors. " + errMsg
|
219
|
+
end
|
220
|
+
|
221
|
+
|
222
|
+
|
223
|
+
# sets the cookie for this identity on the browser
|
224
|
+
def setCookie(identityID, cookieValue)
|
225
|
+
if cookieValue != nil && cookieValue.length > 0
|
226
|
+
if LoginCookie.verifyIdentity(cookieValue, identityID)
|
227
|
+
newCookie = LoginCookie.regenerateCookie(cookieValue)
|
228
|
+
associateWithIdentity(newCookie, LoginCookie.getIdentity(cookieValue))
|
229
|
+
cookies[COOKIE_NAME] = { :value => newCookie, :expires => 10.years.from_now }
|
230
|
+
elsif LoginCookie.isAnonymousIdentity(cookieValue)
|
231
|
+
mergeIdentity(cookieValue, identityID)
|
232
|
+
else
|
233
|
+
newCookie = LoginCookie.generateCookie(getCurrentUser.login, nil)
|
234
|
+
associateWithIdentity(newCookie, identityID)
|
235
|
+
cookies[COOKIE_NAME] = { :value => newCookie, :expires => 10.years.from_now }
|
236
|
+
end
|
237
|
+
else
|
238
|
+
newCookie = LoginCookie.generateCookie(getCurrentUser.login, nil)
|
239
|
+
associateWithIdentity(newCookie, identityID)
|
240
|
+
cookies[COOKIE_NAME] = { :value => newCookie, :expires => 10.years.from_now }
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
|
245
|
+
|
246
|
+
# merge the anonymous identity to a known identity(identity which has a user)
|
247
|
+
def mergeIdentity(anonymousCookieValue, knownIdentityID)
|
248
|
+
# defensive programming
|
249
|
+
if anonymousCookieValue == nil || knownIdentityID == nil
|
250
|
+
return false
|
251
|
+
end
|
252
|
+
|
253
|
+
return false if !LoginCookie.isAnonymous(anonymousCookieValue)
|
254
|
+
|
255
|
+
loginValue = LoginCookie.getCookieLogin(anonymousCookieValue)
|
256
|
+
|
257
|
+
#getting the both anonymous user's identity and the known identity
|
258
|
+
anonymousCookie = LoginCookie.find(:first, :conditions => ["login = ?", loginValue])
|
259
|
+
anonymousIdentity = anonymousCookie.identity
|
260
|
+
|
261
|
+
knownIdentity = Identity.find(knownIdentityID)
|
262
|
+
|
263
|
+
return false if anonymousIdentity == nil || knownIdentity == nil
|
264
|
+
|
265
|
+
#transferring the cookies
|
266
|
+
anonymousIdentity.login_cookies.each { |cookie|
|
267
|
+
cookie.identity_id = knownIdentity.id
|
268
|
+
cookie.save
|
269
|
+
}
|
270
|
+
|
271
|
+
#finally send the identity to its grave
|
272
|
+
anonymousIdentity.destroy
|
273
|
+
|
274
|
+
return true
|
275
|
+
end
|
276
|
+
|
277
|
+
|
278
|
+
|
279
|
+
# associating the cookie with the identity
|
280
|
+
def associateWithIdentity(cookieValue, identityID)
|
281
|
+
#defensive programming
|
282
|
+
if identityID == nil || identityID == 0 || cookieValue == nil
|
283
|
+
raise "null inputs"
|
284
|
+
end
|
285
|
+
|
286
|
+
userIdentity = Identity.find(identityID)
|
287
|
+
cookieFound = LoginCookie.getCookieRef(cookieValue)
|
288
|
+
|
289
|
+
if userIdentity != nil && cookieFound != nil
|
290
|
+
|
291
|
+
if userIdentity.login_cookies == nil
|
292
|
+
userIdentity.login_cookies = cookieFound
|
293
|
+
else
|
294
|
+
userIdentity.login_cookies << cookieFound
|
295
|
+
end
|
296
|
+
|
297
|
+
userIdentity.save
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
end
|
302
|
+
|