fingerrails 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/README.rdoc +3 -0
- data/Rakefile +28 -0
- data/VERSION +1 -0
- data/bin/fingerrails +9 -0
- data/fingerrails.gemspec +90 -0
- data/fingertips.rb +181 -0
- data/templates/.kick +22 -0
- data/templates/Rakefile +23 -0
- data/templates/app/controllers/application_controller.rb +51 -0
- data/templates/app/controllers/members_controller.rb +32 -0
- data/templates/app/controllers/passwords_controller.rb +46 -0
- data/templates/app/controllers/sessions_controller.rb +26 -0
- data/templates/app/helpers/application_helper.rb +20 -0
- data/templates/app/models/mailer.rb +8 -0
- data/templates/app/models/member.rb +10 -0
- data/templates/app/models/member/authentication.rb +44 -0
- data/templates/app/views/layouts/_application_javascript_includes.html.erb +3 -0
- data/templates/app/views/layouts/_head.html.erb +3 -0
- data/templates/app/views/layouts/application.html.erb +25 -0
- data/templates/app/views/mailer/reset_password_message.erb +8 -0
- data/templates/app/views/members/edit.html.erb +21 -0
- data/templates/app/views/members/new.html.erb +26 -0
- data/templates/app/views/members/show.html.erb +3 -0
- data/templates/app/views/passwords/edit.html.erb +22 -0
- data/templates/app/views/passwords/new.html.erb +22 -0
- data/templates/app/views/passwords/reset.html.erb +9 -0
- data/templates/app/views/passwords/sent.html.erb +11 -0
- data/templates/app/views/sessions/_form.html.erb +22 -0
- data/templates/app/views/sessions/_status.html.erb +9 -0
- data/templates/app/views/sessions/new.html.erb +5 -0
- data/templates/config/database.yml +18 -0
- data/templates/lib/active_record_ext.rb +26 -0
- data/templates/lib/token.rb +9 -0
- data/templates/public/403.html +29 -0
- data/templates/public/javascripts/ready.js +9 -0
- data/templates/public/stylesheets/default.css +143 -0
- data/templates/public/stylesheets/reset.css +52 -0
- data/templates/test/ext/authentication.rb +24 -0
- data/templates/test/ext/file_fixtures.rb +8 -0
- data/templates/test/ext/time.rb +8 -0
- data/templates/test/fixtures/members.yml +8 -0
- data/templates/test/functional/application_controller_test.rb +104 -0
- data/templates/test/functional/members_controller_test.rb +71 -0
- data/templates/test/functional/passwords_controller_test.rb +95 -0
- data/templates/test/functional/sessions_controller_test.rb +68 -0
- data/templates/test/lib/active_record_ext_test.rb +13 -0
- data/templates/test/lib/token_test.rb +17 -0
- data/templates/test/test_helper.rb +32 -0
- data/templates/test/unit/helpers/application_helper_test.rb +44 -0
- data/templates/test/unit/mailer_test.rb +13 -0
- data/templates/test/unit/member/authentication_test.rb +73 -0
- data/templates/test/unit/member_test.rb +32 -0
- metadata +108 -0
@@ -0,0 +1,9 @@
|
|
1
|
+
// Let the javascript libs start their behaviour. These are all included at the
|
2
|
+
// end of the document so the browser will first render the document before
|
3
|
+
// requesting the javascripts.
|
4
|
+
|
5
|
+
// Event.observe(document, 'dom:loaded', function() {
|
6
|
+
// });
|
7
|
+
|
8
|
+
// Event.observe(window, 'load', function() {
|
9
|
+
// });
|
@@ -0,0 +1,143 @@
|
|
1
|
+
/* @group Typography */
|
2
|
+
|
3
|
+
html,
|
4
|
+
input
|
5
|
+
{
|
6
|
+
font: 14px/20px "Lucida Grande", Lucida, "Lucida Sans Unicode", Verdana, sans-serif;
|
7
|
+
}
|
8
|
+
|
9
|
+
h1
|
10
|
+
{
|
11
|
+
font-size: 200%;
|
12
|
+
}
|
13
|
+
h2
|
14
|
+
{
|
15
|
+
font-size: 150%;
|
16
|
+
margin-bottom: 24px;
|
17
|
+
}
|
18
|
+
|
19
|
+
/* @end */
|
20
|
+
|
21
|
+
/* @group Basics */
|
22
|
+
|
23
|
+
a,
|
24
|
+
a:visited
|
25
|
+
{
|
26
|
+
color: #689051;
|
27
|
+
}
|
28
|
+
|
29
|
+
a:hover,
|
30
|
+
a:active
|
31
|
+
{
|
32
|
+
color: #84b567;
|
33
|
+
}
|
34
|
+
|
35
|
+
/* @end */
|
36
|
+
|
37
|
+
/* @group Layout */
|
38
|
+
|
39
|
+
a#logo
|
40
|
+
{
|
41
|
+
display: block;
|
42
|
+
padding: 6px 12px;
|
43
|
+
color: #999;
|
44
|
+
text-decoration: none;
|
45
|
+
}
|
46
|
+
|
47
|
+
p#member
|
48
|
+
{
|
49
|
+
position: absolute;
|
50
|
+
top: 3px;
|
51
|
+
right: 12px;
|
52
|
+
}
|
53
|
+
p#member a
|
54
|
+
{
|
55
|
+
text-decoration: none;
|
56
|
+
font-size: 10px;
|
57
|
+
}
|
58
|
+
p#member a+a
|
59
|
+
{
|
60
|
+
margin-left: 6px;
|
61
|
+
}
|
62
|
+
|
63
|
+
ul#navigation
|
64
|
+
{
|
65
|
+
background-color: #bdff94;
|
66
|
+
padding: 6px 12px;
|
67
|
+
margin-bottom: 12px;
|
68
|
+
border-top: 1px solid #bfff97;
|
69
|
+
border-bottom: 1px solid #a0da7d;
|
70
|
+
}
|
71
|
+
ul#navigation li
|
72
|
+
{
|
73
|
+
display: inline;
|
74
|
+
}
|
75
|
+
ul#navigation a,
|
76
|
+
ul#navigation a:visited
|
77
|
+
{
|
78
|
+
text-decoration: none;
|
79
|
+
color: #222;
|
80
|
+
}
|
81
|
+
ul#navigation a:hover,
|
82
|
+
ul#navigation a:active
|
83
|
+
{
|
84
|
+
color: #7d9b1d;
|
85
|
+
}
|
86
|
+
ul#navigation li + li
|
87
|
+
{
|
88
|
+
margin-left: 6px;
|
89
|
+
}
|
90
|
+
|
91
|
+
div#main
|
92
|
+
{
|
93
|
+
padding: 12px;
|
94
|
+
}
|
95
|
+
|
96
|
+
/* @end */
|
97
|
+
|
98
|
+
/* @group Forms */
|
99
|
+
|
100
|
+
div.field
|
101
|
+
{
|
102
|
+
margin: 12px 0px;
|
103
|
+
}
|
104
|
+
label,
|
105
|
+
div.label
|
106
|
+
{
|
107
|
+
color: #666;
|
108
|
+
}
|
109
|
+
div.label a
|
110
|
+
{
|
111
|
+
color: #999;
|
112
|
+
font-size: 80%;
|
113
|
+
text-decoration: none;
|
114
|
+
}
|
115
|
+
div.label a:hover
|
116
|
+
{
|
117
|
+
text-decoration: underline;
|
118
|
+
}
|
119
|
+
div.inline label
|
120
|
+
{
|
121
|
+
font-size: 90%;
|
122
|
+
}
|
123
|
+
|
124
|
+
input[type="text"],
|
125
|
+
input[type="password"]
|
126
|
+
{
|
127
|
+
color: #222;
|
128
|
+
}
|
129
|
+
|
130
|
+
a.cancel
|
131
|
+
{
|
132
|
+
text-decoration: none;
|
133
|
+
color: #999;
|
134
|
+
font-size: 80%;
|
135
|
+
margin-left: 3px;
|
136
|
+
}
|
137
|
+
a.cancel:hover
|
138
|
+
{
|
139
|
+
text-decoration: underline;
|
140
|
+
}
|
141
|
+
|
142
|
+
/* @end */
|
143
|
+
|
@@ -0,0 +1,52 @@
|
|
1
|
+
/* Reset, based on http://meyerweb.com/eric/tools/css/reset/ */
|
2
|
+
|
3
|
+
html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, button, legend, table, caption, tbody, tfoot, thead, tr, th, td
|
4
|
+
{
|
5
|
+
margin: 0;
|
6
|
+
padding: 0;
|
7
|
+
border: 0;
|
8
|
+
outline: 0;
|
9
|
+
font-size: 100%;
|
10
|
+
vertical-align: baseline;
|
11
|
+
background: transparent;
|
12
|
+
}
|
13
|
+
body
|
14
|
+
{
|
15
|
+
line-height: 1;
|
16
|
+
}
|
17
|
+
ol, ul
|
18
|
+
{
|
19
|
+
list-style: none;
|
20
|
+
}
|
21
|
+
blockquote, q
|
22
|
+
{
|
23
|
+
quotes: none;
|
24
|
+
}
|
25
|
+
blockquote:before, blockquote:after, q:before, q:after
|
26
|
+
{
|
27
|
+
content: '';
|
28
|
+
content: none;
|
29
|
+
}
|
30
|
+
:focus /* remember to define focus styles! */
|
31
|
+
{
|
32
|
+
outline: 0;
|
33
|
+
}
|
34
|
+
ins /* remember to highlight inserts somehow! */
|
35
|
+
{
|
36
|
+
text-decoration: none;
|
37
|
+
}
|
38
|
+
del
|
39
|
+
{
|
40
|
+
text-decoration: line-through;
|
41
|
+
}
|
42
|
+
table /* tables still need 'cellspacing="0"' in the markup */
|
43
|
+
{
|
44
|
+
border-collapse: collapse;
|
45
|
+
border-spacing: 0;
|
46
|
+
}
|
47
|
+
hr
|
48
|
+
{
|
49
|
+
height: 1px;
|
50
|
+
background-color: #000;
|
51
|
+
border: none;
|
52
|
+
}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module TestHelpers
|
2
|
+
module Authentication
|
3
|
+
def login(member, password='secret')
|
4
|
+
@authenticated = member
|
5
|
+
request.session[:member_id] = @authenticated.id
|
6
|
+
end
|
7
|
+
|
8
|
+
def logout
|
9
|
+
request.session.delete(:member_id)
|
10
|
+
end
|
11
|
+
|
12
|
+
def authenticated?
|
13
|
+
!request.session[:member_id].blank?
|
14
|
+
end
|
15
|
+
|
16
|
+
def access_denied?
|
17
|
+
response.status.to_i == 403
|
18
|
+
end
|
19
|
+
|
20
|
+
def login_required?
|
21
|
+
response.header['Location'] == new_session_url
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
module TestHelpers
|
2
|
+
module FileFixtures
|
3
|
+
def fixture_file_upload(path, mime_type = nil, binary = false)
|
4
|
+
fixture_path = ActionController::TestCase.send(:fixture_path) if ActionController::TestCase.respond_to?(:fixture_path)
|
5
|
+
ActionController::TestUploadedFile.new("#{fixture_path}#{path}", mime_type, binary)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require File.expand_path('../../test_helper', __FILE__)
|
2
|
+
|
3
|
+
class TestApplicationsController < ApplicationController
|
4
|
+
allow_access :admin, :only => :private_action
|
5
|
+
allow_access :all, :except => :private_action
|
6
|
+
|
7
|
+
def private_action
|
8
|
+
render :nothing => true
|
9
|
+
end
|
10
|
+
|
11
|
+
def private_action_with_flash
|
12
|
+
flash[:notice] = 'Message'
|
13
|
+
render :nothing => true
|
14
|
+
end
|
15
|
+
|
16
|
+
def public_action
|
17
|
+
render :nothing => true
|
18
|
+
end
|
19
|
+
|
20
|
+
def action_with_layout
|
21
|
+
render :inline => '', :layout => 'application'
|
22
|
+
end
|
23
|
+
|
24
|
+
def action_with_respond_block
|
25
|
+
respond_to do |format|
|
26
|
+
format.html { render :text => 'HTML' }
|
27
|
+
format.jpg { render :text => 'JPG'}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
ActionController::Routing::Routes.draw do |map|
|
33
|
+
map.resource :test_application, :member => {
|
34
|
+
:private_action => :get,
|
35
|
+
:private_action_with_flash => :get,
|
36
|
+
:public_action => :get,
|
37
|
+
:action_with_layout => :get,
|
38
|
+
:action_with_respond_block => :get
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
describe TestApplicationsController do
|
43
|
+
it "should find the currently logged in member" do
|
44
|
+
login members(:adrian)
|
45
|
+
get :public_action
|
46
|
+
assigns(:authenticated).should == members(:adrian)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should not find currently logged in member when no-one is logged in" do
|
50
|
+
get :public_action
|
51
|
+
assigns(:authenticated).should.be.blank
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should log a member in" do
|
55
|
+
should.not.be.authenticated
|
56
|
+
@controller.send(:login, members(:adrian))
|
57
|
+
should.be.authenticated
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should log a member out" do
|
61
|
+
@controller.send(:login, members(:adrian))
|
62
|
+
should.be.authenticated
|
63
|
+
@controller.send(:logout)
|
64
|
+
should.not.be.authenticated
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should respond with HTML before JPG" do
|
68
|
+
request.env['HTTP_ACCEPT'] = "*/*"
|
69
|
+
get :action_with_respond_block
|
70
|
+
response.body.should == 'HTML'
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should respond with HTML before JPG, even with IE" do
|
74
|
+
request.env["HTTP_USER_AGENT"] = 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Win64; x64)'
|
75
|
+
request.env['HTTP_ACCEPT'] = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/x-shockwave-flash, application/x-silverlight, */*"
|
76
|
+
get :action_with_respond_block
|
77
|
+
response.body.should == 'HTML'
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe TestApplicationsController, "when dealing with pages that need authorization" do
|
82
|
+
it "should allow the user to authorize" do
|
83
|
+
get :private_action
|
84
|
+
should.redirect_to new_session_url
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should keep the flash around when access was refused" do
|
88
|
+
get :private_action_with_flash
|
89
|
+
flash[:notice].should == 'Message'
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should have stored the url to return the user to after authentication" do
|
93
|
+
get :private_action
|
94
|
+
controller.after_authentication[:redirect_to].should == controller.url_for(:action => :private_action)
|
95
|
+
end
|
96
|
+
|
97
|
+
it "should show a forbidden page when access was refused" do
|
98
|
+
controller.stubs(:find_authenticated)
|
99
|
+
controller.send(:instance_variable_set, "@authenticated", stub(:id => 1, :role => 'member'))
|
100
|
+
|
101
|
+
get :private_action
|
102
|
+
status.should.be :forbidden
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require File.expand_path('../../test_helper', __FILE__)
|
2
|
+
|
3
|
+
describe "On the", MembersController, "a visitor" do
|
4
|
+
it "should see a form for a new member" do
|
5
|
+
get :new
|
6
|
+
status.should.be :ok
|
7
|
+
template.should.be 'members/new'
|
8
|
+
assert_select 'form'
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should create a new member" do
|
12
|
+
lambda {
|
13
|
+
post :create, :member => valid_params
|
14
|
+
}.should.differ('Member.count', +1)
|
15
|
+
assigns(:member).email.should == valid_params[:email]
|
16
|
+
assigns(:member).hashed_password.should == Member.hash_password(valid_params[:password])
|
17
|
+
should.redirect_to root_url
|
18
|
+
should.be.authenticated
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should show validation errors after a failed create" do
|
22
|
+
post :create, :member => valid_params.merge(:email => '')
|
23
|
+
status.should.be :ok
|
24
|
+
template.should.be 'members/new'
|
25
|
+
assert_select 'div.errorExplanation'
|
26
|
+
assert_select 'form'
|
27
|
+
should.not.be.authenticated
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should see a profile" do
|
31
|
+
get :show, :id => members(:adrian)
|
32
|
+
assigns(:member).should == members(:adrian)
|
33
|
+
status.should.be :success
|
34
|
+
template.should.be 'members/show'
|
35
|
+
end
|
36
|
+
|
37
|
+
should.require_login.get :edit, :id => members(:adrian)
|
38
|
+
should.require_login.put :update, :id => members(:adrian)
|
39
|
+
should.require_login.delete :destroy, :id => members(:adrian)
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def valid_params
|
44
|
+
{ :email => 'jurgen@example.com', :password => 'so secret', :verify_password => 'so secret' }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "On the", MembersController, "a member" do
|
49
|
+
before do
|
50
|
+
login members(:adrian)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should see an edit form" do
|
54
|
+
get :edit, :id => @authenticated.to_param
|
55
|
+
|
56
|
+
assert_select 'form'
|
57
|
+
assigns(:member).should == @authenticated
|
58
|
+
status.should.be :success
|
59
|
+
template.should.be 'members/edit'
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should be able to update his profile" do
|
63
|
+
put :update, :id => @authenticated.to_param, :member => { :email => 'sir.adrian@example.com' }
|
64
|
+
|
65
|
+
@authenticated.reload.email.should == 'sir.adrian@example.com'
|
66
|
+
should.redirect_to member_url(@authenticated)
|
67
|
+
end
|
68
|
+
|
69
|
+
should.disallow.put :update, :id => members(:kelly)
|
70
|
+
should.disallow.delete :destroy, :id => members(:kelly)
|
71
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require File.expand_path('../../test_helper', __FILE__)
|
2
|
+
|
3
|
+
describe "On the", PasswordsController, "a visitor" do
|
4
|
+
before do
|
5
|
+
@member = members(:adrian)
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should see a new form to reset his password" do
|
9
|
+
get :new
|
10
|
+
status.should.be :success
|
11
|
+
template.should.be 'passwords/new'
|
12
|
+
assert_select 'form'
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should generate a password reset token and send it via email" do
|
16
|
+
@member.update_attribute(:reset_password_token, nil)
|
17
|
+
|
18
|
+
assert_emails(1) do
|
19
|
+
post :create, :email => @member.email
|
20
|
+
end
|
21
|
+
|
22
|
+
assigns(:member).should == @member
|
23
|
+
@member.reload.reset_password_token.should.not.be.blank
|
24
|
+
|
25
|
+
email = ActionMailer::Base.deliveries.last
|
26
|
+
email.body.should.include edit_password_url(:id => @member.reset_password_token)
|
27
|
+
|
28
|
+
status.should.be :success
|
29
|
+
template.should.be 'passwords/sent'
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should see an error if no member can be found for a given email address" do
|
33
|
+
assert_emails(0) do
|
34
|
+
post :create, :email => "joe.the.plumber@example.com"
|
35
|
+
end
|
36
|
+
|
37
|
+
status.should.be :ok
|
38
|
+
template.should.be 'passwords/new'
|
39
|
+
flash[:error].should.not.be.blank
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should see a warning if an SMTP error occurred" do
|
43
|
+
Mailer.stubs(:deliver_reset_password_message).raises Net::SMTPFatalError
|
44
|
+
post :create, :email => @member.email
|
45
|
+
|
46
|
+
status.should.be :ok
|
47
|
+
template.should.be 'passwords/new'
|
48
|
+
flash[:error].should.not.be.blank
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should see an edit form for his password" do
|
52
|
+
@member.generate_reset_password_token!
|
53
|
+
get :edit, :id => @member.reset_password_token
|
54
|
+
|
55
|
+
status.should.be :success
|
56
|
+
assigns(:member).should == @member
|
57
|
+
template.should.be 'passwords/edit'
|
58
|
+
assert_select 'form'
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should not see an edit form if no member is found for the given token" do
|
62
|
+
get :edit, :id => "doesnotexist"
|
63
|
+
|
64
|
+
status.should.be :not_found
|
65
|
+
assigns(:member).should.be nil
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should be able to update his password" do
|
69
|
+
@member.generate_reset_password_token!
|
70
|
+
put :update, :id => @member.reset_password_token, :password => "newpass"
|
71
|
+
|
72
|
+
Member.authenticate(:email => @member.email, :password => "newpass").should == @member
|
73
|
+
status.should.be :success
|
74
|
+
template.should.be 'passwords/reset'
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should see an error if the password is empty" do
|
78
|
+
@member.generate_reset_password_token!
|
79
|
+
before = @member.hashed_password
|
80
|
+
|
81
|
+
put :update, :id => @member.reset_password_token, :password => ""
|
82
|
+
|
83
|
+
@member.reload.hashed_password.should == before
|
84
|
+
status.should.be :success
|
85
|
+
template.should.be 'passwords/edit'
|
86
|
+
assert_select "div.errorExplanation", :text => "The password can’t be blank."
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should not be allowed to update a password with an incorrect token" do
|
90
|
+
put :update, :id => "doesnotexist", :password => "newpass"
|
91
|
+
|
92
|
+
status.should.be :not_found
|
93
|
+
assigns(:member).should.be nil
|
94
|
+
end
|
95
|
+
end
|