From 0016f273e94771888df4b73eb334d269f1d4975f Mon Sep 17 00:00:00 2001 From: "Ira W. Snyder" Date: Fri, 23 Nov 2007 23:35:18 -0800 Subject: [PATCH] Add Login system Signed-off-by: Ira W. Snyder --- app/controllers/application.rb | 11 +- app/controllers/login_controller.rb | 54 ++++++ app/helpers/login_helper.rb | 2 + app/models/user.rb | 59 ++++++ app/views/layouts/admin.rhtml | 11 ++ app/views/login/add_user.rhtml | 28 +++ app/views/login/delete_user.rhtml | 2 + app/views/login/index.rhtml | 4 + app/views/login/list_users.rhtml | 15 ++ app/views/login/login.rhtml | 21 +++ app/views/login/logout.rhtml | 2 + db/development.sqlite3 | Bin 24576 -> 24576 bytes db/migrate/027_create_users.rb | 14 ++ db/schema.rb | 9 +- public/stylesheets/depot.css | 227 +++++++++++++++++++++++ test/fixtures/users.yml | 5 + test/functional/login_controller_test.rb | 18 ++ test/unit/user_test.rb | 10 + 18 files changed, 490 insertions(+), 2 deletions(-) create mode 100644 app/controllers/login_controller.rb create mode 100644 app/helpers/login_helper.rb create mode 100644 app/models/user.rb create mode 100644 app/views/layouts/admin.rhtml create mode 100644 app/views/login/add_user.rhtml create mode 100644 app/views/login/delete_user.rhtml create mode 100644 app/views/login/index.rhtml create mode 100644 app/views/login/list_users.rhtml create mode 100644 app/views/login/login.rhtml create mode 100644 app/views/login/logout.rhtml create mode 100644 db/migrate/027_create_users.rb create mode 100644 public/stylesheets/depot.css create mode 100644 test/fixtures/users.yml create mode 100644 test/functional/login_controller_test.rb create mode 100644 test/unit/user_test.rb diff --git a/app/controllers/application.rb b/app/controllers/application.rb index 4e5f623..e9da3d0 100644 --- a/app/controllers/application.rb +++ b/app/controllers/application.rb @@ -3,5 +3,14 @@ class ApplicationController < ActionController::Base # Pick a unique cookie name to distinguish our session data from others' - session :session_key => '_try5_session_id' + session :session_key => '_prippropprix_session_id' + + private + + def authorize + unless User.find_by_id(session[:user_id]) + flash[:notice] = "Please log in" + redirect_to :controller => "login", :action => "login" + end + end end diff --git a/app/controllers/login_controller.rb b/app/controllers/login_controller.rb new file mode 100644 index 0000000..9352437 --- /dev/null +++ b/app/controllers/login_controller.rb @@ -0,0 +1,54 @@ +class LoginController < ApplicationController + layout "admin" + + # Make sure that a user logs in before doing any action here + before_filter :authorize, :except => :login + + def add_user + @user = User.new(params[:user]) + if request.post? and @user.save + flash.now[:notice] = "User #{@user.name} created" + @user = User.new + end + end + + def login + session[:user_id] = nil + if request.post? + user = User.authenticate(params[:name], params[:password]) + if user + session[:user_id] = user.id + redirect_to :action => 'index' + else + flash[:notice] = "Invalid user/password combination" + end + end + end + + def logout + session[:user_id] = nil + flash[:notice] = "Logged Out" + redirect_to :action => :login + end + + def index + # No code needed + end + + def delete_user + if request.post? + user = User.find(params[:id]) + begin + user.destroy + flash[:notice] = "User #{user.name} deleted" + rescue Exception => e + flash[:notice] = e.message + end + end + redirect_to(:action => :list_users) + end + + def list_users + @all_users = User.find(:all) + end +end diff --git a/app/helpers/login_helper.rb b/app/helpers/login_helper.rb new file mode 100644 index 0000000..a0418e3 --- /dev/null +++ b/app/helpers/login_helper.rb @@ -0,0 +1,2 @@ +module LoginHelper +end diff --git a/app/models/user.rb b/app/models/user.rb new file mode 100644 index 0000000..1edfc41 --- /dev/null +++ b/app/models/user.rb @@ -0,0 +1,59 @@ +require 'digest/sha1' + +class User < ActiveRecord::Base + validates_presence_of :name + validates_uniqueness_of :name + + validates_length_of :password, :minimum => 6 + + attr_accessor :password_confirmation + validates_confirmation_of :password + + def validate + errors.add_to_base("Missing password") if hashed_password.blank? + end + + def self.authenticate(name, password) + user = self.find_by_name(name) + if user + expected_password = encrypted_password(password, user.salt) + if user.hashed_password != expected_password + user = nil + end + end + user + end + + # 'password' is a virtual attribute + def password + @password + end + + def password=(pwd) + @password = pwd + create_new_salt + self.hashed_password = User.encrypted_password(self.password, self.salt) + end + + def after_destroy + if User.count.zero? + raise "Can't delete last user" + end + end + + + private + + + def self.encrypted_password(password, salt) + # According to the RoR book, 'wibble' makes it harder to guess, which + # I seriously doubt given my background in crypto + string_to_hash = password + 'wibble' + salt + Digest::SHA1.hexdigest(string_to_hash) + end + + def create_new_salt + self.salt = self.object_id.to_s + rand.to_s + end + +end diff --git a/app/views/layouts/admin.rhtml b/app/views/layouts/admin.rhtml new file mode 100644 index 0000000..c6dda9a --- /dev/null +++ b/app/views/layouts/admin.rhtml @@ -0,0 +1,11 @@ +<%= stylesheet_link_tag "scaffold", "depot", :media => "all" %> + + +<%= link_to "Products", :controller => 'admin', :action => 'list' %> +<%= link_to "List users", :controller => 'login', :action => 'list_users' %> +<%= link_to "Add user", :controller => 'login', :action => 'add_user' %> +<%= link_to "Logout", :controller => 'login', :action => 'logout' %> + +<% if flash[:notice] -%> +<%= flash[:notice] %> +<% end -%> <%= yield :layout %> diff --git a/app/views/login/add_user.rhtml b/app/views/login/add_user.rhtml new file mode 100644 index 0000000..f76116a --- /dev/null +++ b/app/views/login/add_user.rhtml @@ -0,0 +1,28 @@ +
+ + <%= error_messages_for 'user' %> + +
+ Enter User Details + + <% form_for :user do |form| %> +

+ + <%= form.text_field :name, :size => 40 %> +

+ +

+ + <%= form.password_field :password, :size => 40 %> +

+ +

+ + <%= form.password_field :password_confirmation, :size => 40 %> +

+ + <%= submit_tag "Add User", :class => "submit" %> + + <% end %> +
+
diff --git a/app/views/login/delete_user.rhtml b/app/views/login/delete_user.rhtml new file mode 100644 index 0000000..054c9bd --- /dev/null +++ b/app/views/login/delete_user.rhtml @@ -0,0 +1,2 @@ +

Login#delete_user

+

Find me in app/views/login/delete_user.rhtml

diff --git a/app/views/login/index.rhtml b/app/views/login/index.rhtml new file mode 100644 index 0000000..872918c --- /dev/null +++ b/app/views/login/index.rhtml @@ -0,0 +1,4 @@ +

Welcome

+It's <%= Time.now %>. + +FIXME: Add menu here! diff --git a/app/views/login/list_users.rhtml b/app/views/login/list_users.rhtml new file mode 100644 index 0000000..d730d82 --- /dev/null +++ b/app/views/login/list_users.rhtml @@ -0,0 +1,15 @@ +

Employees

+ diff --git a/app/views/login/login.rhtml b/app/views/login/login.rhtml new file mode 100644 index 0000000..926bbc7 --- /dev/null +++ b/app/views/login/login.rhtml @@ -0,0 +1,21 @@ +
+
+ Please Log In + + <% form_tag do %> +

+ + <%= text_field_tag :name, params[:name] %> +

+ +

+ + <%= password_field_tag :password, params[:password] %> +

+ +

+ <%= submit_tag "Login" %> +

+ <% end %> +
+
diff --git a/app/views/login/logout.rhtml b/app/views/login/logout.rhtml new file mode 100644 index 0000000..2afdc9c --- /dev/null +++ b/app/views/login/logout.rhtml @@ -0,0 +1,2 @@ +

Login#logout

+

Find me in app/views/login/logout.rhtml

diff --git a/db/development.sqlite3 b/db/development.sqlite3 index 11394f606e13396c816a7459f7a375e391ab5d17..e6ad50fae99ef9f0cd08c9df19e9aba0b3e1fd61 100644 GIT binary patch delta 343 zcmY++O-{l<00rQVMKS&nG=W5jRyJq=)6P$4W(imm6WAEKFvggf=~PSzm=@zwh!>D> z18-1Wxa9^eJb^dx2v&E##aq0We3J#3EWmRqUpD^dEUABHLFr}H+Sp7VU?zr{m_ahV zG#|UUt9DMvfMTs&8)HXF0VPxlLl`inToqy-O3q0bNYtR1bLue2>|>6Q%OQje5k|2a zcEO6VlVZ1#P!tnEh)y+5DN%yE%Ekl}s+cB$EfCUJ3f!Q(#I_M)Zt5}Ya)K#hoDzar z_*%~#ZY>+Wv4qSLcn6N*&!+9#^rDb07K`91KNfPJ?_(Xsf3@B|_nJM=>NQ&(&-$I_ z-+pVmZdLjs?(5+CPQ>xUC<^9Zg}qEH2IKkcV81Me;zmamOO8eZEr!;KciQwjJ*yg4 KkKP>rW99A< false + t.column :hashed_password, :string + t.column :salt, :string + t.column :manager, :boolean, :default => false + end + end + + def self.down + drop_table :users + end +end diff --git a/db/schema.rb b/db/schema.rb index 192232a..f9d22a0 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -2,7 +2,7 @@ # migrations feature of ActiveRecord to incrementally modify your database, and # then regenerate this schema definition. -ActiveRecord::Schema.define(:version => 26) do +ActiveRecord::Schema.define(:version => 27) do create_table "coitems", :force => true do |t| t.column "customer_id", :integer @@ -77,6 +77,13 @@ ActiveRecord::Schema.define(:version => 26) do # Could not dump table "sqlite_sequence" because of following StandardError # Unknown type '' for column 'name' + create_table "users", :force => true do |t| + t.column "name", :string, :null => false + t.column "hashed_password", :string + t.column "salt", :string + t.column "manager", :boolean, :default => false + end + create_table "video_policies", :force => true do |t| t.column "day", :integer, :null => false t.column "fee", :decimal, :precision => 8, :scale => 2, :null => false diff --git a/public/stylesheets/depot.css b/public/stylesheets/depot.css new file mode 100644 index 0000000..247c343 --- /dev/null +++ b/public/stylesheets/depot.css @@ -0,0 +1,227 @@ +/* Global styles */ + +/* START:notice */ +#notice { + border: 2px solid red; + padding: 1em; + margin-bottom: 2em; + background-color: #f0f0f0; + font: bold smaller sans-serif; +} +/* END:notice */ + +/* Styles for admin/list */ + +#product-list .list-title { + color: #244; + font-weight: bold; + font-size: larger; +} + +#product-list .list-image { + width: 60px; + height: 70px; +} + + +#product-list .list-actions { + font-size: x-small; + text-align: right; + padding-left: 1em; +} + +#product-list .list-line-even { + background: #e0f8f8; +} + +#product-list .list-line-odd { + background: #f8b0f8; +} + + +/* Styles for main page */ + +#banner { + background: #9c9; + padding-top: 10px; + padding-bottom: 10px; + border-bottom: 2px solid; + font: small-caps 40px/40px "Times New Roman", serif; + color: #282; + text-align: center; +} + +#banner img { + float: left; +} + +#columns { + background: #141; +} + +#main { + margin-left: 15em; + padding-top: 4ex; + padding-left: 2em; + background: white; +} + +#side { + float: left; + padding-top: 1em; + padding-left: 1em; + padding-bottom: 1em; + width: 14em; + background: #141; +} + +#side a { + color: #bfb; + font-size: small; +} + +h1 { + font: 150% sans-serif; + color: #226; + border-bottom: 3px dotted #77d; +} + +/* An entry in the store catalog */ + +#store .entry { + border-bottom: 1px dotted #77d; +} + +#store .title { + font-size: 120%; + font-family: sans-serif; +} + +#store .entry img { + width: 75px; + float: left; +} + + +#store .entry h3 { + margin-bottom: 2px; + color: #227; +} + +#store .entry p { + margin-top: 0px; + margin-bottom: 0.8em; +} + +#store .entry .price-line { +} + +#store .entry .add-to-cart { + position: relative; +} + +#store .entry .price { + color: #44a; + font-weight: bold; + margin-right: 2em; +} + +/* START:inline */ +#store .entry form, #store .entry form div { + display: inline; +} +/* END:inline */ + +/* START:cart */ +/* Styles for the cart in the main page and the sidebar */ + +.cart-title { + font: 120% bold; +} + +.item-price, .total-line { + text-align: right; +} + +.total-line .total-cell { + font-weight: bold; + border-top: 1px solid #595; +} + + +/* Styles for the cart in the sidebar */ + +#cart, #cart table { + font-size: smaller; + color: white; +} + +#cart table { + border-top: 1px dotted #595; + border-bottom: 1px dotted #595; + margin-bottom: 10px; +} +/* END:cart */ + +/* Styles for order form */ + +.depot-form fieldset { + background: #efe; +} + +.depot-form legend { + color: #dfd; + background: #141; + font-family: sans-serif; + padding: 0.2em 1em; +} + +.depot-form label { + width: 5em; + float: left; + text-align: right; + margin-right: 0.5em; + display: block; +} + +.depot-form .submit { + margin-left: 5.5em; +} + +/* The error box */ + +.fieldWithErrors { + padding: 2px; + background-color: red; + display: table; +} + +#errorExplanation { + width: 400px; + border: 2px solid red; + padding: 7px; + padding-bottom: 12px; + margin-bottom: 20px; + background-color: #f0f0f0; +} + +#errorExplanation h2 { + text-align: left; + font-weight: bold; + padding: 5px 5px 5px 15px; + font-size: 12px; + margin: -7px; + background-color: #c00; + color: #fff; +} + +#errorExplanation p { + color: #333; + margin-bottom: 0; + padding: 5px; +} + +#errorExplanation ul li { + font-size: 12px; + list-style: square; +} diff --git a/test/fixtures/users.yml b/test/fixtures/users.yml new file mode 100644 index 0000000..b49c4eb --- /dev/null +++ b/test/fixtures/users.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +one: + id: 1 +two: + id: 2 diff --git a/test/functional/login_controller_test.rb b/test/functional/login_controller_test.rb new file mode 100644 index 0000000..a21d438 --- /dev/null +++ b/test/functional/login_controller_test.rb @@ -0,0 +1,18 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'login_controller' + +# Re-raise errors caught by the controller. +class LoginController; def rescue_action(e) raise e end; end + +class LoginControllerTest < Test::Unit::TestCase + def setup + @controller = LoginController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/test/unit/user_test.rb b/test/unit/user_test.rb new file mode 100644 index 0000000..5468f7a --- /dev/null +++ b/test/unit/user_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class UserTest < Test::Unit::TestCase + fixtures :users + + # Replace this with your real tests. + def test_truth + assert true + end +end -- 2.25.1