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
--- /dev/null
+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
--- /dev/null
+module LoginHelper
+end
--- /dev/null
+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
--- /dev/null
+<%= stylesheet_link_tag "scaffold", "depot", :media => "all" %>
+<!--<%= image_tag("logo.png") %> <%= @page_title || "Pragmatic Bookshelf" %>-->
+
+<%= 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 %>
--- /dev/null
+<div class="depot-form">
+
+ <%= error_messages_for 'user' %>
+
+ <fieldset>
+ <legend>Enter User Details</legend>
+
+ <% form_for :user do |form| %>
+ <p>
+ <label for="user_name">Name:</label>
+ <%= form.text_field :name, :size => 40 %>
+ </p>
+
+ <p>
+ <label for="user_password">Password:</label>
+ <%= form.password_field :password, :size => 40 %>
+ </p>
+
+ <p>
+ <label for="user_password_confirmation">Confirm:</label>
+ <%= form.password_field :password_confirmation, :size => 40 %>
+ </p>
+
+ <%= submit_tag "Add User", :class => "submit" %>
+
+ <% end %>
+ </fieldset>
+</div>
--- /dev/null
+<h1>Login#delete_user</h1>
+<p>Find me in app/views/login/delete_user.rhtml</p>
--- /dev/null
+<h1>Welcome</h1>
+It's <%= Time.now %>.
+
+FIXME: Add menu here!
--- /dev/null
+<h1>Employees</h1>
+<ul>
+ <% for user in @all_users %>
+ <li><%=link_to "[X]", { # link_to options
+ :controller => 'login',
+ :action => 'delete_user',
+ :id => user },
+ { # html options
+ :method => :post,
+ :confirm => "Really delete #{user.name}?"
+ } %>
+ <%=h user.name %>
+ </li>
+ <% end %>
+</ul>
--- /dev/null
+<div class="depot-form">
+ <fieldset>
+ <legend>Please Log In</legend>
+
+ <% form_tag do %>
+ <p>
+ <label for="name">Name:</label>
+ <%= text_field_tag :name, params[:name] %>
+ </p>
+
+ <p>
+ <label for="password">Password:</label>
+ <%= password_field_tag :password, params[:password] %>
+ </p>
+
+ <p>
+ <%= submit_tag "Login" %>
+ </p>
+ <% end %>
+ </fieldset>
+</div>
--- /dev/null
+<h1>Login#logout</h1>
+<p>Find me in app/views/login/logout.rhtml</p>
--- /dev/null
+class CreateUsers < ActiveRecord::Migration
+ def self.up
+ create_table :users do |t|
+ t.column :name, :string, :null => 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
# 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
# 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
--- /dev/null
+/* 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;
+}
--- /dev/null
+# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
+one:
+ id: 1
+two:
+ id: 2
--- /dev/null
+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
--- /dev/null
+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