diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | app/controllers/admin/users_controller.rb | 8 | ||||
-rw-r--r-- | config/locales/en.rb | 5 | ||||
-rw-r--r-- | lib/gitorious/user_administration.rb | 114 | ||||
-rwxr-xr-x | script/create_user | 21 | ||||
-rwxr-xr-x | script/suspend_user | 40 | ||||
-rw-r--r-- | test/unit/lib/gitorious/user_administration_test.rb | 110 |
7 files changed, 297 insertions, 2 deletions
@@ -27,3 +27,4 @@ public/stylesheets/sites/* public/javascripts/sites/* public/system/avatars public/system/group_avatars +/.keeptesting diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index c49a665..725a6cb 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -19,6 +19,8 @@ #++ class Admin::UsersController < AdminController + include Gitorious::UserAdministration + def index @users = paginate(:action => "index") do User.paginate(:all, :order => 'suspended_at, login', :page => params[:page]) @@ -58,12 +60,14 @@ class Admin::UsersController < AdminController def suspend @user = User.find_by_login!(params[:id]) - @user.suspend + suspend_summary = suspend_user(@user) + if @user.save - flash[:notice] = I18n.t("admin.users_controller.suspend_notice", :user_name => @user.login) + flash[:notice] = suspend_summary else flash[:error] = I18n.t("admin.users_controller.suspend_error", :user_name => @user.login) end + redirect_to admin_users_url end diff --git a/config/locales/en.rb b/config/locales/en.rb index 5f1d155..c8628d3 100644 --- a/config/locales/en.rb +++ b/config/locales/en.rb @@ -14,6 +14,11 @@ :unsuspend_notice => "User {{user_name}} was successfully unsuspended.", :unsuspend_error => "Unable to unsuspend user {{user_name}}." }, + :user_suspend => { + :suspended_cannot_log_back_in_or_run_git_ops => "Suspended Gitorious account. User will not be able to log back in. Ssh keys are revoked. User can no longer push code to the site.", + :removed_user_from_teams => "Removed user from team(s): {{group_names}}", + :removed_user_committerships_from_repos => "Removed user committerships from repo(s): {{repo_names}}" + }, :check_admin => "For Administrators Only" }, :mailer => { diff --git a/lib/gitorious/user_administration.rb b/lib/gitorious/user_administration.rb new file mode 100644 index 0000000..2e1d430 --- /dev/null +++ b/lib/gitorious/user_administration.rb @@ -0,0 +1,114 @@ + +# encoding: utf-8 +#-- +# Copyright (C) 2012 Gitorious AS +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#++ + + +module Gitorious + module UserAdministration + + def suspend_user(user) + summarized do |s| + s << suspend_account(user) + + if(user.groups.count > 0) + s << remove_from_teams(user) + end + + if(committership_count(user) > 0) + s << remove_committerships(user) + end + end + end + + protected + + def committership_count(user) + Committership.all(:conditions => {:committer_id => user.id, :committer_type => "User"}).count + end + + def suspend_account(user) + summarized do |s| + user.suspend + user.save + s << " "+I18n.t("admin.user_suspend.suspended_cannot_log_back_in_or_run_git_ops") + end + end + + def remove_from_teams(user) + summarized do |s| + groups = user.groups + groups.each do |g| + g.members.delete(user) + g.save + end + group_names = groups.map { |g| g.name }.join(", ") + s << " "+I18n.t("admin.user_suspend.removed_user_from_teams", :group_names => group_names) + end + end + + def remove_committerships(user) + summarized do |s| + committerships = Committership.all(:conditions => {:committer_id => user.id, :committer_type => "User"}) + + repos = [] + committerships.each do |c| + if c.repository + repos << c.repository + end + c.delete + end + + repo_names = repos.uniq.map { |r| r.name }.join(", ") + s << " "+I18n.t("admin.user_suspend.removed_user_committerships_from_repos", :repo_names => repo_names) + end + end + + def teams_orphaned_by_user_leaving(user) + member_groups = user.groups + user.groups.find_all{ |group| sole_admin?(user, group)} + end + + def sole_admin?(user, group) + admins = group.memberships.select{|m| m.role.name == "Administrator"} + admins.none? {|a| a.user != user} + end + + def projects_orphaned_by_user_leaving(user) + orphans = Project.all(:conditions => {:owner_id => user.id, :owner_type => "User"}) + end + + private + + def summarized + summary = "" + yield(summary) + return summary + end + + end +end + + + + + + + + + + diff --git a/script/create_user b/script/create_user new file mode 100755 index 0000000..4d6ed8f --- /dev/null +++ b/script/create_user @@ -0,0 +1,21 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__)+'/../config/environment' +ActionMailer::Base.raise_delivery_errors = false +ActionMailer::Base.delivery_method = :test + +puts "Type in users login: " +login = gets.strip + +puts "Type in users e-mail: " +email = gets.strip +puts "Type in users password: " +password = gets.strip + +user = User.new(:email => email, :terms_of_use => '1') +user.password = password +user.password_confirmation = password +user.login = login + +user.save! +user.activate +puts "User '#{login}' created successfully." diff --git a/script/suspend_user b/script/suspend_user new file mode 100755 index 0000000..e3c399f --- /dev/null +++ b/script/suspend_user @@ -0,0 +1,40 @@ +#!/usr/bin/env ruby + +def print_usage + puts "" + puts "Usage: suspend_user USER_EMAIL_ADDRESS" + puts "" + puts "Suspends specified Gitorious user, revoking all access to web UI and git operations." + puts "" + puts "Account is suspended, can no longer log in and loses current browser session." + puts "SSH keys are revoked, user no longer able to push, pull, clone git repositories" + puts "Committerships and team memberships are removed." + puts "" +end + +puts "---Loading Gitorious environment---" +require File.dirname(__FILE__)+'/../config/environment' +puts "---Done loading Gitorious environment---\n\n" + +def find_user(user_email) + user = User.find_by_email(user_email) + if !user + puts "No Gitorious account with email '#{user_email}', exiting..." + exit 1 + end + return user +end + +include Gitorious::UserAdministration + +if !ARGV[0] + print_usage + exit 1 +end + +user_email = ARGV[0] + +puts "Suspending '#{user_email}'..." + +user = find_user(user_email) +puts suspend_user(user) diff --git a/test/unit/lib/gitorious/user_administration_test.rb b/test/unit/lib/gitorious/user_administration_test.rb new file mode 100644 index 0000000..4235e8a --- /dev/null +++ b/test/unit/lib/gitorious/user_administration_test.rb @@ -0,0 +1,110 @@ +# encoding: utf-8 +#-- +# Copyright (C) 2012 Gitorious +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#++ + +require 'test_helper' + +class UserAdministrationTest < ActiveSupport::TestCase + include Gitorious::UserAdministration + + should "summarize operations for feedback in CLI or GUI" do + summary = suspend_user(User.new) + assert summary.length > 0 + assert summary.class == String + assert summary =~ /Suspended Gitorious account/ + end + + context "team cleanup" do + setup do + @g = groups(:team_thunderbird) # Already has Mike + @user = users(:johan) + @g.add_member(@user, Role.admin) + end + + should "remove user from all his current teams" do + assert_equal 2, @g.members.size + remove_from_teams(@user) + @g.reload + assert_equal 1, @g.members.size + end + + should "report which teams user has been removed from" do + summary = remove_from_teams(@user) + assert summary =~ /Removed user from team\(s\): team-thunderbird, a-team/ + end + end + + context "committership cleanup" do + setup do + @r1 = repositories(:johans) + @r2 = repositories(:moes) + @user = users(:mike) + + c = @r1.committerships.new + c.creator = @user + c.committer = @user + c.build_permissions :review, :admin, :commit + c.save + end + + should "remove the users committerships" do + assert_equal 2, @r1.committerships.size + assert_equal 1, @r2.committerships.size + remove_committerships(@user) + assert_equal 1, @r1.committerships.size + assert_equal 1, @r2.committerships.size + end + + should "report repos where committerships were deleted" do + summary = remove_committerships(@user) + assert summary =~ /Removed user committerships from repo\(s\): johansprojectrepos/ + end + end + + context "reporting on orphaned teams" do + setup do + @user = users(:johan) + @g1 = groups(:a_team) # Johan is sole admin in this one + @g2 = groups(:team_thunderbird) # Mike is admin as well in this one + @g2.add_member(@user, Role.admin) + end + + should "build list of teams which are orphaned if user leaves" do + orphans = teams_orphaned_by_user_leaving(@user) + assert sole_admin?(users(:johan), @g1) + assert !sole_admin?(users(:johan), @g2) + assert_equal 1, orphans.size + assert orphans.include?(@g1) + assert !orphans.include?(@g2) + end + end + + context "reporting on orphaned projects" do + setup do + @user = users(:johan) + @johans_project = projects(:johans) + @johans_project.owner = @user + @johans_project.save + @other_project = projects(:johans) + end + + should "build list of projects which are orphaned if user leaves" do + orphans = projects_orphaned_by_user_leaving(@user) + assert orphans.all?{|p| p.owner.id == @user.id} + end + end +end |