we5-browsercms 3.0.2 → 3.0.5

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.
Files changed (76) hide show
  1. data/README.markdown +1 -0
  2. data/app/controllers/cms/content_block_controller.rb +25 -2
  3. data/app/controllers/cms/content_controller.rb +31 -2
  4. data/app/controllers/cms/dashboard_controller.rb +2 -1
  5. data/app/controllers/cms/error_handling.rb +9 -2
  6. data/app/controllers/cms/links_controller.rb +2 -0
  7. data/app/controllers/cms/pages_controller.rb +22 -18
  8. data/app/controllers/cms/section_nodes_controller.rb +1 -1
  9. data/app/controllers/cms/sections_controller.rb +12 -7
  10. data/app/controllers/cms/sessions_controller.rb +17 -10
  11. data/app/controllers/cms/users_controller.rb +8 -6
  12. data/app/helpers/cms/application_helper.rb +2 -6
  13. data/app/helpers/cms/menu_helper.rb +118 -146
  14. data/app/helpers/cms/page_helper.rb +2 -2
  15. data/app/models/attachment.rb +2 -2
  16. data/app/models/forgot_password_mailer.rb +12 -0
  17. data/app/models/group.rb +13 -2
  18. data/app/models/guest_user.rb +9 -3
  19. data/app/models/link.rb +2 -2
  20. data/app/models/page.rb +1 -1
  21. data/app/models/section.rb +7 -2
  22. data/app/models/user.rb +35 -17
  23. data/app/portlets/forgot_password_portlet.rb +27 -0
  24. data/app/portlets/reset_password_portlet.rb +28 -0
  25. data/app/views/cms/blocks/_toolbar_for_member.html.erb +3 -3
  26. data/app/views/cms/blocks/index.html.erb +11 -6
  27. data/app/views/cms/content/show.html.erb +3 -3
  28. data/app/views/cms/forgot_password_mailer/reset_password.text.html.erb +3 -0
  29. data/app/views/cms/forgot_password_mailer/reset_password.text.plain.erb +3 -0
  30. data/app/views/cms/menus/_menu.html.erb +9 -0
  31. data/app/views/cms/menus/_menu_item.html.erb +11 -0
  32. data/app/views/cms/pages/_edit_connector.html.erb +1 -1
  33. data/app/views/cms/pages/_edit_container.html.erb +1 -1
  34. data/app/views/cms/section_nodes/_node.html.erb +1 -1
  35. data/app/views/cms/sections/_form.html.erb +36 -34
  36. data/app/views/cms/shared/access_denied.html.erb +3 -0
  37. data/app/views/cms/users/change_password.html.erb +8 -6
  38. data/app/views/cms/users/index.html.erb +1 -1
  39. data/app/views/cms/users/show.html.erb +50 -0
  40. data/app/views/layouts/_cms_toolbar.html.erb +1 -1
  41. data/app/views/layouts/_page_toolbar.html.erb +7 -7
  42. data/app/views/layouts/cms/administration.html.erb +24 -7
  43. data/app/views/portlets/forgot_password/_form.html.erb +5 -0
  44. data/app/views/portlets/forgot_password/render.html.erb +14 -0
  45. data/app/views/portlets/reset_password/_form.html.erb +3 -0
  46. data/app/views/portlets/reset_password/render.html.erb +24 -0
  47. data/{we5-browsercms.gemspec → browsercms.gemspec} +72 -54
  48. data/db/migrate/20091109175123_browsercms_3_0_5.rb +9 -0
  49. data/lib/acts_as_list.rb +8 -4
  50. data/lib/cms/acts/content_block.rb +1 -1
  51. data/lib/cms/authentication/controller.rb +26 -7
  52. data/lib/cms/behaviors/attaching.rb +3 -3
  53. data/lib/cms/behaviors/publishing.rb +12 -1
  54. data/lib/cms/behaviors/rendering.rb +17 -4
  55. data/lib/cms/behaviors/versioning.rb +2 -2
  56. data/lib/cms/routes.rb +4 -0
  57. data/lib/tasks/cms.rake +0 -18
  58. data/public/javascripts/cms/content_library.js +36 -0
  59. data/public/javascripts/cms/sitemap.js +21 -9
  60. data/public/stylesheets/cms/form_layout.css +16 -2
  61. data/public/stylesheets/cms/nav.css +4 -3
  62. data/test/functional/cms/content_block_controller_test.rb +120 -0
  63. data/test/functional/cms/content_controller_test.rb +135 -80
  64. data/test/functional/cms/links_controller_test.rb +89 -1
  65. data/test/functional/cms/pages_controller_test.rb +138 -0
  66. data/test/functional/cms/section_nodes_controller_test.rb +45 -5
  67. data/test/functional/cms/sections_controller_test.rb +148 -1
  68. data/test/functional/cms/sessions_controller_test.rb +26 -2
  69. data/test/functional/cms/users_controller_test.rb +49 -2
  70. data/test/integration/cms/password_management_test.rb +57 -0
  71. data/test/test_helper.rb +3 -1
  72. data/test/unit/behaviors/attaching_test.rb +26 -0
  73. data/test/unit/helpers/menu_helper_test.rb +118 -278
  74. data/test/unit/models/group_test.rb +6 -0
  75. data/test/unit/models/user_test.rb +127 -29
  76. metadata +20 -3
data/README.markdown CHANGED
@@ -42,6 +42,7 @@ The user documentation and guides for this version of the application can be fou
42
42
 
43
43
  1. http://browsercms.org/doc/guides/html/index.html - User guides and manuals that cover the features and general functionality of the project. (Found locally at doc/guides/html/index.html)
44
44
  2. http://browsercms.org/doc/app/index.html - The RDoc API documenation (locally at doc/app/index.html)
45
+ 3. http://wiki.github.com/browsermedia/browsercms - The project wiki
45
46
 
46
47
  ## Modifying the source
47
48
  If you want to experiment with the source code, the BrowserCMS project can bootstrap itself as a web application. This allows developers who want to contribute to the project to easily alter and test changes. To run the application itself, do the following:
@@ -33,6 +33,7 @@ class Cms::ContentBlockController < Cms::BaseController
33
33
  after_create_on_failure
34
34
  end
35
35
  rescue Exception => @exception
36
+ raise @exception if @exception.is_a?(Cms::Errors::AccessDenied)
36
37
  after_create_on_error
37
38
  end
38
39
 
@@ -50,6 +51,7 @@ class Cms::ContentBlockController < Cms::BaseController
50
51
  rescue ActiveRecord::StaleObjectError => @exception
51
52
  after_update_on_edit_conflict
52
53
  rescue Exception => @exception
54
+ raise @exception if @exception.is_a?(Cms::Errors::AccessDenied)
53
55
  after_update_on_error
54
56
  end
55
57
 
@@ -127,15 +129,18 @@ class Cms::ContentBlockController < Cms::BaseController
127
129
  options[:order] = params[:order] unless params[:order].blank?
128
130
  scope = model_class.respond_to?(:list) ? model_class.list : model_class
129
131
  @blocks = scope.searchable? ? scope.search(params[:search]).paginate(options) : scope.paginate(options)
132
+ check_permissions
130
133
  end
131
134
 
132
135
  def load_block
133
136
  @block = model_class.find(params[:id])
137
+ check_permissions
134
138
  end
135
139
 
136
140
  def load_block_draft
137
- load_block
141
+ @block = model_class.find(params[:id])
138
142
  @block = @block.as_of_draft_version if model_class.versioned?
143
+ check_permissions
139
144
  end
140
145
 
141
146
  # path related methods - available in the view as helpers
@@ -162,6 +167,7 @@ class Cms::ContentBlockController < Cms::BaseController
162
167
 
163
168
  def build_block
164
169
  @block = model_class.new(params[model_name])
170
+ check_permissions
165
171
  end
166
172
 
167
173
  def set_default_category
@@ -244,6 +250,23 @@ class Cms::ContentBlockController < Cms::BaseController
244
250
  false
245
251
  end
246
252
  end
253
+
254
+ # Use a "whitelist" approach to access to avoid mistakes
255
+ # By default everyone can create new block and view them and their properties,
256
+ # but blocks can only be modified based on the permissions of the pages they
257
+ # are connected to.
258
+ def check_permissions
259
+ case action_name
260
+ when "index", "show", "new", "create", "version", "versions", "usages"
261
+ # Allow
262
+ when "edit", "update"
263
+ raise Cms::Errors::AccessDenied unless current_user.able_to_edit?(@block)
264
+ when "destroy", "publish", "revert_to"
265
+ raise Cms::Errors::AccessDenied unless current_user.able_to_publish?(@block)
266
+ else
267
+ raise Cms::Errors::AccessDenied
268
+ end
269
+ end
247
270
 
248
271
  # methods to setup the view
249
272
 
@@ -259,4 +282,4 @@ class Cms::ContentBlockController < Cms::BaseController
259
282
  "cms/blocks"
260
283
  end
261
284
 
262
- end
285
+ end
@@ -58,6 +58,7 @@ class Cms::ContentController < Cms::ApplicationController
58
58
  # if caching is not enabled
59
59
  def render_page
60
60
  @_page_route.execute(self) if @_page_route
61
+ prepare_connectables_for_render
61
62
  render :layout => @page.layout, :action => 'show'
62
63
  end
63
64
 
@@ -94,11 +95,39 @@ class Cms::ContentController < Cms::ApplicationController
94
95
  @template.instance_variable_set("#{v}", nil)
95
96
  end
96
97
 
98
+ prepare_connectables_for_render
97
99
  render :layout => @page.layout, :template => 'cms/content/show', :status => status
98
100
  else
99
101
  handle_server_error(exception)
100
102
  end
101
- end
103
+ end
104
+
105
+ # If any of the page's connectables (portlets, etc) are renderable, they may have a render method
106
+ # which does "controller" stuff, so we need to get that run before rendering the page.
107
+ def prepare_connectables_for_render
108
+
109
+ @_connectors = @page.connectors.for_page_version(@page.version)
110
+ @_connectables = @_connectors.map(&:connectable_with_deleted)
111
+ unless (logged_in? && current_user.able_to?(:administrate, :edit_content, :publish_content))
112
+ worst_exception = nil
113
+ @_connectables.each do |c|
114
+ begin
115
+ c.prepare_to_render(self)
116
+ rescue
117
+ logger.debug "THROWN EXCEPTION by connectable #{c}: #{$!}"
118
+ case $!
119
+ when ActiveRecord::RecordNotFound
120
+ raise
121
+ when Cms::Errors::AccessDenied
122
+ worst_exception = $!
123
+ else
124
+ c.render_exception = $!
125
+ end
126
+ end
127
+ end
128
+ raise worst_exception if worst_exception
129
+ end
130
+ end
102
131
 
103
132
  # ----- Before Filters -------------------------------------------------------
104
133
  def construct_path
@@ -224,4 +253,4 @@ class Cms::ContentController < Cms::ApplicationController
224
253
 
225
254
 
226
255
 
227
- end
256
+ end
@@ -2,8 +2,9 @@ class Cms::DashboardController < Cms::BaseController
2
2
 
3
3
  def index
4
4
  @unpublished_pages = Page.unpublished.all(:order => "updated_at desc")
5
+ @unpublished_pages = @unpublished_pages.select { |page| current_user.able_to_publish?(page) }
5
6
  @incomplete_tasks = current_user.tasks.incomplete.all(
6
7
  :include => :page,
7
8
  :order => "tasks.due_date desc, pages.name")
8
9
  end
9
- end
10
+ end
@@ -2,7 +2,8 @@ module Cms
2
2
  module ErrorHandling
3
3
  def self.included(controller)
4
4
  controller.class_eval do
5
- rescue_from Exception, :with => :handle_server_error
5
+ rescue_from Exception, :with => :handle_server_error unless RAILS_ENV == "test"
6
+ rescue_from Cms::Errors::AccessDenied, :with => :handle_access_denied
6
7
  end
7
8
  end
8
9
 
@@ -13,6 +14,12 @@ module Cms
13
14
  :status => :internal_server_error,
14
15
  :locals => {:exception => exception}
15
16
  end
17
+
18
+ def handle_access_denied(exception)
19
+ render :layout => 'cms/application',
20
+ :template => 'cms/shared/access_denied',
21
+ :status => 403
22
+ end
16
23
 
17
24
  end
18
- end
25
+ end
@@ -47,10 +47,12 @@ class Cms::LinksController < Cms::BaseController
47
47
 
48
48
  def load_section
49
49
  @section = Section.find(params[:section_id])
50
+ raise Cms::Errors::AccessDenied unless current_user.able_to_edit?(@section)
50
51
  end
51
52
 
52
53
  def load_link
53
54
  @link = Link.find(params[:id])
55
+ raise Cms::Errors::AccessDenied unless current_user.able_to_edit?(@link)
54
56
  end
55
57
 
56
58
  def load_draft_link
@@ -1,5 +1,5 @@
1
1
  class Cms::PagesController < Cms::BaseController
2
-
2
+
3
3
  before_filter :set_toolbar_tab
4
4
  before_filter :load_section, :only => [:new, :create]
5
5
  before_filter :load_page, :only => [:versions, :version, :revert_to, :destroy]
@@ -18,7 +18,7 @@ class Cms::PagesController < Cms::BaseController
18
18
  def show
19
19
  redirect_to Page.find(params[:id]).path
20
20
  end
21
-
21
+
22
22
  def create
23
23
  @page = Page.new(params[:page])
24
24
  @page.section = @section
@@ -38,7 +38,7 @@ class Cms::PagesController < Cms::BaseController
38
38
  render :action => "edit"
39
39
  end
40
40
  rescue ActiveRecord::StaleObjectError => e
41
- @other_version = @page.class.find(@page.id)
41
+ @other_version = @page.class.find(@page.id)
42
42
  render :action => "edit"
43
43
  end
44
44
 
@@ -55,14 +55,14 @@ class Cms::PagesController < Cms::BaseController
55
55
  end
56
56
  end
57
57
  end
58
-
58
+
59
59
  #status actions
60
60
  {:publish => "published", :hide => "hidden", :archive => "archived"}.each do |status, verb|
61
61
  define_method status do
62
62
  if params[:page_ids]
63
- params[:page_ids].each do |id|
64
- Page.find(id).send(status)
65
- end
63
+ @pages = params[:page_ids].map { |id| Page.find(id) }
64
+ raise Cms::Errors::AccessDenied unless @pages.all? { |page| current_user.able_to_edit?(page) }
65
+ @pages.each { |page| page.send(status) }
66
66
  flash[:notice] = "#{params[:page_ids].size} pages #{verb}"
67
67
  redirect_to cms_dashboard_url
68
68
  else
@@ -74,25 +74,27 @@ class Cms::PagesController < Cms::BaseController
74
74
  end
75
75
  end
76
76
  end
77
-
77
+
78
78
  def version
79
79
  @page = @page.as_of_version(params[:version])
80
80
  @show_toolbar = true
81
81
  @show_page_toolbar = true
82
+ @_connectors = @page.connectors.for_page_version(@page.version)
83
+ @_connectables = @_connectors.map(&:connectable_with_deleted)
82
84
  render :layout => @page.layout, :template => 'cms/content/show'
83
- end
84
-
85
+ end
86
+
85
87
  def revert_to
86
88
  if @page.revert_to(params[:version])
87
89
  flash[:notice] = "Page '#{@page.name}' was reverted to version #{params[:version]}"
88
90
  end
89
-
91
+
90
92
  respond_to do |format|
91
93
  format.html { redirect_to @page.path }
92
94
  format.js { render :template => 'cms/shared/show_notice' }
93
- end
95
+ end
94
96
  end
95
-
97
+
96
98
  private
97
99
  def strip_publish_params
98
100
  unless current_user.able_to?(:publish_content)
@@ -103,17 +105,19 @@ class Cms::PagesController < Cms::BaseController
103
105
 
104
106
  def load_page
105
107
  @page = Page.find(params[:id])
108
+ raise Cms::Errors::AccessDenied unless current_user.able_to_edit?(@page)
106
109
  end
107
-
110
+
108
111
  def load_draft_page
109
112
  load_page
110
113
  @page = @page.as_of_draft_version
111
114
  end
112
-
115
+
113
116
  def load_section
114
117
  @section = Section.find(params[:section_id])
118
+ raise Cms::Errors::AccessDenied unless current_user.able_to_edit?(@section)
115
119
  end
116
-
120
+
117
121
  def hide_toolbar
118
122
  @hide_page_toolbar = true
119
123
  end
@@ -121,9 +125,9 @@ class Cms::PagesController < Cms::BaseController
121
125
  def set_toolbar_tab
122
126
  @toolbar_tab = :sitemap
123
127
  end
124
-
128
+
125
129
  def load_templates
126
130
  @templates = PageTemplate.options
127
131
  end
128
-
132
+
129
133
  end
@@ -1,5 +1,5 @@
1
1
  class Cms::SectionNodesController < Cms::BaseController
2
- check_permissions :publish_content, :only => [:move_before, :move_after]
2
+ check_permissions :publish_content, :except => [:index]
3
3
 
4
4
  def index
5
5
  @toolbar_tab = :sitemap
@@ -1,6 +1,7 @@
1
1
  class Cms::SectionsController < Cms::BaseController
2
2
 
3
3
  before_filter :load_parent, :only => [:new, :create]
4
+ before_filter :load_section, :only => [:edit, :update, :destroy, :move]
4
5
  before_filter :set_toolbar_tab
5
6
 
6
7
  helper_method :public_groups
@@ -16,12 +17,13 @@ class Cms::SectionsController < Cms::BaseController
16
17
 
17
18
  def new
18
19
  @section = @parent.sections.build
19
- @section.groups = public_groups + cms_groups
20
+ @section.groups = @parent.groups
20
21
  end
21
22
 
22
23
  def create
23
24
  @section = Section.new(params[:section])
24
25
  @section.parent = @parent
26
+ @section.groups = @section.parent.groups unless current_user.able_to?(:administrate)
25
27
  if @section.save
26
28
  flash[:notice] = "Section '#{@section.name}' was created"
27
29
  redirect_to [:cms, @section]
@@ -31,13 +33,12 @@ class Cms::SectionsController < Cms::BaseController
31
33
  end
32
34
 
33
35
  def edit
34
- @section = Section.find(params[:id])
35
- raise Cms::Errors::AccessDenied unless current_user.able_to_edit?(@section)
36
36
  end
37
37
 
38
38
  def update
39
- @section = Section.find(params[:id])
40
- if @section.update_attributes(params[:section])
39
+ params[:section].delete('group_ids') if params[:section] && !current_user.able_to?(:administrate)
40
+ @section.attributes = params[:section]
41
+ if @section.save
41
42
  flash[:notice] = "Section '#{@section.name}' was updated"
42
43
  redirect_to [:cms, @section]
43
44
  else
@@ -46,7 +47,6 @@ class Cms::SectionsController < Cms::BaseController
46
47
  end
47
48
 
48
49
  def destroy
49
- @section = Section.find(params[:id])
50
50
  respond_to do |format|
51
51
  if @section.deletable? && @section.destroy
52
52
  message = "Section '#{@section.name}' was deleted."
@@ -61,7 +61,6 @@ class Cms::SectionsController < Cms::BaseController
61
61
  end
62
62
 
63
63
  def move
64
- @section = Section.find(params[:id])
65
64
  if params[:section_id]
66
65
  @move_to = Section.find(params[:section_id])
67
66
  else
@@ -81,6 +80,12 @@ class Cms::SectionsController < Cms::BaseController
81
80
  protected
82
81
  def load_parent
83
82
  @parent = Section.find(params[:section_id])
83
+ raise Cms::Errors::AccessDenied unless current_user.able_to_edit?(@parent)
84
+ end
85
+
86
+ def load_section
87
+ @section = Section.find(params[:id])
88
+ raise Cms::Errors::AccessDenied unless current_user.able_to_edit?(@section)
84
89
  end
85
90
 
86
91
  def handle_file_browser_upload
@@ -3,11 +3,11 @@ class Cms::SessionsController < Cms::ApplicationController
3
3
 
4
4
  before_filter :redirect_to_cms_site, :only => [:new]
5
5
  layout "cms/login"
6
-
6
+
7
7
  def new
8
-
8
+
9
9
  end
10
-
10
+
11
11
  def create
12
12
  logout_keeping_session!
13
13
  user = User.authenticate(params[:login], params[:password])
@@ -21,7 +21,7 @@ class Cms::SessionsController < Cms::ApplicationController
21
21
  handle_remember_cookie! new_cookie_flag
22
22
  flash[:notice] = "Logged in successfully"
23
23
  if params[:success_url] # Coming from login portlet
24
- redirect_to(session[:return_to] || params[:success_url] || "/")
24
+ redirect_to((!params[:success_url].blank? && params[:success_url]) || session[:return_to] || "/")
25
25
  session[:return_to] = nil
26
26
  else
27
27
  redirect_back_or_default(cms_home_url)
@@ -30,7 +30,7 @@ class Cms::SessionsController < Cms::ApplicationController
30
30
  note_failed_signin
31
31
  @login = params[:login]
32
32
  @remember_me = params[:remember_me]
33
- flash[:login_error] = "Log in failed"
33
+ flash[:login_error] = "Log in failed"
34
34
  if params[:success_url] # Coming from login portlet
35
35
  if params[:success_url].blank?
36
36
  success_url = session[:return_to] || "/"
@@ -42,23 +42,30 @@ class Cms::SessionsController < Cms::ApplicationController
42
42
  flash[:success_url] = success_url
43
43
  redirect_to request.referrer
44
44
  else
45
- render :action => "new"
46
- end
45
+ render :action => "new"
46
+ end
47
47
  end
48
48
  end
49
49
 
50
50
  def destroy
51
+ logout_user
52
+ redirect_back_or_default("/")
53
+ end
54
+
55
+ protected
56
+
57
+ # Pulled this out as a separate method so that modules (like bcms_cas) can override/alias destroy and
58
+ # not have a redirect happen as a side effect.
59
+ def logout_user
51
60
  logout_killing_session!
52
61
  cookies.delete :openSectionNodes
53
62
  flash[:notice] = "You have been logged out."
54
- redirect_back_or_default("/")
55
63
  end
56
64
 
57
- protected
58
65
  # Track failed login attempts
59
66
  def note_failed_signin
60
67
  flash[:error] = "Couldn't log you in as '#{params[:login]}'"
61
68
  logger.warn "Failed login for '#{params[:login]}' from #{request.remote_ip} at #{Time.now.utc}"
62
69
  end
63
-
70
+
64
71
  end
@@ -1,7 +1,9 @@
1
1
  class Cms::UsersController < Cms::ResourceController
2
2
  layout 'cms/administration'
3
3
 
4
- check_permissions :administrate
4
+ check_permissions :administrate, :except => [:show, :change_password, :update_password]
5
+ before_filter :only_self_or_administrator, :only => [:show, :change_password, :update_password]
6
+
5
7
  before_filter :set_menu_section
6
8
  after_filter :update_group_membership, :only => [:update, :create]
7
9
  after_filter :update_flash, :only => [ :update, :create ]
@@ -33,10 +35,6 @@ class Cms::UsersController < Cms::ResourceController
33
35
  @users = User.paginate(:page => params[:page], :per_page => per_page, :include => :user_group_memberships, :conditions => conditions, :order => "first_name, last_name, email")
34
36
  end
35
37
 
36
- def show
37
- redirect_to [:edit, :cms, user]
38
- end
39
-
40
38
  def change_password
41
39
  user
42
40
  end
@@ -44,7 +42,7 @@ class Cms::UsersController < Cms::ResourceController
44
42
  def update_password
45
43
  if user.update_attributes(params[:user])
46
44
  flash[:notice] = "Password for '#{user.login}' was changed"
47
- redirect_to cms_users_path
45
+ redirect_to(current_user.able_to?(:administrate) ? cms_users_path : cms_user_path(user))
48
46
  else
49
47
  render :action => 'change_password'
50
48
  end
@@ -95,4 +93,8 @@ class Cms::UsersController < Cms::ResourceController
95
93
  def set_menu_section
96
94
  @menu_section = 'users'
97
95
  end
96
+
97
+ def only_self_or_administrator
98
+ raise Cms::Errors::AccessDenied if !current_user.able_to?(:administrate) && params[:id].to_i != current_user.id
99
+ end
98
100
  end