login_generator 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/USAGE ADDED
@@ -0,0 +1,24 @@
1
+ NAME
2
+ login - creates a functional login system
3
+
4
+ SYNOPSIS
5
+ login [Controller name]
6
+
7
+ Good names are Account Myaccount Security
8
+
9
+ DESCRIPTION
10
+ This generator creates a general purpose login system.
11
+
12
+ Included:
13
+ - a User model which uses sha1 encryption for passwords
14
+ - a Controller with signup, login, welcome and logoff actions
15
+ - a mixin which lets you easily add advanced authentication
16
+ features to your abstract base controller
17
+ - a user_model.sql with the minimal sql required to get the model to work.
18
+ - extensive unit and functional test cases to make sure nothing breaks.
19
+
20
+ EXAMPLE
21
+ ./script/generate login Account
22
+
23
+ This will generate an Account controller with login and logout methods.
24
+ The model is always called User
@@ -0,0 +1,36 @@
1
+ class LoginGenerator < Rails::Generator::NamedBase
2
+ def manifest
3
+ record do |m|
4
+
5
+ # Login module, controller class, functional test, and helper.
6
+ m.template "login_system.rb", "lib/login_system.rb"
7
+ m.template "controller.rb", File.join("app/controllers", class_path, "#{file_name}_controller.rb")
8
+ m.template "controller_test.rb", File.join("test/functional", class_path, "#{file_name}_controller_test.rb")
9
+ m.template "helper.rb", File.join("app/helpers", class_path, "#{file_name}_helper.rb")
10
+
11
+ # Model class, unit test, fixtures, and example schema.
12
+ m.template "user.rb", "app/models/user.rb"
13
+ m.template "user_test.rb", "test/unit/user_test.rb"
14
+ m.template "users.yml", "test/fixtures/users.yml"
15
+
16
+ # Layout and stylesheet.
17
+ m.template "scaffold:layout.rhtml", "app/views/layouts/scaffold.rhtml"
18
+ m.template "scaffold:style.css", "public/stylesheets/scaffold.css"
19
+
20
+ # Views.
21
+ m.directory File.join("app/views", class_path, file_name)
22
+ login_views.each do |action|
23
+ m.template "view_#{action}.rhtml",
24
+ File.join("app/views", class_path, file_name, "#{action}.rhtml")
25
+ end
26
+
27
+ m.template "README", "README_LOGIN"
28
+ end
29
+ end
30
+
31
+ attr_accessor :controller_class_name
32
+
33
+ def login_views
34
+ %w(welcome login logout signup)
35
+ end
36
+ end
data/templates/README ADDED
@@ -0,0 +1,119 @@
1
+ == Installation
2
+
3
+ Done generating the login system. but there are still a few things you have to
4
+ do manually. First open your application.rb and add
5
+
6
+ require_dependency "login_system"
7
+
8
+ to the top of the file and include the login system with
9
+
10
+ include LoginSystem
11
+
12
+ The beginning of your ApplicationController.
13
+ It should look something like this :
14
+
15
+ require_dependency "login_system"
16
+
17
+ class ApplicationController < ActionController::Base
18
+ include LoginSystem
19
+ model :user
20
+
21
+ After you have done the modifications the the AbstractController you can import
22
+ the user model into the database. This model is meant as an example and you
23
+ should extend it. If you just want to get things up and running you can find
24
+ some create table syntax in db/user_model.sql.
25
+
26
+ The model :user is required when you are hitting problems to the degree of
27
+ "Session could not be restored becuase not all items in it are known"
28
+
29
+ == Requirements
30
+
31
+ You need a database table corresponding to the User model.
32
+
33
+ mysql syntax:
34
+ CREATE TABLE users (
35
+ id int(11) NOT NULL auto_increment,
36
+ login varchar(80) default NULL,
37
+ password varchar(40) default NULL,
38
+ PRIMARY KEY (id)
39
+ );
40
+  
41
+ postgres :
42
+ CREATE TABLE "users" (
43
+  "id" SERIAL NOT NULL UNIQUE,
44
+  "login" VARCHAR(80),
45
+  "password" VARCHAR,
46
+  PRIMARY KEY("id")
47
+ ) WITH OIDS;
48
+
49
+
50
+ sqlite:
51
+ CREATE TABLE 'users' (
52
+ 'id' INTEGER PRIMARY KEY NOT NULL,
53
+ 'user' VARCHAR(80) DEFAULT NULL,
54
+ 'password' VARCHAR(40) DEFAULT NULL
55
+ );
56
+
57
+ Of course your user model can have any amount of extra fields. This is just a
58
+ starting point
59
+
60
+ == How to use it
61
+
62
+ Now you can go around and happily add "before_filter :login_required" to the
63
+ controllers which you would like to protect.
64
+
65
+ After integrating the login system with your rails application navigate to your
66
+ new controller's signup method. There you can create a new account. After you
67
+ are done you should have a look at your DB. Your freshly created user will be
68
+ there but the password will be a sha1 hashed 40 digit mess. I find this should
69
+ be the minimum of security which every page offering login&password should give
70
+ its customers. Now you can move to one of those controllers which you protected
71
+ with the before_filter :login_required snippet. You will automatically be re-
72
+ directed to your freshly created login controller and you are asked for a
73
+ password. After entering valid account data you will be taken back to the
74
+ controller which you requested earlier. Simple huh?
75
+
76
+ == Tips & Tricks
77
+
78
+ How do I...
79
+
80
+ ... access the user who is currently logged in
81
+
82
+ A: You can get the user object from the session using @session['user']
83
+ Example:
84
+ Welcome <%%= @session[:user].name %>
85
+
86
+ ... restrict access to only a few methods?
87
+
88
+ A: Use before_filters build in scoping.
89
+ Example:
90
+ before_filter :login_required, :only => [:myaccount, :changepassword]
91
+ before_filter :login_required, :except => [:index]
92
+
93
+ ... check if a user is logged-in in my views?
94
+
95
+ A: @session[:user] will tell you. Here is an example helper which you can use to make this more pretty:
96
+ Example:
97
+ def user?
98
+ !@session[:user].nil?
99
+ end
100
+
101
+ ... return a user to the page they came from before logging in?
102
+
103
+ A: The user will be send back to the last url which called the method "store_location"
104
+ Example:
105
+ User was at /articles/show/1, wants to log in.
106
+ in articles_controller.rb, add store_location to the show function and send the user
107
+ to the login form.
108
+ After he logs in he will be send back to /articles/show/1
109
+
110
+
111
+ You can find more help at http://wiki.rubyonrails.com/rails/show/LoginGenerator
112
+
113
+ == Changelog
114
+
115
+ 1.1.0 Major security bugfix and modernisation
116
+ 1.0.5 Bugfix in generator code
117
+ 1.0.2 Updated the readme with more tips&tricks
118
+ 1.0.1 Fixed problem in the readme
119
+ 1.0.0 First gem release
@@ -0,0 +1,36 @@
1
+ class <%= class_name %>Controller < ApplicationController
2
+ layout 'scaffold'
3
+
4
+ def login
5
+ case @request.method
6
+ when :post
7
+ if @session[:user] = User.authenticate(@params[:user_login], @params[:user_password])
8
+
9
+ flash['notice'] = "Login successful"
10
+ redirect_back_or_default :action => "welcome"
11
+ else
12
+ flash.now['notice'] = "Login unsuccessful"
13
+
14
+ @login = @params[:user_login]
15
+ end
16
+ end
17
+ end
18
+
19
+ def signup
20
+ @user = User.new(@params[:user])
21
+
22
+ if @request.post? and @user.save
23
+ @session[:user] = User.authenticate(@user.login, @params[:user][:password])
24
+ flash['notice'] = "Signup successful"
25
+ redirect_back_or_default :action => "welcome"
26
+ end
27
+ end
28
+
29
+ def logout
30
+ @session[:user] = nil
31
+ end
32
+
33
+ def welcome
34
+ end
35
+
36
+ end
@@ -0,0 +1,74 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+ require '<%= file_name %>_controller'
3
+
4
+ # Set salt to 'change-me' because thats what the fixtures assume.
5
+ User.salt = 'change-me'
6
+
7
+ # Raise errors beyond the default web-based presentation
8
+ class <%= class_name %>Controller; def rescue_action(e) raise e end; end
9
+
10
+ class <%= class_name %>ControllerTest < Test::Unit::TestCase
11
+
12
+ fixtures :users
13
+
14
+ def setup
15
+ @controller = <%= class_name %>Controller.new
16
+ @request, @response = ActionController::TestRequest.new, ActionController::TestResponse.new
17
+ @request.host = "localhost"
18
+ end
19
+
20
+ def test_auth_bob
21
+ @request.session[:return_to] = "/bogus/location"
22
+
23
+ post :login, :user_login => "bob", :user_password => "test"
24
+ assert_session_has :user
25
+
26
+ assert_equal @bob, @response.session[:user]
27
+
28
+ assert_redirect_url "/bogus/location"
29
+ end
30
+
31
+ def test_signup
32
+ @request.session[:return_to] = "/bogus/location"
33
+
34
+ post :signup, :user => { :login => "newbob", :password => "newpassword", :password_confirmation => "newpassword" }
35
+ assert_session_has :user
36
+
37
+ assert_redirect_url "/bogus/location"
38
+ end
39
+
40
+ def test_bad_signup
41
+ @request.session[:return_to] = "/bogus/location"
42
+
43
+ post :signup, :user => { :login => "newbob", :password => "newpassword", :password_confirmation => "wrong" }
44
+ assert_invalid_column_on_record "user", :password
45
+ assert_success
46
+
47
+ post :signup, :user => { :login => "yo", :password => "newpassword", :password_confirmation => "newpassword" }
48
+ assert_invalid_column_on_record "user", :login
49
+ assert_success
50
+
51
+ post :signup, :user => { :login => "yo", :password => "newpassword", :password_confirmation => "wrong" }
52
+ assert_invalid_column_on_record "user", [:login, :password]
53
+ assert_success
54
+ end
55
+
56
+ def test_invalid_login
57
+ post :login, :user_login => "bob", :user_password => "not_correct"
58
+
59
+ assert_session_has_no :user
60
+
61
+ assert_template_has "login"
62
+ end
63
+
64
+ def test_login_logoff
65
+
66
+ post :login, :user_login => "bob", :user_password => "test"
67
+ assert_session_has :user
68
+
69
+ get :logout
70
+ assert_session_has_no :user
71
+
72
+ end
73
+
74
+ end
@@ -0,0 +1,2 @@
1
+ module <%= class_name %>Helper
2
+ end
@@ -0,0 +1,87 @@
1
+ require_dependency "user"
2
+
3
+ module LoginSystem
4
+
5
+ protected
6
+
7
+ # overwrite this if you want to restrict access to only a few actions
8
+ # or if you want to check if the user has the correct rights
9
+ # example:
10
+ #
11
+ # # only allow nonbobs
12
+ # def authorize?(user)
13
+ # user.login != "bob"
14
+ # end
15
+ def authorize?(user)
16
+ true
17
+ end
18
+
19
+ # overwrite this method if you only want to protect certain actions of the controller
20
+ # example:
21
+ #
22
+ # # don't protect the login and the about method
23
+ # def protect?(action)
24
+ # if ['action', 'about'].include?(action)
25
+ # return false
26
+ # else
27
+ # return true
28
+ # end
29
+ # end
30
+ def protect?(action)
31
+ true
32
+ end
33
+
34
+ # login_required filter. add
35
+ #
36
+ # before_filter :login_required
37
+ #
38
+ # if the controller should be under any rights management.
39
+ # for finer access control you can overwrite
40
+ #
41
+ # def authorize?(user)
42
+ #
43
+ def login_required
44
+
45
+ if not protect?(action_name)
46
+ return true
47
+ end
48
+
49
+ if @session[:user] and authorize?(@session[:user])
50
+ return true
51
+ end
52
+
53
+ # store current location so that we can
54
+ # come back after the user logged in
55
+ store_location
56
+
57
+ # call overwriteable reaction to unauthorized access
58
+ access_denied
59
+ return false
60
+ end
61
+
62
+ # overwrite if you want to have special behavior in case the user is not authorized
63
+ # to access the current operation.
64
+ # the default action is to redirect to the login screen
65
+ # example use :
66
+ # a popup window might just close itself for instance
67
+ def access_denied
68
+ redirect_to :controller=>"/<%= file_name %>", :action =>"login"
69
+ end
70
+
71
+ # store current uri in the session.
72
+ # we can return to this location by calling return_location
73
+ def store_location
74
+ @session[:return_to] = @request.request_uri
75
+ end
76
+
77
+ # move to the last store_location call or to the passed default one
78
+ def redirect_back_or_default(default)
79
+ if @session[:return_to].nil?
80
+ redirect_to default
81
+ else
82
+ redirect_to_url @session[:return_to]
83
+ @session[:return_to] = nil
84
+ end
85
+ end
86
+
87
+ end
data/templates/user.rb ADDED
@@ -0,0 +1,59 @@
1
+ require 'digest/sha1'
2
+
3
+ # this model expects a certain database layout and its based on the name/login pattern.
4
+ class User < ActiveRecord::Base
5
+
6
+ # Please change the salt to something else,
7
+ # Every application should use a different one
8
+ @@salt = 'change-me'
9
+ cattr_accessor :salt
10
+
11
+ # Authenticate a user.
12
+ #
13
+ # Example:
14
+ # @user = User.authenticate('bob', 'bobpass')
15
+ #
16
+ def self.authenticate(login, pass)
17
+ find_first(["login = ? AND password = ?", login, sha1(pass)])
18
+ end
19
+
20
+
21
+ protected
22
+
23
+ # Apply SHA1 encryption to the supplied password.
24
+ # We will additionally surround the password with a salt
25
+ # for additional security.
26
+ def self.sha1(pass)
27
+ Digest::SHA1.hexdigest("#{salt}--#{pass}--")
28
+ end
29
+
30
+ before_create :crypt_password
31
+
32
+ # Before saving the record to database we will crypt the password
33
+ # using SHA1.
34
+ # We never store the actual password in the DB.
35
+ def crypt_password
36
+ write_attribute "password", self.class.sha1(password)
37
+ end
38
+
39
+ before_update :crypt_unless_empty
40
+
41
+ # If the record is updated we will check if the password is empty.
42
+ # If its empty we assume that the user didn't want to change his
43
+ # password and just reset it to the old value.
44
+ def crypt_unless_empty
45
+ if password.empty?
46
+ user = self.class.find(self.id)
47
+ self.password = user.password
48
+ else
49
+ write_attribute "password", self.class.sha1(password)
50
+ end
51
+ end
52
+
53
+ validates_uniqueness_of :login, :on => :create
54
+
55
+ validates_confirmation_of :password
56
+ validates_length_of :login, :within => 3..40
57
+ validates_length_of :password, :within => 5..40
58
+ validates_presence_of :login, :password, :password_confirmation
59
+ end
@@ -0,0 +1,91 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+
3
+ # Set salt to 'change-me' because thats what the fixtures assume.
4
+ User.salt = 'change-me'
5
+
6
+ class UserTest < Test::Unit::TestCase
7
+
8
+ fixtures :users
9
+
10
+ def test_auth
11
+
12
+ assert_equal @bob, User.authenticate("bob", "test")
13
+ assert_nil User.authenticate("nonbob", "test")
14
+
15
+ end
16
+
17
+ def test_disallowed_passwords
18
+
19
+ u = User.new
20
+ u.login = "nonbob"
21
+
22
+ u.password = u.password_confirmation = "tiny"
23
+ assert !u.save
24
+ assert u.errors.invalid?('password')
25
+
26
+ u.password = u.password_confirmation = "hugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehuge"
27
+ assert !u.save
28
+ assert u.errors.invalid?('password')
29
+
30
+ u.password = u.password_confirmation = ""
31
+ assert !u.save
32
+ assert u.errors.invalid?('password')
33
+
34
+ u.password = u.password_confirmation = "bobs_secure_password"
35
+ assert u.save
36
+ assert u.errors.empty?
37
+
38
+ end
39
+
40
+ def test_bad_logins
41
+
42
+ u = User.new
43
+ u.password = u.password_confirmation = "bobs_secure_password"
44
+
45
+ u.login = "x"
46
+ assert !u.save
47
+ assert u.errors.invalid?('login')
48
+
49
+ u.login = "hugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhug"
50
+ assert !u.save
51
+ assert u.errors.invalid?('login')
52
+
53
+ u.login = ""
54
+ assert !u.save
55
+ assert u.errors.invalid?('login')
56
+
57
+ u.login = "okbob"
58
+ assert u.save
59
+ assert u.errors.empty?
60
+
61
+ end
62
+
63
+
64
+ def test_collision
65
+ u = User.new
66
+ u.login = "existingbob"
67
+ u.password = u.password_confirmation = "bobs_secure_password"
68
+ assert !u.save
69
+ end
70
+
71
+
72
+ def test_create
73
+ u = User.new
74
+ u.login = "nonexistingbob"
75
+ u.password = u.password_confirmation = "bobs_secure_password"
76
+
77
+ assert u.save
78
+
79
+ end
80
+
81
+ def test_sha1
82
+ u = User.new
83
+ u.login = "nonexistingbob"
84
+ u.password = u.password_confirmation = "bobs_secure_password"
85
+ assert u.save
86
+
87
+ assert_equal '98740ff87bade6d895010bceebbd9f718e7856bb', u.password
88
+ end
89
+
90
+
91
+ end
@@ -0,0 +1,16 @@
1
+ # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
2
+
3
+ bob:
4
+ id: 1000001
5
+ login: bob
6
+ password: 9a91e1d8d95b6315991a88121bb0aa9f03ba0dfc # test
7
+
8
+ existingbob:
9
+ id: 1000002
10
+ login: existingbob
11
+ password: 9a91e1d8d95b6315991a88121bb0aa9f03ba0dfc # test
12
+
13
+ longbob:
14
+ id: 1000003
15
+ login: longbob
16
+ password: 8e9b1a9a38e66ca572a5e8fdac8e256848842dfa # longtest
@@ -0,0 +1,22 @@
1
+ <%%= start_form_tag :action=> "login" %>
2
+
3
+ <div title="Account login" id="loginform" class="form">
4
+ <h3>Please login</h3>
5
+
6
+ <%% if @flash['notice'] %>
7
+ <div id="message"><%%= @flash['notice'] %></div>
8
+ <%% end %>
9
+
10
+ <label for="user_login">Login:</label><br/>
11
+ <input type="text" name="user_login" id="user_login" size="30" value="<%= @login %>"/><br/>
12
+
13
+ <label for="user_password">Password:</label><br/>
14
+ <input type="password" name="user_password" id="user_password" size="30"/>
15
+
16
+ <br/>
17
+ <input type="submit" name="login" value="Login &#187;" class="primary" />
18
+
19
+ </div>
20
+
21
+ <%%= end_form_tag %>
22
+
@@ -0,0 +1,10 @@
1
+
2
+ <div class="memo">
3
+ <h3>Logoff</h3>
4
+
5
+ <p>You are now logged out of the system...</p>
6
+
7
+ <%%= link_to "&#171; login", :action=>"login"%>
8
+
9
+ </div>
10
+
@@ -0,0 +1,17 @@
1
+ <%%= start_form_tag :action=> "signup" %>
2
+
3
+ <div title="Account signup" id="signupform" class="form">
4
+ <h3>Signup</h3>
5
+ <%%= error_messages_for 'user' %><br/>
6
+
7
+ <label for="user_login">Desired login:</label><br/>
8
+ <%%= text_field "user", "login", :size => 30 %><br/>
9
+ <label for="user_password">Choose password:</label><br/>
10
+ <%%= password_field "user", "password", :size => 30 %><br/>
11
+ <label for="user_password_confirmation">Confirm password:</label><br/>
12
+ <%%= password_field "user", "password_confirmation", :size => 30 %><br/>
13
+
14
+ <input type="submit" value="Signup &#187;" class="primary" />
15
+
16
+ <%%= end_form_tag %>
17
+
@@ -0,0 +1,13 @@
1
+
2
+ <div class="memo">
3
+ <h3>Welcome</h3>
4
+
5
+ <p>You are now logged into the system...</p>
6
+ <p>
7
+ Since you are here it's safe to assume the application never called store_location, otherwise
8
+ you would have been redirected somewhere else after a successful login.
9
+ </p>
10
+
11
+ <%%= link_to "&#171; logout", :action=>"logout"%>
12
+
13
+ </div>
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.10.1
3
+ specification_version: 1
4
+ name: login_generator
5
+ version: !ruby/object:Gem::Version
6
+ version: 1.1.0
7
+ date: 2005-04-09
8
+ summary: "[Rails] Login generator."
9
+ require_paths:
10
+ - "."
11
+ email: tobi@leetsoft.com
12
+ homepage: http://www.rubyonrails.org/show/Generators
13
+ rubyforge_project:
14
+ description: Generates Rails code implementing a login system for your Rails app.
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: false
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ -
22
+ - ">"
23
+ - !ruby/object:Gem::Version
24
+ version: 0.0.0
25
+ version:
26
+ platform: ruby
27
+ authors:
28
+ - Tobias Luetke
29
+ files:
30
+ - USAGE
31
+ - login_generator.rb
32
+ - templates/controller.rb
33
+ - templates/controller_test.rb
34
+ - templates/helper.rb
35
+ - templates/login_system.rb
36
+ - templates/README
37
+ - templates/user.rb
38
+ - templates/user_test.rb
39
+ - templates/users.yml
40
+ - templates/view_login.rhtml
41
+ - templates/view_logout.rhtml
42
+ - templates/view_signup.rhtml
43
+ - templates/view_welcome.rhtml
44
+ test_files: []
45
+ rdoc_options: []
46
+ extra_rdoc_files: []
47
+ executables: []
48
+ extensions: []
49
+ requirements: []
50
+ dependencies:
51
+ - !ruby/object:Gem::Dependency
52
+ name: rails
53
+ version_requirement:
54
+ version_requirements: !ruby/object:Gem::Version::Requirement
55
+ requirements:
56
+ -
57
+ - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: 0.10.0
60
+ version: