Crabgrass modifications to add anonymous delegation

Branch: github.com/riseuplabs/Crabgrass-Core/tr...

In this case anonymous delegation means that a user can post a comment as anonymous.

Only group and network context anonymous posts are currently supported, what means that pages must be owned by a group/network and the user must be in the group.

The main method implemented is “user.may_act_as?”

When the user choose to post as anonymous, “user_id = 0” and the user id is not stored in the post. However, the columns last_seen_at and last_updated in page register the user id.

With this feature it’s not possible to create pages (wiki, pads, galleries) as anonymous user

files added/modified

 app/helpers/common/ui/entity_display_helper.rb
 app/helpers/groups/permissions_helper.rb
 app/models/discussion/post.rb
 app/models/user.rb
 app/views/common/posts/table/_reply_row.html.haml
 app/views/groups/permissions/index.html.haml
 config/permissions.rb

 app/views/common/posts/table/_reply.html.haml

# why removing group_setting? (is still in master)
 app/models/group.rb 
 app/models/group_setting.rb
 db/schema.rb
 test/fixtures/group_settings.yml

modifications

app/helpers/common/ui/entity_display_helper.rb

     display, title = if entity.nil?
-      [:unknown.t, nil]
+      [:anonymous.t, nil]


+  def author_selection_tag(element_id, context)
+    if current_user.may_act_as?(:user => 0, :context => context)
+      options = options_for_select([[current_user.name, current_user.id], [:anonymous.t,0]])
+      select_tag('author_selector', options, :onchange => "$('#{element_id}').value=this.value;")
+    else
+      author_style = "background-image: url(#{avatar_url_for(current_user,'small')})"
+      content_tag(:div, :class => 'author', :style => author_style) do
+        content_tag(:div, :class => 'username') do
+          display_entity(current_user)
+      end
+    end

app/helpers/groups/permissions_helper.rb

+  def allow_anonymous_content_checkbox(form)
+    form.row do |r|
+      r.input castle_gate_tag(@group, :post_anonymously, @holders, :label => @group.gate(:post_anonymou~
+      r.info @group.gate(:post_anonymously).info
+    end
+  end

app/models/discussion/post.rb

-  validates_presence_of :user, :body
+  validates_presence_of :body

-  # user -- the user creating the post (optional)
+  # user -- the user creating the post
+  # group -- the context under which this post is created.
+  # user_id -- set if the user to show on the post is different than the user making the post (optional)
+  # group_id -- set a group to be the author of the post (optional)

   def self.create!(*args, &block)
     user = nil
+    group = nil
     page = nil


+    # parse the args
     args.each do |arg|
       user       = arg if arg.is_a? User
+      group      = arg if arg.is_a? Group


-    end
-    if discussion
+      group ||= page.owner if page.owner_type == 'Group'
+    elsif discussion
       attributes[:discussion] = discussion
+    else
+      raise ArgumentError.new('discussion required')
     end
-    if user
-      attributes[:user] = user
+
+    # ensure that there is a user or a group, and that the current_user has
+    # permission to post as this user or group.
+    if attributes[:user_id].any? || attributes[:group_id].any?
+      if attributes[:user_id].any? && !user.may_act_as?(:user => attributes[:user_id], :context => ~
+        raise PermissionDenied.new('may not post as that user')
+      elsif attributes[:group_id].any? && !user.may_act_as?(:group => attributes[:group_id], :conte~
+        raise PermissionDenied.new('may not post as that group')
+      end
+    else
+      attributes[:user_id] = user.id
+
+    # create the post

app/models/user.rb

+  #
+  # may this user act as another user, or even another group?
+  #
+  # In other words, may this user be 'anonymous' or take on the identity
+  # of someone else when creating posts or edits?
+  #
+  # supported options:
+  #
+  #   :user  -- may self act as this user?
+  #             (if :user => 0, this means anonymous)
+  #
+  #   :group -- may self act as this group?
+  #   :context -- what entity is the context for this request.
+  #               the context determines what is possible.
+  #
+  def may_act_as?(options={})
+    user, group, context = options[:user], options[:group], options[:context]
+    if context.is_a? Group
+      if user.to_s == "0"
+        context.access?(self => :post_anonymously)
+      elsif user && user.id == self.id
+        true
+    elsif context.is_a? Network
+      if self.member_of?(group) && group.member_of?(context)
+        true
+      end
+    else
+      false # only group and network context anonymous posts are currently supported
+    end
+  end

app/views/common/posts/table/_reply.html.haml

-    %td.post_author
-      %div.author{:style => author_style}
-        %div.username= display_entity(current_user)
-    %td{:class => 'post_body'}
-      - if local_assigns[:reply_body]
-        = local_assigns[:reply_body]
-      - else
-        = render :partial => 'common/posts/table/reply_body', :locals => local_assigns
+    = render :partial => 'common/posts/table/reply_row', :locals => local_assigns
+    -# %td.post_author
+    -#   = author_selection
+    -#   -# %div.author{:style => author_style}
+    -#   -#   %div.username= display_entity(current_user)
+    -#   -#   %div.date  
+    -# %td.post_body
+    -#   -# i don't think this is used anywhere... ?
+    -#   -# - if local_assigns[:reply_body]
+    -#   -#   = local_assigns[:reply_body]
+    -#   -# - else
+    -#   = render :partial => 'common/posts/table/reply_body', :locals => local_assigns

app/views/common/posts/table/_reply_row.html.haml

+-# requires:
+-#   create_url -- url to send the action to
+-# options:
+-#   last_id -- set in_reply_to_id, which currently is just used for helping
+-#              to generate the activity feed.
+-#   remote  -- if not nil, generate an ajax form.
+-#
+
+- remote = local_assigns[:remote] || false
+- form = remote ? method(:remote_form_for) : method(:form_for)
+- form_options = {:url => create_url, :loading => show_spinner('post')}
+%td.post_author
+  = author_selection_tag('user_id', @group || @user || @context.try(:entity))
+%td.post_body
+  - form.call(:post, @post, form_options) do |f|
+    = hidden_field_tag('post[user_id]', '', :id => 'user_id')
+    = f.text_area :body, :rows => 8, :class => 'input-xlarge'
+    .buttons
+      .float_left= formatting_reference_link
+      - if remote
+        = spinner('post')
+      = submit_tag :post_message.t, :name => 'post_message', :class => 'btn btn-primary'

app/views/groups/permissions/index.html.haml

+    - f.heading :content.t
+    - allow_anonymous_content_checkbox(f)

config/permissions.rb

+    gate 13, :post_anonymously,
+      :label => 'Anonymous Posts',
+      :info => 'Are members allowed to post anonymously?'
+
     protected

     def create_permissions
       grant_access! self => :all
+      revoke_access! self => :post_anonymously
       if council?

Example traces

Request as user blue the page
localhost:3000/rainbow/scipit-ligula-a-...

view

Processing DiscussionPageController#show (for 78.249.69.26 at 2012-07-08 19:28:03) [GET]
  Parameters: {"id"=>nil, "_context"=>"rainbow", "_page"=>"scipit-ligula-a-nisi-quisque-augu+95", "controller"=>"discussion_page", "action"=>"show", "path"=>[]}
...
  User Update (0.3ms)   UPDATE `users` SET `last_seen_at` = '2012-07-08 10:28:03', `updated_at` = '2012-07-08 10:28:03' WHERE `id` = 4
...
  DiscussionPage Columns (0.9ms)   SHOW FIELDS FROM `pages`
  UserParticipation Columns (0.5ms)   SHOW FIELDS FROM `user_participations` 
  UserParticipation Load (0.2ms)   SELECT * FROM `user_participations` WHERE (`user_participations`.`user_id` = 4) AND (`user_participations`.page_id = 95) LIMIT 1
...
  Post Load (8.4ms)   SELECT * FROM `posts` WHERE (`posts`.`discussion_id` = 24) AND (deleted_at IS NULL) ORDER BY created_at ASC LIMIT 0, 30

post as anonymous

Processing Pages::PostsController#create (for 78.249.69.26 at 2012-07-08 19:21:47) [POST]
  Parameters: {"page_id"=>"95", "authenticity_token"=>"DeXhuCJSMH3mMPelHX5mtvDPOic1rY57un8ay3nX+xo=", "post"=>{"user_id"=>"0", "body"=>"anonymous"}, "controller"=>"pages/posts", "action"=>"create", "post_message"=>"Post Message"}
...
  Post Create (0.3ms)   INSERT INTO `posts` (`created_at`, `updated_at`, `user_id`, `page_terms_id`, `type`, `discussion_id`, `body`, `body_html`, `deleted_at`) VALUES('2012-07-08 10:21:50', '2012-07-08 10:21:50', 0, 95, NULL, 24, 'anonymous', '<p>anonymous</p>', NULL)
...
  DiscussionPage Update (0.2ms)   UPDATE `pages` SET `posts_count` = 7 WHERE `id` = 95
...
  Discussion Update (0.2ms)   UPDATE `discussions` SET `posts_count` = 7, `last_post_id` = 15, `replied_by_id` = 0, `replied_at` = '2012-07-08 10:21:50' WHERE `id` = 24
...
  PageHistory::AddComment Create (0.2ms)   INSERT INTO `page_histories` (`created_at`, `notification_digest_sent_at`, `page_id`, `user_id`, `details`, `notification_sent_at`, `type`, `object_id`, `object_type`) VALUES('2012-07-08 10:21:50', NULL, 95, 4, NULL, NULL, 'AddComment', 15, 'Post')
  Page Update (2.1ms)   UPDATE `pages` SET updated_at = '2012-07-08 10:21:50' WHERE (id = 95) 
...
  UserParticipation Update (0.6ms)   UPDATE `user_participations` SET viewed = 0 WHERE (`user_participations`.page_id = 95)
  SQL (0.1ms)   BEGIN
  UserParticipation Update (0.2ms)   UPDATE `user_participations` SET `changed_at` = '2012-07-08 10:21:50', `viewed_at` = '2012-07-08 10:21:50' WHERE `id` = 315

post as user blue

Processing Pages::PostsController#create (for 78.249.69.26 at 2012-07-08 19:14:36) [POST]
  Parameters: {"page_id"=>"95", "authenticity_token"=>"DeXhuCJSMH3mMPelHX5mtvDPOic1rY57un8ay3nX+xo=", "post"=>{"user_id"=>"", "body"=>"blue"}, "controller"=>"pages/posts", "action"=>"create", "post_message"=>"Post Message"}
...
  Post Create (0.3ms)   INSERT INTO `posts` (`created_at`, `updated_at`, `user_id`, `page_terms_id`, `type`, `discussion_id`, `body`, `body_html`, `deleted_at`) VALUES('2012-07-08 10:14:39', '2012-07-08 10:14:39', 4, 95, NULL, 24, 'blue', '<p>blue</p>', NULL)
...
  DiscussionPage Update (0.3ms)   UPDATE `pages` SET `posts_count` = 6 WHERE `id` = 95
...
  Discussion Update (0.4ms)   UPDATE `discussions` SET `posts_count` = 6, `last_post_id` = 14, `replied_by_id` = 4, `replied_at` = '2012-07-08 10:14:39' WHERE `id` = 24
...
  PageHistory::AddComment Create (0.3ms)   INSERT INTO `page_histories` (`created_at`, `notification_digest_sent_at`, `page_id`, `user_id`, `details`, `notification_sent_at`, `type`, `object_id`, `object_type`) VALUES('2012-07-08 10:14:39', NULL, 95, 4, NULL, NULL, 'AddComment', 14, 'Post')
  Page Update (0.2ms)   UPDATE `pages` SET updated_at = '2012-07-08 10:14:39' WHERE (id = 95)
...
  UserParticipation Update (0.6ms)   UPDATE `user_participations` SET viewed = 0 WHERE (`user_participations`.page_id = 95) 
  SQL (0.1ms)   BEGIN
  UserParticipation Update (0.2ms)   UPDATE `user_participations` SET `changed_at` = '2012-07-08 10:14:39', `viewed_at` = '2012-07-08 10:14:39' WHERE `id` = 315

debugging in console

/home/jula/crabgrass-core-delegation/app/controllers/pages/posts_controller.rb:22
current_user.updated(@page)
(rdb:1) p @post
#<Post id: 12, user_id: 0, discussion_id: 24, body: "more anonymous", body_html: "<p>more anonymous</p>", created_at: "2012-07-08 08:32:51", updated_at: "2012-07-08 08:32:51", deleted_at: nil, type: nil, page_terms_id: 95>


#<DiscussionPage id: 95, title: "scipit ligula a nisi. Quisque augu", created_at: "2012-07-01 15:51:38", updated_at: "2012-07-08 08:33:49", resolved: false, public: true, created_by_id: nil, updated_by_id: 4, summary: "Sed blandit elit a libero. Suspendisse potenti. Don...", type: "DiscussionPage", message_count: 0, data_id: nil, data_type: nil, contributors_count: 0, posts_count: 4, name: nil, updated_by_login: "blue", created_by_login: nil, flow: nil, stars_count: 1, views_count: 61, owner_id: 3, owner_type: "Group", owner_name: "rainbow", is_image: nil, is_audio: nil, is_video: nil, is_document: nil, site_id: nil, happens_at: nil, cover_id: nil>

Status of the db after the anonymous/blue postings

mysql> select user_id from posts where discussion_id=24;
+---------+
| user_id |
+---------+
|       4 |
|       0 |
|       4 |
|       0 |
|       0 |
+---------+
mysql> select * from users where id=0;
Empty set (0.00 sec)

to fix

  • some discussion pages appear as owned by anonymous when it’s not possibe a page owner is anonymous (only the post)
  • after posting is needed to refresh the page to see the dropbox where u can choose to publish as anonymous
  • No defined method group_settings when trying to create a page as a group. Instead it’s possible to create it as a user and :
http://localhost:3000/pages/new/rainbow

NoMethodError in Pages/create#new

Showing app/views/pages/create/_choose_page_class.html.haml where line #10 raised:

undefined method `group_setting' for #<Group:0xb47369dc>

Extracted source (around line #10):

7:     = I18n.t(:page_added_to_group, :group_type => @group.group_type.downcase, :group_name => content_tag(:b, @group.display_name)) 
8: 
9: = column_layout 2, page_creation_links, :class => 'layout'

Trace of template inclusion: app/views/pages/create/new.html.haml

RAILS_ROOT: /home/jula/crabgrass-core-delegation
Application Trace | Framework Trace | Full Trace

/home/jula/.rbenv/versions/1.8.7-p358/lib/ruby/gems/1.8/gems/activerecord-2.3.14/lib/active_record/attribute_methods.rb:260:in `method_missing'
/home/jula/crabgrass-core-delegation/app/models/site.rb:164:in `tools_for'
/home/jula/crabgrass-core-delegation/app/helpers/common/page/form_helper.rb:23:in `tree_of_page_types'
/home/jula/crabgrass-core-delegation/app/helpers/pages/creation_helper.rb:21:in `page_creation_links'
/home/jula/crabgrass-core-delegation/app/views/pages/create/_choose_page_class.html.haml:10:in `_run_haml_app47views47pages47create47_choose_page_class46html46haml_locals_choose_page_class_object'
/home/jula/crabgrass-core-delegation/app/views/pages/create/new.html.haml:4:in `_run_haml_app47views47pages47create47new46html46haml'
/home/jula/crabgrass-core-delegation/app/controllers/pages/create_controller.rb:99:in `render_new_template'
/home/jula/crabgrass-core-delegation/app/controllers/pages/create_controller.rb:34:in `new'

Request

Parameters:

{"owner"=>"rainbow"}