roles and the ability to log in with a user name

git-svn-id: http://svn.barleysodas.com/barleysodas/trunk@60 0f7b21a7-9e3a-4941-bbeb-ce5c7c368fa7
master
andrew 2007-12-23 00:32:08 +00:00
parent 55e2701538
commit fc863cddef
34 changed files with 653 additions and 15 deletions

View File

@ -1,6 +1,8 @@
class ApplicationController < ActionController::Base
session :session_key => '_barleysodas_session_id'
append_before_filter :block_prefetching_links
append_before_filter :authorized?
helper_method :logged_in?
##
# Ensures that the request was made using an Ajax request.
@ -10,6 +12,53 @@ class ApplicationController < ActionController::Base
true
end
##
# Determines if the user is logged in.
#
def logged_in?
return !session[:people_title].nil?
end
##
# Saves the request uri in the session for later redirect after a login.
#
def save_request_url
session[:request_url] = request.request_uri
end
##
# Checks to see if the currently requested uri is the same as the uri saved
# in the session.
#
def already_saved_request_url
return true if session[:request_url] and
session[:request_url] == request.request_uri
false
end
##
# Determines if a user can access an action.
#
def authorized?
return true if has_permission_for_action?
respond_to do |format|
format.html {
# prevent double-redirects to the login page if for some reason it is
# not allowed
unless logged_in? and !already_saved_request_url
save_request_url
redirect_to new_session_path
return
end
@content_title = 'Forbidden'
@secondary_title = ''
@hide_sidebar = true
render :template => 'shared/unauthorized'
}
format.xml { render :nothing => true, :status => 403 }
end
end
##
# Sane error and missing document messages.
#
@ -71,4 +120,26 @@ class ApplicationController < ActionController::Base
return false
end
end
##
# Finds a People Permission models and determines if the People has access
# to a particular aspect of the system. Also finds the Guest user and checks
# for the Guest Role.
#
def has_permission_for_action?
role = nil
if logged_in?
role = People.find_by_title(session[:people_title]).role rescue nil
end
logger.debug("role is #{role.inspect}")
role ||= Role.base_role
while role
return true if role.permissions.detect do |p|
p.controller.to_s == params[:controller].to_s and
p.action.to_s == params[:action].to_s
end
role = role.parent
end
false
end
end

View File

@ -41,6 +41,7 @@ class PeoplesController < ApplicationController
# POST /peoples.xml
def create
@people = People.new(params[:people])
set_people_role
@page = Page.new(params[:page])
@people.page = @page
respond_to do |format|
@ -60,6 +61,7 @@ class PeoplesController < ApplicationController
# PUT /peoples/1.xml
def update
@people.attributes = params[:people]
set_people_role
@page.attributes = params[:page]
respond_to do |format|
if @people.update_attributes(params[:people])
@ -91,4 +93,11 @@ class PeoplesController < ApplicationController
raise ActiveRecord::RecordNotFound.new if @people.nil?
@page = @people.page
end
def set_people_role
# set checks here for valid role assignment
if params[:people] and params[:people][:role_id]
@people.role_id = params[:people][:role_id]
end
end
end

View File

@ -0,0 +1,41 @@
class SessionsController < ApplicationController
def new
@content_title = 'Log In'
@secondary_title = ''
end
def create
@people = People.find_by_title(params[:login]) rescue nil
if @people
session[:people_title] = @people.title
respond_to do |format|
format.html {
flash[:info] = "Welcome, #{@people.title}"
if session[:request_url]
t_url = session[:request_url]
session[:request_url] = nil
redirect_to t_url
else
redirect_to '/'
end
}
format.xml { head :ok }
end
else
respond_to do |format|
format.html {
@content_title = 'Log In'
@secondary_title = ''
flash.now[:error] = 'Login failed, try again.'
render :action => 'new'
}
format.xml { render :xml => @beer.errors.to_xml, :status => 400 }
end
end
end
def destroy
reset_session
redirect_to '/'
end
end

View File

@ -0,0 +1,15 @@
module RolesHelper
def new_role_link
link_to 'New Role', new_role_path, { :title => 'Create a new role' }
end
def show_role_link(role)
link_to role.name, role_path(role.code),
{ :title => role.name }
end
def edit_role_link(role)
link_to 'Edit Role', edit_role_path(role.code),
{ :title => "Edit #{role.name}" }
end
end

View File

@ -0,0 +1,2 @@
module SessionsHelper
end

View File

@ -5,16 +5,11 @@ class People < ActiveRecord::Base
has_one_tuxwiki_page :owner_class => 'People'
belongs_to :role
attr_protected :role_id
validates_presence_of :role_id
before_create :set_base_role
protected
##
# Sets the Role to the top level model.
# Finds the Guest user for the system.
#
def set_base_role
self.role = Role.base_role
def self.guest_user
self.find_by_title('Guest') rescue nil
end
end

18
app/models/permission.rb Normal file
View File

@ -0,0 +1,18 @@
##
# Models the ability to perform an action in the system.
#
class Permission < ActiveRecord::Base
has_and_belongs_to_many :roles
validates_presence_of :controller, :action
def to_s # :nodoc:
"#{controller} :: #{action}"
end
##
# Helper to find the necessary models for a form edit.
#
def self.find_for_form
self.find(:all, :order => "controller ASC, action ASC")
end
end

39
app/models/role.rb Normal file
View File

@ -0,0 +1,39 @@
##
# This model is a grouping of Permission models associated to a particular
# People.
#
class Role < ActiveRecord::Base
has_many :peoples
belongs_to :parent, :foreign_key => 'parent_id', :class_name => 'Role'
validates_presence_of :code, :name
validates_uniqueness_of :code
validates_format_of :code, :with => /^([A-Za-z0-9])+$/,
:message => 'may only contain letters and numbers'
has_and_belongs_to_many :permissions
##
# Ensures that the Role does not have a parent of itself.
#
def validate
if !self.new_record? and self.parent_id == id
self.errors.add(:parent, 'cannot be self')
end
return false if self.errors.size > 0
true
end
##
# Returns a Role found by +code+ if the method is missing.
#
def self.method_missing(method_name, *args)
return self.find_by_code($1) if method_name.to_s =~ /^(.+)_role$/
super
end
##
# Returns a select-box compatible array.
#
def self.for_select
self.find(:all).collect { |x| [ x.name, x.id.to_s ] }
end
end

View File

@ -8,6 +8,9 @@
<script type="text/javascript">
<%= @content_for_script %>
</script>
<style type="text/css">
<%= @content_for_stylesheet %>
</style>
</head>
<body>
@ -27,6 +30,7 @@
<div id="content">
<%= yield %>
</div>
<% unless @hide_sidebar -%>
<div id="sidebar">
<%= link_to_unless_current 'Browse The Beer Wiki', pages_path -%><br />
<%= link_to_unless_current 'Browse Beers', beers_path -%><br />
@ -34,15 +38,17 @@
<%= link_to_unless_current 'Discussions', discussions_path -%><br />
<%= link_to_unless_current 'Peoples', peoples_path -%><br />
<%= link_to_unless_current 'Roles', roles_path -%><br />
<% unless logged_in? -%><%= link_to_unless_current 'Login', new_session_path -%><% else -%><%= link_to 'Logout', session_path(:id => session[:people_title]), :method => :delete -%><% end %>
<hr />
<%= yield :sidebar %>
</div>
<% end -%>
<br style="clear:both;" />
</div>
<div id="footer">
<hr />
<p><a href="http://www.penguincoder.org">PenguinCoder</a></p>
<p><a href="http://www.penguincoder.org" title="Built by PenguinCoder">PenguinCoder</a> | <a href="http://www.quotedprintable.com/pages/scribbish" title="Scribbish">Scribbish</a></p>
</div>
</div>

View File

@ -0,0 +1 @@
<li><%= check_box_tag 'role[permission_ids][]', permission.id, @role.permissions.include?(permission) -%> <%= permission -%></li>

View File

@ -0,0 +1,14 @@
<p>
<label for="role_name">Name</label> <%= text_field :role, :name %>
</p>
<p>
<label for="role_code">Code</label> <%= text_field :role, :code %>
</p>
<p>
<label for="role_parent_id">Parent</label> <%= select :role, :parent_id, Role.for_select, { :include_blank => true, :selected => @role.parent_id.to_s } %>
</p>
<h3>Permissions</h3>
<p><%= link_to_function "Check All", "set_all_checkboxes('role_form', 'role[permission_ids][]', true);" -%> <%= link_to_function "Uncheck All", "set_all_checkboxes('role_form', 'role[permission_ids][]', false);" -%>
<ul>
<%= render :partial => 'permission', :collection => Permission.find_for_form -%>
</ul>

View File

@ -0,0 +1,13 @@
<%= error_messages_for :role %>
<% form_for(:role, :url => role_path(@role.code), :html => { :id => 'role_form', :method => :put }) do |f| %>
<%= render :partial => 'role_form' %>
<p>
<%= submit_tag "Update" %>
</p>
<% end %>
<% content_for :sidebar do -%>
<%= new_role_link -%><br />
<%= show_role_link(@role) -%><br />
<% end -%>

View File

@ -0,0 +1,13 @@
<ul>
<% unless @roles.empty? -%>
<% for role in @roles %>
<li><%= show_role_link(role) -%></li>
<% end %>
<% else -%>
<li>No roles, yet.</li>
<% end -%>
</ul>
<% content_for :sidebar do -%>
<%= new_role_link -%><br />
<% end -%>

View File

@ -0,0 +1,8 @@
<%= error_messages_for :role %>
<% form_for(:role, :url => roles_path, :html => { :id => 'role_form' }) do |f| %>
<%= render :partial => 'role_form' %>
<p>
<%= submit_tag "Create" %>
</p>
<% end %>

View File

@ -0,0 +1,15 @@
<h1><%= @role.name -%></h1>
<ul>
<% unless @role.permissions.empty? -%>
<% @role.permissions.collect { |p| -%><li><%= p -%></li><% } -%>
<% else -%>
<li>No associated permissions.</li>
<% end -%>
</ul>
<% content_for :sidebar do -%>
<%= new_role_link -%><br />
<%= edit_role_link(@role) -%><br />
<%= link_to 'Destroy Role', role_path(@role.code), :confirm => 'Are you sure?', :method => :delete -%><br />
<% end -%>

View File

@ -0,0 +1,6 @@
<% form_for(:session, @people, :url => sessions_path, :html => { :method => :post }) do |f| -%>
<p>
<label for="login">People</label> <%= text_field_tag 'login' -%>
</p>
<%= submit_tag 'Login' %>
<% end -%>

View File

@ -0,0 +1 @@
<div style="text-align: center; margin: 5px auto; font-size: 1.5em;"><%= image_tag 'process-stop.png' -%> Sorry, you do not have permission to perform this action.</div>

View File

@ -1,7 +1,6 @@
ActionController::Routing::Routes.draw do |map|
map.resources :roles
map.resources :beers, :breweries, :pages, :discussions, :peoples
map.resources :beers, :breweries, :pages, :discussions, :peoples, :roles,
:sessions
map.connect ':controller/:action/:id.:format'
map.connect ':controller/:action/:id'

View File

@ -3,6 +3,7 @@ class CreatePeoples < ActiveRecord::Migration
create_table :peoples do |t|
t.column :title, :string
end
People.create :title => 'Guest', :page => Page.new
end
def self.down

View File

@ -0,0 +1,24 @@
class CreateRoles < ActiveRecord::Migration
def self.up
create_table :roles do |t|
t.column :name, :string
t.column :code, :string
t.column :parent_id, :integer
end
add_index :roles, :parent_id
add_index :roles, :code
add_column :peoples, :role_id, :integer
add_index :peoples, :role_id
br = Role.create :code => 'base', :name => 'Base Role'
ar = Role.create :code => 'admin', :name => 'Administrative Role',
:parent_id => br.id
g = People.guest_user
g.role = br
g.save
end
def self.down
drop_table :roles
remove_column :peoples, :role_id
end
end

View File

@ -0,0 +1,20 @@
class CreatePermissions < ActiveRecord::Migration
def self.up
create_table :permissions do |t|
t.column :controller, :string
t.column :action, :string
t.column :http_method, :string
end
create_table :permissions_roles, :id => false do |t|
t.column :permission_id, :integer
t.column :role_id, :integer
end
add_index :permissions_roles, :permission_id
add_index :permissions_roles, :role_id
end
def self.down
drop_table :permissions
drop_table :permissions_roles
end
end

43
generate_permissions Normal file
View File

@ -0,0 +1,43 @@
Permission.destroy_all
base_actions = ApplicationController.action_methods
# i should probably figure out all of the children of ApplicationController
# rather than defining them here.
controllers = [ AutocompleteController, SessionsController, PagesController,
PeoplesController, BeersController, BreweriesController, RolesController,
DiscussionsController ]
controllers.each do |c|
actions = c.action_methods - base_actions
cname = c.controller_name
actions.each { |a| Permission.create(:controller => cname, :action => a) }
end
r = Role.base_role
Permission.find(:all,
:conditions => [ 'controller = ?', 'autocomplete' ]).each do |p|
r.permissions << p
end
Permission.find(:all,
:conditions => [ 'controller = ?', 'sessions' ]).each do |p|
r.permissions << p
end
Permission.find(:all,
:conditions => [ 'controller = ?', 'pages' ]).each do |p|
next if [ 'new', 'create', 'edit', 'update', 'destroy' ].include?(p.action)
r.permissions << p
end
r2 = Role.admin_role
Permission.find(:all).each do |p|
r2.permissions << p unless r.permissions.include?(p)
end
p = People.new :title => 'penguincoder'
page = Page.new
p.page = page
p.role = r2
p.save
puts "All permissions created"

View File

@ -0,0 +1,13 @@
namespace :barleysodas do
desc "Saves permission models to the test fixture file"
task :extract_permissions => :environment do
i = "000"
File.open("#{RAILS_ROOT}/test/fixtures/permissions.yml", 'w') do |file|
p = Permission.find(:all)
file.write p.inject({}) { |hash, record|
hash["permissions_#{i.succ!}"] = record.attributes.reject { |key, val| key == "id" }
hash
}.to_yaml
end
end
end

View File

@ -0,0 +1,8 @@
namespace :barleysodas do
desc "Loads permission models from the test fixture file"
task :load_permissions => :environment do
YAML::load_file("#{RAILS_ROOT}/test/fixtures/permissions.yml").each do |k,p|
Permission.create(p)
end
end
end

Binary file not shown.

After

Width:  |  Height:  |  Size: 820 B

View File

@ -1,2 +1,15 @@
// Place your application-specific JavaScript functions and classes here
// This file is automatically included by javascript_include_tag :defaults
function set_all_checkboxes(form_name, field_name, check_value)
{
if(!document.forms[form_name])
return;
var objCheckBoxes = document.forms[form_name].elements[field_name];
if(!objCheckBoxes)
return;
var countCheckBoxes = objCheckBoxes.length;
if(!countCheckBoxes)
objCheckBoxes.checked = check_value;
else
// set the check value for all check boxes
for(var i = 0; i < countCheckBoxes; i++)
objCheckBoxes[i].checked = check_value;
}

View File

@ -2,3 +2,4 @@
one:
id: 1
title: penguin coder
role_id: 1

145
test/fixtures/permissions.yml vendored Normal file
View File

@ -0,0 +1,145 @@
---
permissions_030:
http_method:
action: destroy
controller: peoples
permissions_019:
http_method:
action: new
controller: pages
permissions_008:
http_method:
action: update
controller: breweries
permissions_031:
http_method:
action: new
controller: roles
permissions_020:
http_method:
action: update
controller: pages
permissions_009:
http_method:
action: create
controller: breweries
permissions_032:
http_method:
action: update
controller: roles
permissions_021:
http_method:
action: create
controller: pages
permissions_010:
http_method:
action: edit
controller: breweries
permissions_033:
http_method:
action: create
controller: roles
permissions_022:
http_method:
action: edit
controller: pages
permissions_011:
http_method:
action: index
controller: breweries
permissions_034:
http_method:
action: edit
controller: roles
permissions_023:
http_method:
action: index
controller: pages
permissions_012:
http_method:
action: destroy
controller: breweries
permissions_001:
http_method:
action: new
controller: beers
permissions_035:
http_method:
action: index
controller: roles
permissions_024:
http_method:
action: destroy
controller: pages
permissions_013:
http_method:
action: new
controller: discussions
permissions_002:
http_method:
action: update
controller: beers
permissions_036:
http_method:
action: destroy
controller: roles
permissions_025:
http_method:
action: new
controller: peoples
permissions_014:
http_method:
action: update
controller: discussions
permissions_003:
http_method:
action: create
controller: beers
permissions_026:
http_method:
action: update
controller: peoples
permissions_015:
http_method:
action: create
controller: discussions
permissions_004:
http_method:
action: edit
controller: beers
permissions_027:
http_method:
action: create
controller: peoples
permissions_016:
http_method:
action: edit
controller: discussions
permissions_005:
http_method:
action: index
controller: beers
permissions_028:
http_method:
action: edit
controller: peoples
permissions_017:
http_method:
action: index
controller: discussions
permissions_006:
http_method:
action: destroy
controller: beers
permissions_029:
http_method:
action: index
controller: peoples
permissions_018:
http_method:
action: destroy
controller: discussions
permissions_007:
http_method:
action: new
controller: breweries

9
test/fixtures/roles.yml vendored Normal file
View File

@ -0,0 +1,9 @@
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
one:
id: 1
code: base
name: Base Role
two:
id: 2
code: admin
name: Administrative Role

View File

@ -26,7 +26,7 @@ class PeoplesControllerTest < Test::Unit::TestCase
def test_should_create_people
old_count = People.count
post :create, :people => { :title => '1' }
post :create, :people => { :title => 'mypeople', :role_id => 1 }
assert_equal old_count+1, People.count
assert_redirected_to people_path(assigns(:people).page.title_for_url)

View File

@ -0,0 +1,57 @@
require File.dirname(__FILE__) + '/../test_helper'
require 'roles_controller'
# Re-raise errors caught by the controller.
class RolesController; def rescue_action(e) raise e end; end
class RolesControllerTest < Test::Unit::TestCase
fixtures :roles
def setup
@controller = RolesController.new
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
end
def test_should_get_index
get :index
assert_response :success
assert assigns(:roles)
end
def test_should_get_new
get :new
assert_response :success
end
def test_should_create_role
old_count = Role.count
post :create, :role => { :code => 'test', :name => 'test role' }
assert_equal old_count+1, Role.count
assert_redirected_to role_path(assigns(:role).code)
end
def test_should_show_role
get :show, :id => 'base'
assert_response :success
end
def test_should_get_edit
get :edit, :id => 'base'
assert_response :success
end
def test_should_update_role
put :update, :id => 'base', :role => { :name => 'base role new!' }
assert_redirected_to role_path(assigns(:role).code)
end
def test_should_destroy_role
old_count = Role.count
delete :destroy, :id => 'base'
assert_equal old_count-1, Role.count
assert_redirected_to roles_path
end
end

View File

@ -0,0 +1,18 @@
require File.dirname(__FILE__) + '/../test_helper'
require 'sessions_controller'
# Re-raise errors caught by the controller.
class SessionsController; def rescue_action(e) raise e end; end
class SessionsControllerTest < Test::Unit::TestCase
def setup
@controller = SessionsController.new
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
end
# Replace this with your real tests.
def test_truth
assert true
end
end

View File

@ -0,0 +1,10 @@
require File.dirname(__FILE__) + '/../test_helper'
class PermissionTest < Test::Unit::TestCase
fixtures :permissions
# Replace this with your real tests.
def test_truth
assert true
end
end

10
test/unit/role_test.rb Normal file
View File

@ -0,0 +1,10 @@
require File.dirname(__FILE__) + '/../test_helper'
class RoleTest < Test::Unit::TestCase
fixtures :roles
# Replace this with your real tests.
def test_truth
assert true
end
end