From 80bec6efc9cbed8a9f901d1e9bc46fb0a0922144 Mon Sep 17 00:00:00 2001
From: Ismael Abreu <ismaelga@gmail.com>
Date: Tue, 12 Feb 2013 22:58:08 +0000
Subject: [PATCH] Adds grant and revoke moderation buttons so admins can make
 users moderators

---
 .../admin/models/admin_user.js.coffee         | 13 ++++++
 .../admin/templates/user.js.handlebars        | 15 +++++++
 app/controllers/admin/users_controller.rb     | 16 +++++++
 app/models/user.rb                            |  5 +++
 .../admin_detailed_user_serializer.rb         | 12 ++++-
 config/locales/en.yml                         |  2 +
 config/routes.rb                              |  2 +
 lib/guardian.rb                               | 17 +++++++
 spec/components/guardian_spec.rb              | 40 +++++++++++++++++
 .../admin/users_controller_spec.rb            | 45 +++++++++++++++++--
 10 files changed, 163 insertions(+), 4 deletions(-)

diff --git a/app/assets/javascripts/admin/models/admin_user.js.coffee b/app/assets/javascripts/admin/models/admin_user.js.coffee
index 950fb624ac7..42d97bf2b08 100644
--- a/app/assets/javascripts/admin/models/admin_user.js.coffee
+++ b/app/assets/javascripts/admin/models/admin_user.js.coffee
@@ -17,6 +17,19 @@ window.Discourse.AdminUser = Discourse.Model.extend
     @set('can_revoke_admin',true)
     $.ajax "/admin/users/#{@get('id')}/grant_admin", type: 'PUT'
 
+  # Revoke the user's moderation access
+  revokeModeration: ->
+    @set('moderator',false)
+    @set('can_grant_moderation',true)
+    @set('can_revoke_moderation',false)
+    $.ajax "/admin/users/#{@get('id')}/revoke_moderation", type: 'PUT'
+
+  grantModeration: ->
+    @set('moderator',true)
+    @set('can_grant_moderation',false)
+    @set('can_revoke_moderation',true)
+    $.ajax "/admin/users/#{@get('id')}/grant_moderation", type: 'PUT'
+
   refreshBrowsers: ->
     $.ajax "/admin/users/#{@get('id')}/refresh_browsers",
       type: 'POST'
diff --git a/app/assets/javascripts/admin/templates/user.js.handlebars b/app/assets/javascripts/admin/templates/user.js.handlebars
index 1d29846be58..ddfbfec07ef 100644
--- a/app/assets/javascripts/admin/templates/user.js.handlebars
+++ b/app/assets/javascripts/admin/templates/user.js.handlebars
@@ -84,6 +84,21 @@
   <div class='display-row'>
     <div class='field'>{{i18n admin.user.moderator}}</div>
     <div class='value'>{{content.moderator}}</div>
+    <div class='controls'>
+      {{#if content.can_revoke_moderation}}        
+        <button class='btn' {{action revokeModeration target="content"}}>
+          <i class='icon icon-eye-close'></i>
+          {{i18n admin.user.revoke_moderation}}
+        </button>
+      {{/if}}
+      {{#if content.can_grant_moderation}}
+        <button class='btn' {{action grantModeration target="content"}}>
+          <i class='icon icon-eye-open'></i>
+          {{i18n admin.user.grant_moderation}}
+        </button>
+      {{/if}}      
+    </div>
+    
   </div>
   <div class='display-row'>
     <div class='field'>{{i18n trust_level}}</div>
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index a378f907997..ada4943a005 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -63,6 +63,22 @@ class Admin::UsersController < Admin::AdminController
     render_serialized(@user, AdminUserSerializer)
   end
 
+  def revoke_moderation
+    @moderator = User.where(id: params[:user_id]).first
+    guardian.ensure_can_revoke_moderation!(@moderator)
+    @moderator.change_trust_level(:advanced)
+    @moderator.save
+    render nothing: true
+  end
+
+  def grant_moderation
+    @user = User.where(id: params[:user_id]).first
+    guardian.ensure_can_grant_moderation!(@user)
+    @user.change_trust_level(:moderator)
+    @user.save
+    render_serialized(@user, AdminUserSerializer)
+  end
+
   def approve
     @user = User.where(id: params[:user_id]).first
     guardian.ensure_can_approve!(@user)
diff --git a/app/models/user.rb b/app/models/user.rb
index 56cdff20ade..5d484acff3d 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -401,6 +401,11 @@ class User < ActiveRecord::Base
     (self.trust_level || TrustLevel.Levels[:new]) >= TrustLevel.Levels[level]
   end
 
+  def change_trust_level(level)
+    raise "Invalid trust level #{level}" unless TrustLevel.Levels.has_key?(level)
+    self.trust_level = TrustLevel.Levels[level]
+  end
+
   def guardian
     Guardian.new(self)
   end
diff --git a/app/serializers/admin_detailed_user_serializer.rb b/app/serializers/admin_detailed_user_serializer.rb
index 24694d11b50..ab458b4301c 100644
--- a/app/serializers/admin_detailed_user_serializer.rb
+++ b/app/serializers/admin_detailed_user_serializer.rb
@@ -2,8 +2,10 @@ class AdminDetailedUserSerializer < AdminUserSerializer
 
   attributes :moderator,
              :can_grant_admin,
-             :can_impersonate,
              :can_revoke_admin,
+             :can_grant_moderation,
+             :can_revoke_moderation,
+             :can_impersonate,
              :like_count,
              :post_count,
              :flags_given_count,
@@ -21,6 +23,14 @@ class AdminDetailedUserSerializer < AdminUserSerializer
     scope.can_grant_admin?(object)
   end
 
+  def can_revoke_moderation
+    scope.can_revoke_moderation?(object)
+  end
+
+  def can_grant_moderation
+    scope.can_grant_moderation?(object)
+  end
+
   def can_delete_all_posts
     scope.can_delete_all_posts?(object)
   end
diff --git a/config/locales/en.yml b/config/locales/en.yml
index a1bd47e8b3a..920e5f16347 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -390,6 +390,8 @@ en:
         impersonate: 'Impersonate'
         revoke_admin: 'Revoke Admin'
         grant_admin: 'Grant Admin'
+        revoke_moderation: 'Revoke Moderation'
+        grant_moderation: 'Grant Moderation'
         basics: Basics
         reputation: Reputation
         permissions: Permissions
diff --git a/config/routes.rb b/config/routes.rb
index a5980b3e695..855654b7279 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -34,6 +34,8 @@ Discourse::Application.routes.draw do
       put 'unban' => 'users#unban'
       put 'revoke_admin' => 'users#revoke_admin'
       put 'grant_admin' => 'users#grant_admin'
+      put 'revoke_moderation' => 'users#revoke_moderation'
+      put 'grant_moderation' => 'users#grant_moderation'
       put 'approve' => 'users#approve'
       post 'refresh_browsers' => 'users#refresh_browsers'
     end
diff --git a/lib/guardian.rb b/lib/guardian.rb
index d8df070ffac..7577afaf2fe 100644
--- a/lib/guardian.rb
+++ b/lib/guardian.rb
@@ -131,6 +131,23 @@ class Guardian
     true
   end
 
+  def can_revoke_moderation?(moderator)
+    return false unless @user.try(:admin?)
+    return false if moderator.blank?
+    return false if @user.id == moderator.id
+    return false unless moderator.trust_level == TrustLevel.Levels[:moderator]
+    true
+  end
+
+  def can_grant_moderation?(user)
+    return false unless @user.try(:admin?)
+    return false if user.blank?
+    return false if @user.id == user.id
+    return false if user.admin?
+    return false if user.has_trust_level?(:moderator)
+    true
+  end
+
   # Can we see who acted on a post in a particular way?
   def can_see_post_actors?(topic, post_action_type_id)
     return false unless topic.present?
diff --git a/spec/components/guardian_spec.rb b/spec/components/guardian_spec.rb
index 4c12e0e8de8..a63005a034b 100644
--- a/spec/components/guardian_spec.rb
+++ b/spec/components/guardian_spec.rb
@@ -761,6 +761,46 @@ describe Guardian do
     end
   end
 
+  context 'can_grant_moderation?' do
+    it "wont allow a non logged in user to grant an moderator's access" do
+      Guardian.new.can_grant_moderation?(user).should be_false
+    end
+
+    it "wont allow a regular user to revoke an modearator's access" do
+      Guardian.new(user).can_grant_moderation?(moderator).should be_false
+    end
+
+    it 'wont allow an admin to grant their own access' do
+      Guardian.new(admin).can_grant_moderation?(admin).should be_false     
+    end
+
+    it 'wont allow an admin to grant it to an already moderator' do
+      Guardian.new(admin).can_grant_moderation?(moderator).should be_false     
+    end
+
+    it "allows an admin to grant a regular user access" do
+      Guardian.new(admin).can_grant_moderation?(user).should be_true     
+    end
+  end
+
+  context 'can_revoke_moderation?' do
+    it "wont allow a non logged in user to revoke an moderator's access" do
+      Guardian.new.can_revoke_moderation?(moderator).should be_false
+    end
+
+    it "wont allow a regular user to revoke an moderator's access" do
+      Guardian.new(user).can_revoke_moderation?(moderator).should be_false
+    end
+
+    it 'wont allow an moderator to revoke their own moderator' do
+      Guardian.new(moderator).can_revoke_moderation?(moderator).should be_false     
+    end
+
+    it "allows an admin to revoke a moderator's access" do
+      Guardian.new(admin).can_revoke_moderation?(moderator).should be_true     
+    end
+  end
+
   context "can_see_pending_invites_from?" do
 
     it 'is false without a logged in user' do
diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb
index 1268fddba62..e882b7ab86f 100644
--- a/spec/controllers/admin/users_controller_spec.rb
+++ b/spec/controllers/admin/users_controller_spec.rb
@@ -111,8 +111,47 @@ describe Admin::UsersController do
       end
     end
 
+    describe '.revoke_moderation' do
+      before do
+        @moderator = Fabricate(:moderator)
+      end
+
+      it 'raises an error unless the user can revoke access' do
+        Guardian.any_instance.expects(:can_revoke_moderation?).with(@moderator).returns(false)
+        xhr :put, :revoke_moderation, user_id: @moderator.id
+        response.should be_forbidden
+      end
+
+      it 'updates the moderator flag' do        
+        xhr :put, :revoke_moderation, user_id: @moderator.id
+        @moderator.reload
+        @moderator.has_trust_level?(:moderator).should_not be_true
+      end
+    end
+
+    context '.grant_moderation' do
+      before do
+        @another_user = Fabricate(:coding_horror)
+      end
+
+      it "raises an error when the user doesn't have permission" do
+        Guardian.any_instance.expects(:can_grant_moderation?).with(@another_user).returns(false)
+        xhr :put, :grant_moderation, user_id: @another_user.id
+        response.should be_forbidden
+      end
+
+      it "returns a 404 if the username doesn't exist" do        
+        xhr :put, :grant_moderation, user_id: 123123
+        response.should be_forbidden
+      end
+
+      it 'updates the moderator flag' do        
+        xhr :put, :grant_moderation, user_id: @another_user.id
+        @another_user.reload
+        @another_user.has_trust_level?(:moderator).should be_true
+      end
+    end
+
   end
 
-
-
-end
\ No newline at end of file
+end