we5-browsercms 3.0.2 → 3.0.5

Sign up to get free protection for your applications and to get access to all the features.
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