login_generator 1.1.0

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/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: