Merge branch 'close_account_rework'
This commit is contained in:
commit
dea01600aa
41 changed files with 1126 additions and 71 deletions
|
|
@ -90,6 +90,11 @@ class PeopleController < ApplicationController
|
|||
raise ActiveRecord::RecordNotFound
|
||||
end
|
||||
|
||||
if @person.closed_account?
|
||||
redirect_to :back, :notice => t("people.show.closed_account")
|
||||
return
|
||||
end
|
||||
|
||||
@post_type = :all
|
||||
@aspect = :profile
|
||||
@share_with = (params[:share_with] == 'true')
|
||||
|
|
@ -177,6 +182,4 @@ class PeopleController < ApplicationController
|
|||
def remote_profile_with_no_user_session?
|
||||
@person && @person.remote? && !user_signed_in?
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
|
|
|||
|
|
@ -32,6 +32,12 @@ class PublicsController < ApplicationController
|
|||
|
||||
def hcard
|
||||
@person = Person.where(:guid => params[:guid]).first
|
||||
|
||||
if @person && @person.closed_account?
|
||||
render :nothing => true, :status => 404
|
||||
return
|
||||
end
|
||||
|
||||
unless @person.nil? || @person.owner.nil?
|
||||
render 'publics/hcard'
|
||||
else
|
||||
|
|
@ -45,6 +51,12 @@ class PublicsController < ApplicationController
|
|||
|
||||
def webfinger
|
||||
@person = Person.local_by_account_identifier(params[:q]) if params[:q]
|
||||
|
||||
if @person && @person.closed_account?
|
||||
render :nothing => true, :status => 404
|
||||
return
|
||||
end
|
||||
|
||||
unless @person.nil?
|
||||
render 'webfinger', :content_type => 'application/xrd+xml'
|
||||
else
|
||||
|
|
|
|||
|
|
@ -81,11 +81,9 @@ class UsersController < ApplicationController
|
|||
|
||||
def destroy
|
||||
if params[:user] && params[:user][:current_password] && current_user.valid_password?(params[:user][:current_password])
|
||||
Resque.enqueue(Jobs::DeleteAccount, current_user.id)
|
||||
current_user.lock_access!
|
||||
current_user.close_account!
|
||||
sign_out current_user
|
||||
flash[:notice] = I18n.t 'users.destroy.success'
|
||||
redirect_to multi_path
|
||||
redirect_to(multi_path, :notice => I18n.t('users.destroy.success'))
|
||||
else
|
||||
if params[:user].present? && params[:user][:current_password].present?
|
||||
flash[:error] = t 'users.destroy.wrong_password'
|
||||
|
|
|
|||
|
|
@ -39,6 +39,8 @@ module AspectsHelper
|
|||
end
|
||||
|
||||
def aspect_membership_button(aspect, contact, person)
|
||||
return if person && person.closed_account?
|
||||
|
||||
if contact.nil? || !contact.aspect_memberships.detect{ |am| am.aspect_id == aspect.id}
|
||||
add_to_aspect_button(aspect.id, person.id)
|
||||
else
|
||||
|
|
|
|||
112
app/models/account_deleter.rb
Normal file
112
app/models/account_deleter.rb
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
# Copyright (c) 2010-2011, Diaspora Inc. This file is
|
||||
# licensed under the Affero General Public License version 3 or later. See
|
||||
# the COPYRIGHT file.
|
||||
|
||||
class AccountDeleter
|
||||
|
||||
# Things that are not removed from the database:
|
||||
# - Comments
|
||||
# - Likes
|
||||
# - Messages
|
||||
# - NotificationActors
|
||||
#
|
||||
# Given that the User in question will be tombstoned, all of the
|
||||
# above will come from an anonomized account (via the UI).
|
||||
# The deleted user will appear as "Deleted Account" in
|
||||
# the interface.
|
||||
|
||||
attr_accessor :person, :user
|
||||
|
||||
def initialize(diaspora_handle)
|
||||
self.person = Person.where(:diaspora_handle => diaspora_handle).first
|
||||
self.user = self.person.owner
|
||||
end
|
||||
|
||||
def perform!
|
||||
#person
|
||||
delete_standard_person_associations
|
||||
remove_conversation_visibilities
|
||||
remove_share_visibilities_on_persons_posts
|
||||
delete_contacts_of_me
|
||||
tombstone_person_and_profile
|
||||
|
||||
if self.user
|
||||
#user deletion methods
|
||||
remove_share_visibilities_on_contacts_posts
|
||||
delete_standard_user_associations
|
||||
disassociate_invitations
|
||||
disconnect_contacts
|
||||
tombstone_user
|
||||
end
|
||||
end
|
||||
|
||||
#user deletions
|
||||
def normal_ar_user_associates_to_delete
|
||||
[:tag_followings, :authorizations, :invitations_to_me, :services, :aspects, :user_preferences, :notifications, :blocks]
|
||||
end
|
||||
|
||||
def special_ar_user_associations
|
||||
[:invitations_from_me, :person, :contacts]
|
||||
end
|
||||
|
||||
def ignored_ar_user_associations
|
||||
[:followed_tags, :invited_by, :contact_people, :applications, :aspect_memberships]
|
||||
end
|
||||
|
||||
def delete_standard_user_associations
|
||||
normal_ar_user_associates_to_delete.each do |asso|
|
||||
self.user.send(asso).each{|model| model.delete}
|
||||
end
|
||||
end
|
||||
|
||||
def delete_standard_person_associations
|
||||
normal_ar_person_associates_to_delete.each do |asso|
|
||||
self.person.send(asso).delete_all
|
||||
end
|
||||
end
|
||||
|
||||
def disassociate_invitations
|
||||
user.invitations_from_me.each do |inv|
|
||||
inv.convert_to_admin!
|
||||
end
|
||||
end
|
||||
|
||||
def disconnect_contacts
|
||||
user.contacts.destroy_all
|
||||
end
|
||||
|
||||
# Currently this would get deleted due to the db foreign key constrainsts,
|
||||
# but we'll keep this method here for completeness
|
||||
def remove_share_visibilities_on_persons_posts
|
||||
ShareVisibility.for_contacts_of_a_person(person).destroy_all
|
||||
end
|
||||
|
||||
def remove_share_visibilities_on_contacts_posts
|
||||
ShareVisibility.for_a_users_contacts(user).destroy_all
|
||||
end
|
||||
|
||||
def remove_conversation_visibilities
|
||||
ConversationVisibility.where(:person_id => person.id).destroy_all
|
||||
end
|
||||
|
||||
def tombstone_person_and_profile
|
||||
self.person.lock_access!
|
||||
self.person.clear_profile!
|
||||
end
|
||||
|
||||
def tombstone_user
|
||||
self.user.clear_account!
|
||||
end
|
||||
|
||||
def delete_contacts_of_me
|
||||
Contact.all_contacts_of_person(self.person).destroy_all
|
||||
end
|
||||
|
||||
def normal_ar_person_associates_to_delete
|
||||
[:posts, :photos, :mentions]
|
||||
end
|
||||
|
||||
def ignored_or_special_ar_person_associations
|
||||
[:comments, :contacts, :notification_actors, :notifications, :owner, :profile ]
|
||||
end
|
||||
end
|
||||
50
app/models/account_deletion.rb
Normal file
50
app/models/account_deletion.rb
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
# Copyright (c) 2010-2011, Diaspora Inc. This file is
|
||||
# licensed under the Affero General Public License version 3 or later. See
|
||||
# the COPYRIGHT file.
|
||||
|
||||
class AccountDeletion < ActiveRecord::Base
|
||||
include ROXML
|
||||
include Diaspora::Webhooks
|
||||
|
||||
|
||||
belongs_to :person
|
||||
after_create :queue_delete_account
|
||||
|
||||
attr_accessible :person
|
||||
|
||||
xml_name :account_deletion
|
||||
xml_attr :diaspora_handle
|
||||
|
||||
def person=(person)
|
||||
self[:diaspora_handle] = person.diaspora_handle
|
||||
self[:person_id] = person.id
|
||||
end
|
||||
|
||||
def diaspora_handle=(diaspora_handle)
|
||||
self[:diaspora_handle] = diaspora_handle
|
||||
self[:person_id] ||= Person.find_by_diaspora_handle(diaspora_handle).id
|
||||
end
|
||||
|
||||
|
||||
|
||||
def queue_delete_account
|
||||
Resque.enqueue(Jobs::DeleteAccount, self.id)
|
||||
end
|
||||
|
||||
def perform!
|
||||
self.dispatch if person.local?
|
||||
AccountDeleter.new(self.diaspora_handle).perform!
|
||||
end
|
||||
|
||||
def subscribers(user)
|
||||
person.owner.contact_people.remote | Person.who_have_reshared_a_users_posts(person.owner).remote
|
||||
end
|
||||
|
||||
def dispatch
|
||||
Postzord::Dispatcher.build(person.owner, self).post
|
||||
end
|
||||
|
||||
def public?
|
||||
true
|
||||
end
|
||||
end
|
||||
|
|
@ -15,7 +15,8 @@ class Contact < ActiveRecord::Base
|
|||
has_many :posts, :through => :share_visibilities, :source => :shareable, :source_type => 'Post'
|
||||
|
||||
validate :not_contact_for_self,
|
||||
:not_blocked_user
|
||||
:not_blocked_user,
|
||||
:not_contact_with_closed_account
|
||||
|
||||
validates_presence_of :user
|
||||
validates_uniqueness_of :person_id, :scope => :user_id
|
||||
|
|
@ -23,7 +24,10 @@ class Contact < ActiveRecord::Base
|
|||
before_destroy :destroy_notifications,
|
||||
:repopulate_cache!
|
||||
|
||||
# contact.sharing is true when contact.person is sharing with contact.user
|
||||
|
||||
scope :all_contacts_of_person, lambda {|x| where(:person_id => x.id)}
|
||||
|
||||
# contact.sharing is true when contact.person is sharing with contact.user
|
||||
scope :sharing, lambda {
|
||||
where(:sharing => true)
|
||||
}
|
||||
|
|
@ -94,6 +98,12 @@ class Contact < ActiveRecord::Base
|
|||
end
|
||||
|
||||
private
|
||||
def not_contact_with_closed_account
|
||||
if person_id && person.closed_account?
|
||||
errors[:base] << 'Cannot be in contact with a closed account'
|
||||
end
|
||||
end
|
||||
|
||||
def not_contact_for_self
|
||||
if person_id && person.owner == user
|
||||
errors[:base] << 'Cannot create self-contact'
|
||||
|
|
|
|||
|
|
@ -99,6 +99,17 @@ class Invitation < ActiveRecord::Base
|
|||
self
|
||||
end
|
||||
|
||||
|
||||
# converts a personal invitation to an admin invite
|
||||
# used in account deletion
|
||||
# @return [Invitation] self
|
||||
def convert_to_admin!
|
||||
self.admin = true
|
||||
self.sender = nil
|
||||
self.aspect = nil
|
||||
self.save
|
||||
self
|
||||
end
|
||||
# @return [Invitation] self
|
||||
def resend
|
||||
self.send!
|
||||
|
|
|
|||
|
|
@ -6,10 +6,9 @@
|
|||
module Jobs
|
||||
class DeleteAccount < Base
|
||||
@queue = :delete_account
|
||||
def self.perform(user_id)
|
||||
user = User.find(user_id)
|
||||
user.remove_all_traces
|
||||
user.destroy
|
||||
def self.perform(account_deletion_id)
|
||||
account_deletion = AccountDeletion.find(account_deletion_id)
|
||||
account_deletion.perform!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ class Person < ActiveRecord::Base
|
|||
|
||||
before_destroy :remove_all_traces
|
||||
before_validation :clean_url
|
||||
|
||||
|
||||
validates :url, :presence => true
|
||||
validates :profile, :presence => true
|
||||
validates :serialized_public_key, :presence => true
|
||||
|
|
@ -61,6 +61,10 @@ class Person < ActiveRecord::Base
|
|||
|
||||
scope :profile_tagged_with, lambda{|tag_name| joins(:profile => :tags).where(:profile => {:tags => {:name => tag_name}}).where('profiles.searchable IS TRUE') }
|
||||
|
||||
scope :who_have_reshared_a_users_posts, lambda{|user|
|
||||
joins(:posts).where(:posts => {:root_guid => StatusMessage.guids_for_author(user.person), :type => 'Reshare'} )
|
||||
}
|
||||
|
||||
def self.community_spotlight
|
||||
AppConfig[:community_spotlight].present? ? Person.where(:diaspora_handle => AppConfig[:community_spotlight]) : []
|
||||
end
|
||||
|
|
@ -78,7 +82,7 @@ class Person < ActiveRecord::Base
|
|||
super
|
||||
self.profile ||= Profile.new unless profile_set
|
||||
end
|
||||
|
||||
|
||||
def self.find_from_id_or_username(params)
|
||||
p = if params[:id].present?
|
||||
Person.where(:id => params[:id]).first
|
||||
|
|
@ -91,7 +95,6 @@ class Person < ActiveRecord::Base
|
|||
p
|
||||
end
|
||||
|
||||
|
||||
def self.search_query_string(query)
|
||||
query = query.downcase
|
||||
like_operator = postgres? ? "ILIKE" : "LIKE"
|
||||
|
|
@ -276,8 +279,6 @@ class Person < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
# @param person [Person]
|
||||
# @param url [String]
|
||||
def update_url(url)
|
||||
|
|
@ -288,6 +289,16 @@ class Person < ActiveRecord::Base
|
|||
self.update_attributes(:url => newuri)
|
||||
end
|
||||
|
||||
def lock_access!
|
||||
self.closed_account = true
|
||||
self.save
|
||||
end
|
||||
|
||||
def clear_profile!
|
||||
self.profile.tombstone!
|
||||
self
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def clean_url
|
||||
|
|
|
|||
|
|
@ -146,6 +146,14 @@ class Profile < ActiveRecord::Base
|
|||
self.full_name
|
||||
end
|
||||
|
||||
def tombstone!
|
||||
self.taggings.delete_all
|
||||
clearable_fields.each do |field|
|
||||
self[field] = nil
|
||||
end
|
||||
self.save
|
||||
end
|
||||
|
||||
protected
|
||||
def strip_names
|
||||
self.first_name.strip! if self.first_name
|
||||
|
|
@ -166,6 +174,10 @@ class Profile < ActiveRecord::Base
|
|||
end
|
||||
|
||||
private
|
||||
def clearable_fields
|
||||
self.attributes.keys - Profile.protected_attributes.to_a - ["created_at", "updated_at", "person_id"]
|
||||
end
|
||||
|
||||
def absolutify_local_url url
|
||||
pod_url = AppConfig[:pod_url].dup
|
||||
pod_url.chop! if AppConfig[:pod_url][-1,1] == '/'
|
||||
|
|
|
|||
|
|
@ -6,6 +6,13 @@ class ShareVisibility < ActiveRecord::Base
|
|||
belongs_to :contact
|
||||
belongs_to :shareable, :polymorphic => :true
|
||||
|
||||
scope :for_a_users_contacts, lambda { |user|
|
||||
where(:contact_id => user.contacts.map {|c| c.id})
|
||||
}
|
||||
scope :for_contacts_of_a_person, lambda { |person|
|
||||
where(:contact_id => person.contacts.map {|c| c.id})
|
||||
}
|
||||
|
||||
# Perform a batch import, given a set of contacts and a shareable
|
||||
# @note performs a bulk insert in mySQL; performs linear insertions in postgres
|
||||
# @param contacts [Array<Contact>] Recipients
|
||||
|
|
|
|||
|
|
@ -42,6 +42,10 @@ class StatusMessage < Post
|
|||
joins(:likes).where(:likes => {:author_id => person.id})
|
||||
}
|
||||
|
||||
def self.guids_for_author(person)
|
||||
Post.connection.select_values(Post.where(:author_id => person.id).select('posts.guid').to_sql)
|
||||
end
|
||||
|
||||
def self.user_tag_stream(user, tag_ids)
|
||||
owned_or_visible_by_user(user).
|
||||
tag_stream(tag_ids)
|
||||
|
|
|
|||
|
|
@ -33,17 +33,18 @@ class User < ActiveRecord::Base
|
|||
has_one :person, :foreign_key => :owner_id
|
||||
delegate :public_key, :posts, :photos, :owns?, :diaspora_handle, :name, :public_url, :profile, :first_name, :last_name, :to => :person
|
||||
|
||||
has_many :invitations_from_me, :class_name => 'Invitation', :foreign_key => :sender_id, :dependent => :destroy
|
||||
has_many :invitations_to_me, :class_name => 'Invitation', :foreign_key => :recipient_id, :dependent => :destroy
|
||||
has_many :invitations_from_me, :class_name => 'Invitation', :foreign_key => :sender_id
|
||||
has_many :invitations_to_me, :class_name => 'Invitation', :foreign_key => :recipient_id
|
||||
has_many :aspects, :order => 'order_id ASC'
|
||||
has_many :aspect_memberships, :through => :aspects
|
||||
has_many :contacts
|
||||
has_many :contact_people, :through => :contacts, :source => :person
|
||||
has_many :services, :dependent => :destroy
|
||||
has_many :user_preferences, :dependent => :destroy
|
||||
has_many :tag_followings, :dependent => :destroy
|
||||
has_many :services
|
||||
has_many :user_preferences
|
||||
has_many :tag_followings
|
||||
has_many :followed_tags, :through => :tag_followings, :source => :tag, :order => 'tags.name'
|
||||
has_many :blocks
|
||||
has_many :notifications, :foreign_key => :recipient_id
|
||||
|
||||
has_many :authorizations, :class_name => 'OAuth2::Provider::Models::ActiveRecord::Authorization', :foreign_key => :resource_owner_id
|
||||
has_many :applications, :through => :authorizations, :source => :client
|
||||
|
|
@ -487,4 +488,26 @@ class User < ActiveRecord::Base
|
|||
errors[:base] << 'That username has already been taken'
|
||||
end
|
||||
end
|
||||
|
||||
def close_account!
|
||||
self.person.lock_access!
|
||||
self.lock_access!
|
||||
AccountDeletion.create(:person => self.person)
|
||||
end
|
||||
|
||||
def clear_account!
|
||||
clearable_fields.each do |field|
|
||||
self[field] = nil
|
||||
end
|
||||
|
||||
random_password = ActiveSupport::SecureRandom.hex(20)
|
||||
self.password = random_password
|
||||
self.password_confirmation = random_password
|
||||
self.save(:validate => false)
|
||||
end
|
||||
|
||||
private
|
||||
def clearable_fields
|
||||
self.attributes.keys - ["id", "username", "encrypted_password", "created_at", "updated_at", "locked_at", "serialized_private_key"]
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -164,13 +164,42 @@
|
|||
= link_to t('.download_photos'), "#", :class => "button", :id => "photo-export-button", :title => t('.photo_export_unavailable')
|
||||
|
||||
.span-5.last
|
||||
%h3
|
||||
= t('.close_account')
|
||||
= form_for 'user', :url => user_path, :html => { :method => :delete } do |f|
|
||||
= f.error_messages
|
||||
%h3
|
||||
= t('.close_account_text')
|
||||
=link_to 'Close Account', '#close_account_pane', :rel => 'facebox', :class => "button"
|
||||
|
||||
%p
|
||||
= f.label :close_account_password, t('.current_password'), :for => :close_account_password
|
||||
= f.password_field :current_password, :id => :close_account_password
|
||||
%p
|
||||
= f.submit t('.close_account'), :confirm => t('are_you_sure')
|
||||
.hidden#close_account_pane{:rel => 'facebox'}
|
||||
#inner_account_delete
|
||||
%h1
|
||||
= t('.close_account.dont_go')
|
||||
%p
|
||||
= t('.close_account.make_diaspora_better')
|
||||
.span-10
|
||||
= image_tag 'http://itstrulyrandom.com/wp-content/uploads/2008/03/sadcat.jpg'
|
||||
%br
|
||||
%small
|
||||
%b
|
||||
= t('.close_account.mr_wiggles')
|
||||
.span-10.last
|
||||
%ul
|
||||
%li
|
||||
= t('.close_account.what_we_delete')
|
||||
%li
|
||||
= t('.close_account.locked_out')
|
||||
%li
|
||||
= t('.close_account.lock_username')
|
||||
%li
|
||||
= t('.close_account.no_turning_back')
|
||||
%p
|
||||
%b
|
||||
= t('.close_account.no_turning_back')
|
||||
|
||||
|
||||
= form_for 'user', :url => user_path, :html => { :method => :delete } do |f|
|
||||
= f.error_messages
|
||||
|
||||
%p
|
||||
= f.label :close_account_password, t('.current_password'), :for => :close_account_password
|
||||
= f.password_field :current_password, :id => :close_account_password
|
||||
%p
|
||||
= f.submit t('.close_account_text'), :confirm => t('are_you_sure_delete_account')
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ en:
|
|||
password: "Password"
|
||||
password_confirmation: "Password confirmation"
|
||||
are_you_sure: "Are you sure?"
|
||||
are_you_sure_delete_account: "Are you sure you want to close your account? This can't be undone!"
|
||||
fill_me_out: "Fill me out"
|
||||
back: "Back"
|
||||
public: "Public"
|
||||
|
|
@ -551,6 +552,7 @@ en:
|
|||
message: "Message"
|
||||
mention: "Mention"
|
||||
ignoring: "You are ignoring all posts from %{name}."
|
||||
closed_account: "This account has been closed."
|
||||
sub_header:
|
||||
you_have_no_tags: "you have no tags!"
|
||||
add_some: "add some"
|
||||
|
|
@ -930,7 +932,7 @@ en:
|
|||
edit:
|
||||
export_data: "Export Data"
|
||||
photo_export_unavailable: "Photo exporting currently unavailable"
|
||||
close_account: "Close Account"
|
||||
close_account_text: "Close Account"
|
||||
change_language: "Change language"
|
||||
change_password: "Change password"
|
||||
change_email: "Change email"
|
||||
|
|
@ -956,6 +958,16 @@ en:
|
|||
show_getting_started: 'Re-enable Getting Started'
|
||||
getting_started: 'New User Prefrences'
|
||||
|
||||
close_account:
|
||||
dont_go: "Hey, please don't go!"
|
||||
make_diaspora_better: "We want you to help us make Diaspora better, so you should help us out instead of leaving. If you do want to leave, we want you to know what happens next."
|
||||
mr_wiggles: 'Mr Wiggles will be sad to see you go'
|
||||
what_we_delete: "We delete all of your posts, profile data, as soon as humanly possible. Your comments will hang around, but be associated with your Diaspora Handle."
|
||||
locked_out: "You will get signed out and locked out of your account."
|
||||
lock_username: "This will lock your username if you decided to sign back up."
|
||||
no_turning_back: "Currently, there is no turning back."
|
||||
if_you_want_this: "If you really want this, type in your password below and click 'Close Account'"
|
||||
|
||||
privacy_settings:
|
||||
title: "Privacy Settings"
|
||||
ignored_users: "Ignored Users"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
class AddClosedAccountFlagToPerson < ActiveRecord::Migration
|
||||
def self.up
|
||||
add_column :people, :closed_account, :boolean, :default => false
|
||||
end
|
||||
|
||||
def self.down
|
||||
remove_column :people, :closed_account
|
||||
end
|
||||
end
|
||||
12
db/migrate/20111109023618_create_account_deletions.rb
Normal file
12
db/migrate/20111109023618_create_account_deletions.rb
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
class CreateAccountDeletions < ActiveRecord::Migration
|
||||
def self.up
|
||||
create_table :account_deletions do |t|
|
||||
t.string :diaspora_handle
|
||||
t.integer :person_id
|
||||
end
|
||||
end
|
||||
|
||||
def self.down
|
||||
drop_table :account_deletions
|
||||
end
|
||||
end
|
||||
16
db/schema.rb
16
db/schema.rb
|
|
@ -10,7 +10,12 @@
|
|||
#
|
||||
# It's strongly recommended to check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(:version => 20111101202137) do
|
||||
ActiveRecord::Schema.define(:version => 20111109023618) do
|
||||
|
||||
create_table "account_deletions", :force => true do |t|
|
||||
t.string "diaspora_handle"
|
||||
t.integer "person_id"
|
||||
end
|
||||
|
||||
create_table "aspect_memberships", :force => true do |t|
|
||||
t.integer "aspect_id", :null => false
|
||||
|
|
@ -238,13 +243,14 @@ ActiveRecord::Schema.define(:version => 20111101202137) do
|
|||
add_index "oauth_clients", ["nonce"], :name => "index_oauth_clients_on_nonce", :unique => true
|
||||
|
||||
create_table "people", :force => true do |t|
|
||||
t.string "guid", :null => false
|
||||
t.text "url", :null => false
|
||||
t.string "diaspora_handle", :null => false
|
||||
t.text "serialized_public_key", :null => false
|
||||
t.string "guid", :null => false
|
||||
t.text "url", :null => false
|
||||
t.string "diaspora_handle", :null => false
|
||||
t.text "serialized_public_key", :null => false
|
||||
t.integer "owner_id"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.boolean "closed_account", :default => false
|
||||
end
|
||||
|
||||
add_index "people", ["diaspora_handle"], :name => "index_people_on_diaspora_handle", :unique => true
|
||||
|
|
|
|||
|
|
@ -8,22 +8,24 @@ Feature: Close Account
|
|||
Given I am signed in
|
||||
When I click on my name in the header
|
||||
And I follow "Settings"
|
||||
And I put in my password in "close_account_password"
|
||||
And I follow "Close Account"
|
||||
And I put in my password in "close_account_password" in the modal window
|
||||
And I preemptively confirm the alert
|
||||
And I press "Close Account"
|
||||
And I press "Close Account" in the modal window
|
||||
Then I should be on the new user session page
|
||||
|
||||
When I try to sign in manually
|
||||
Then I should be on the new user session page
|
||||
When I wait for the ajax to finish
|
||||
Then I should see "Invalid email or password."
|
||||
Then I should see "Your account is locked."
|
||||
|
||||
Scenario: user is forced to enter something in the password field on closing account
|
||||
Given I am signed in
|
||||
When I click on my name in the header
|
||||
And I follow "Settings"
|
||||
And I follow "Close Account"
|
||||
And I preemptively confirm the alert
|
||||
And I press "Close Account"
|
||||
And I press "Close Account" in the modal window
|
||||
Then I should be on the edit user page
|
||||
And I should see "Please enter your current password to close your account."
|
||||
|
||||
|
|
@ -31,9 +33,10 @@ Feature: Close Account
|
|||
Given I am signed in
|
||||
When I click on my name in the header
|
||||
And I follow "Settings"
|
||||
And I follow "Close Account"
|
||||
And I preemptively confirm the alert
|
||||
And I fill in "close_account_password" with "none sense"
|
||||
And I press "Close Account"
|
||||
And I fill in "close_account_password" with "none sense" in the modal window
|
||||
And I press "Close Account" in the modal window
|
||||
Then I should be on the edit user page
|
||||
And I should see "The entered password didn't match your current password."
|
||||
|
||||
|
|
@ -50,9 +53,10 @@ Feature: Close Account
|
|||
Then I sign in as "bob@bob.bob"
|
||||
When I click on my name in the header
|
||||
And I follow "Settings"
|
||||
And I put in my password in "close_account_password"
|
||||
And I follow "Close Account"
|
||||
And I put in my password in "close_account_password" in the modal window
|
||||
And I preemptively confirm the alert
|
||||
And I press "Close Account"
|
||||
And I press "Close Account" in the modal window
|
||||
Then I sign in as "alice@alice.alice"
|
||||
And I am on the home page
|
||||
Then I should see "Hi, Bob Jones long time no see"
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ class Postzord::Receiver::Public < Postzord::Receiver
|
|||
|
||||
if @object.respond_to?(:relayable?)
|
||||
receive_relayable
|
||||
elsif @object.is_a?(AccountDeletion)
|
||||
#nothing
|
||||
else
|
||||
Resque.enqueue(Jobs::ReceiveLocalBatch, @object.class.to_s, @object.id, self.recipient_user_ids)
|
||||
true
|
||||
|
|
|
|||
|
|
@ -2281,6 +2281,9 @@ ul.show_comments,
|
|||
:position relative
|
||||
:top 10px
|
||||
|
||||
#inner_account_delete
|
||||
:width 810px
|
||||
|
||||
#aspect_edit_pane
|
||||
:width 810px
|
||||
.person_tiles
|
||||
|
|
|
|||
|
|
@ -161,6 +161,13 @@ describe PeopleController do
|
|||
response.code.should == "404"
|
||||
end
|
||||
|
||||
it 'redirects home for closed account' do
|
||||
@person = Factory.create(:person, :closed_account => true)
|
||||
get :show, :id => @person.id
|
||||
response.should be_redirect
|
||||
flash[:notice].should_not be_blank
|
||||
end
|
||||
|
||||
it 'does not allow xss attacks' do
|
||||
user2 = bob
|
||||
profile = user2.profile
|
||||
|
|
|
|||
|
|
@ -97,6 +97,12 @@ describe PublicsController do
|
|||
assigns[:person].should be_nil
|
||||
response.should be_not_found
|
||||
end
|
||||
|
||||
it 'finds nothing for closed accounts' do
|
||||
@user.person.update_attributes(:closed_account => true)
|
||||
get :hcard, :guid => @user.person.guid.to_s
|
||||
response.should be_not_found
|
||||
end
|
||||
end
|
||||
|
||||
describe '#webfinger' do
|
||||
|
|
@ -127,6 +133,12 @@ describe PublicsController do
|
|||
get :webfinger, :q => @user.diaspora_handle
|
||||
response.body.should include "http://webfinger.net/rel/profile-page"
|
||||
end
|
||||
|
||||
it 'finds nothing for closed accounts' do
|
||||
@user.person.update_attributes(:closed_account => true)
|
||||
get :webfinger, :q => @user.diaspora_handle
|
||||
response.should be_not_found
|
||||
end
|
||||
end
|
||||
|
||||
describe '#hub' do
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ describe UsersController do
|
|||
@aspect = @user.aspects.first
|
||||
@aspect1 = @user.aspects.create(:name => "super!!")
|
||||
sign_in :user, @user
|
||||
@controller.stub(:current_user).and_return(@user)
|
||||
end
|
||||
|
||||
describe '#export' do
|
||||
|
|
@ -192,15 +193,16 @@ describe UsersController do
|
|||
delete :destroy, :user => { :current_password => "stuff" }
|
||||
end
|
||||
|
||||
it 'closes the account' do
|
||||
alice.should_receive(:close_account!)
|
||||
delete :destroy, :user => { :current_password => "bluepin7" }
|
||||
end
|
||||
|
||||
it 'enqueues a delete job' do
|
||||
Resque.should_receive(:enqueue).with(Jobs::DeleteAccount, alice.id)
|
||||
delete :destroy, :user => { :current_password => "bluepin7" }
|
||||
end
|
||||
|
||||
it 'locks the user out' do
|
||||
delete :destroy, :user => { :current_password => "bluepin7" }
|
||||
alice.reload.access_locked?.should be_true
|
||||
end
|
||||
end
|
||||
|
||||
describe '#confirm_email' do
|
||||
|
|
|
|||
|
|
@ -13,9 +13,18 @@ end
|
|||
Factory.define :profile do |p|
|
||||
p.sequence(:first_name) { |n| "Robert#{n}#{r_str}" }
|
||||
p.sequence(:last_name) { |n| "Grimm#{n}#{r_str}" }
|
||||
p.bio "I am a cat lover and I love to run"
|
||||
p.gender "robot"
|
||||
p.location "Earth"
|
||||
p.birthday Date.today
|
||||
end
|
||||
|
||||
Factory.define :profile_with_image_url, :parent => :profile do |p|
|
||||
p.image_url "http://example.com/image.jpg"
|
||||
p.image_url_medium "http://example.com/image_mid.jpg"
|
||||
p.image_url_small "http://example.com/image_small.jpg"
|
||||
end
|
||||
|
||||
Factory.define :person do |p|
|
||||
p.sequence(:diaspora_handle) { |n| "bob-person-#{n}#{r_str}@example.net" }
|
||||
p.sequence(:url) { |n| AppConfig[:pod_url] }
|
||||
|
|
@ -28,6 +37,13 @@ Factory.define :person do |p|
|
|||
end
|
||||
end
|
||||
|
||||
Factory.define :account_deletion do |d|
|
||||
d.association :person
|
||||
d.after_build do |delete|
|
||||
delete.diaspora_handle= delete.person.diaspora_handle
|
||||
end
|
||||
end
|
||||
|
||||
Factory.define :searchable_person, :parent => :person do |p|
|
||||
p.after_build do |person|
|
||||
person.profile = Factory.build(:profile, :person => person, :searchable => true)
|
||||
|
|
@ -166,3 +182,22 @@ end
|
|||
Factory.define(:oauth_access_token, :class => OAuth2::Provider.access_token_class) do |a|
|
||||
a.association(:authorization, :factory => :oauth_authorization)
|
||||
end
|
||||
|
||||
Factory.define(:tag, :class => ActsAsTaggableOn::Tag) do |t|
|
||||
t.name "partytimeexcellent"
|
||||
end
|
||||
|
||||
Factory.define(:tag_following) do |a|
|
||||
a.association(:tag, :factory => :tag)
|
||||
a.association(:user, :factory => :user)
|
||||
end
|
||||
|
||||
Factory.define(:contact) do |c|
|
||||
c.association(:person, :factory => :person)
|
||||
c.association(:user, :factory => :user)
|
||||
end
|
||||
|
||||
Factory.define(:mention) do |c|
|
||||
c.association(:person, :factory => :person)
|
||||
c.association(:post, :factory => :status_message)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -61,4 +61,15 @@ module HelperMethods
|
|||
fixture_name = File.join(File.dirname(__FILE__), 'fixtures', fixture_filename)
|
||||
File.open(fixture_name)
|
||||
end
|
||||
|
||||
def create_conversation_with_message(sender, recipient_person, subject, text)
|
||||
create_hash = {
|
||||
:author => sender.person,
|
||||
:participant_ids => [sender.person.id, recipient_person.id],
|
||||
:subject => subject,
|
||||
:messages_attributes => [ {:author => sender.person, :text => text} ]
|
||||
}
|
||||
|
||||
Conversation.create!(create_hash)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
138
spec/integration/account_deletion_spec.rb
Normal file
138
spec/integration/account_deletion_spec.rb
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe 'deleteing your account' do
|
||||
context "user" do
|
||||
before do
|
||||
@bob2 = bob
|
||||
@person = @bob2.person
|
||||
@alices_post = alice.post(:status_message, :text => "@{@bob2 Grimn; #{@bob2.person.diaspora_handle}} you are silly", :to => alice.aspects.find_by_name('generic'))
|
||||
|
||||
@bobs_contact_ids = @bob2.contacts.map {|c| c.id}
|
||||
|
||||
#@bob2's own content
|
||||
@bob2.post(:status_message, :text => 'asldkfjs', :to => @bob2.aspects.first)
|
||||
f = Factory(:photo, :author => @bob2.person)
|
||||
|
||||
@aspect_vis = AspectVisibility.where(:aspect_id => @bob2.aspects.map(&:id))
|
||||
|
||||
#objects on post
|
||||
@bob2.like(true, :target => @alices_post)
|
||||
@bob2.comment("here are some thoughts on your post", :post => @alices_post)
|
||||
|
||||
#conversations
|
||||
create_conversation_with_message(alice, @bob2.person, "Subject", "Hey @bob2")
|
||||
|
||||
#join tables
|
||||
@users_sv = ShareVisibility.where(:contact_id => @bobs_contact_ids).all
|
||||
@persons_sv = ShareVisibility.where(:contact_id => bob.person.contacts.map(&:id)).all
|
||||
|
||||
#user associated objects
|
||||
@prefs = []
|
||||
%w{mentioned liked reshared}.each do |pref|
|
||||
@prefs << @bob2.user_preferences.create!(:email_type => pref)
|
||||
end
|
||||
|
||||
# notifications
|
||||
@notifications = []
|
||||
3.times do |n|
|
||||
@notifications << Factory(:notification, :recipient => @bob2)
|
||||
end
|
||||
|
||||
# services
|
||||
@services = []
|
||||
3.times do |n|
|
||||
@services << Factory(:service, :user => @bob2)
|
||||
end
|
||||
|
||||
# block
|
||||
@block = @bob2.blocks.create!(:person => eve.person)
|
||||
|
||||
#authorization
|
||||
@authorization = Factory.create(:oauth_authorization, :resource_owner => @bob2)
|
||||
|
||||
AccountDeleter.new(@bob2.person.diaspora_handle).perform!
|
||||
@bob2.reload
|
||||
end
|
||||
|
||||
it "deletes all of the user's preferences" do
|
||||
UserPreference.where(:id => @prefs.map{|pref| pref.id}).should be_empty
|
||||
end
|
||||
|
||||
it "deletes all of the user's notifications" do
|
||||
Notification.where(:id => @notifications.map{|n| n.id}).should be_empty
|
||||
end
|
||||
|
||||
it "deletes all of the users's blocked users" do
|
||||
Block.where(:id => @block.id).should be_empty
|
||||
end
|
||||
|
||||
it "deletes all of the user's services" do
|
||||
Service.where(:id => @services.map{|s| s.id}).should be_empty
|
||||
end
|
||||
|
||||
it 'deletes all of @bob2s share visiblites' do
|
||||
ShareVisibility.where(:id => @users_sv.map{|sv| sv.id}).should be_empty
|
||||
ShareVisibility.where(:id => @persons_sv.map{|sv| sv.id}).should be_empty
|
||||
end
|
||||
|
||||
it 'deletes all of @bob2s aspect visiblites' do
|
||||
AspectVisibility.where(:id => @aspect_vis.map(&:id)).should be_empty
|
||||
end
|
||||
|
||||
it 'deletes all aspects' do
|
||||
@bob2.aspects.should be_empty
|
||||
end
|
||||
|
||||
it 'deletes all user contacts' do
|
||||
@bob2.contacts.should be_empty
|
||||
end
|
||||
|
||||
it 'deletes all the authorizations' do
|
||||
OAuth2::Provider.authorization_class.where(:id => @authorization.id).should be_empty
|
||||
end
|
||||
|
||||
it "clears the account fields" do
|
||||
@bob2.send(:clearable_fields).each do |field|
|
||||
@bob2.reload[field].should be_blank
|
||||
end
|
||||
end
|
||||
|
||||
it_should_behave_like 'it removes the person associations'
|
||||
end
|
||||
|
||||
context 'remote person' do
|
||||
before do
|
||||
@person = remote_raphael
|
||||
|
||||
#contacts
|
||||
@contacts = @person.contacts
|
||||
|
||||
#posts
|
||||
@posts = (1..3).map do
|
||||
Factory.create(:status_message, :author => @person)
|
||||
end
|
||||
|
||||
@persons_sv = @posts.each do |post|
|
||||
@contacts.each do |contact|
|
||||
ShareVisibility.create!(:contact_id => contact.id, :shareable => post)
|
||||
end
|
||||
end
|
||||
|
||||
#photos
|
||||
@photo = Factory(:photo, :author => @person)
|
||||
|
||||
#mentions
|
||||
@mentions = 3.times do
|
||||
Factory.create(:mention, :person => @person)
|
||||
end
|
||||
|
||||
#conversations
|
||||
create_conversation_with_message(alice, @person, "Subject", "Hey @bob2")
|
||||
|
||||
AccountDeleter.new(@person.diaspora_handle).perform!
|
||||
@person.reload
|
||||
end
|
||||
|
||||
it_should_behave_like 'it removes the person associations'
|
||||
end
|
||||
end
|
||||
|
|
@ -61,4 +61,22 @@ describe 'making sure the spec runner works' do
|
|||
alice.comment "yo", :post => person_status
|
||||
end
|
||||
end
|
||||
|
||||
describe '#post' do
|
||||
it 'creates a notification with a mention' do
|
||||
lambda{
|
||||
alice.post(:status_message, :text => "@{Bob Grimn; #{bob.person.diaspora_handle}} you are silly", :to => alice.aspects.find_by_name('generic'))
|
||||
}.should change(Notification, :count).by(1)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#create_conversation_with_message" do
|
||||
it 'creates a conversation and a message' do
|
||||
conversation = create_conversation_with_message(alice, bob.person, "Subject", "Hey Bob")
|
||||
|
||||
conversation.participants.should == [alice.person, bob.person]
|
||||
conversation.subject.should == "Subject"
|
||||
conversation.messages.first.text.should == "Hey Bob"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
183
spec/models/account_deleter_spec.rb
Normal file
183
spec/models/account_deleter_spec.rb
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
# Copyright (c) 2010-2011, Diaspora Inc. This file is
|
||||
# licensed under the Affero General Public License version 3 or later. See
|
||||
# the COPYRIGHT file.
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe AccountDeleter do
|
||||
before do
|
||||
@account_deletion = AccountDeleter.new(bob.person.diaspora_handle)
|
||||
@account_deletion.user = bob
|
||||
end
|
||||
|
||||
it "attaches the user" do
|
||||
AccountDeleter.new(bob.person.diaspora_handle).user.should == bob
|
||||
AccountDeleter.new(remote_raphael.diaspora_handle).user.should == nil
|
||||
end
|
||||
|
||||
describe '#perform' do
|
||||
|
||||
|
||||
user_removal_methods = [:delete_standard_user_associations,
|
||||
:disassociate_invitations,
|
||||
:remove_share_visibilities_on_contacts_posts,
|
||||
:disconnect_contacts,
|
||||
:tombstone_user]
|
||||
|
||||
person_removal_methods = [:delete_contacts_of_me,
|
||||
:delete_standard_person_associations,
|
||||
:tombstone_person_and_profile,
|
||||
:remove_share_visibilities_on_persons_posts,
|
||||
:remove_conversation_visibilities]
|
||||
|
||||
context "user deletion" do
|
||||
after do
|
||||
@account_deletion.perform!
|
||||
end
|
||||
|
||||
(user_removal_methods + person_removal_methods).each do |method|
|
||||
|
||||
it "calls ##{method.to_s}" do
|
||||
@account_deletion.should_receive(method)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "person deletion" do
|
||||
before do
|
||||
@person_deletion = AccountDeleter.new(remote_raphael.diaspora_handle)
|
||||
end
|
||||
|
||||
after do
|
||||
@person_deletion.perform!
|
||||
end
|
||||
|
||||
(user_removal_methods).each do |method|
|
||||
|
||||
it "does not call ##{method.to_s}" do
|
||||
@person_deletion.should_not_receive(method)
|
||||
end
|
||||
end
|
||||
|
||||
(person_removal_methods).each do |method|
|
||||
|
||||
it "calls ##{method.to_s}" do
|
||||
@person_deletion.should_receive(method)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "#delete_standard_user_associations" do
|
||||
it 'removes all standard user associaltions' do
|
||||
@account_deletion.normal_ar_user_associates_to_delete.each do |asso|
|
||||
association_mock = mock
|
||||
association_mock.should_receive(:delete)
|
||||
bob.should_receive(asso).and_return([association_mock])
|
||||
end
|
||||
|
||||
@account_deletion.delete_standard_user_associations
|
||||
end
|
||||
end
|
||||
|
||||
describe "#delete_standard_person_associations" do
|
||||
before do
|
||||
@account_deletion.person = bob.person
|
||||
end
|
||||
it 'removes all standard person associaltions' do
|
||||
@account_deletion.normal_ar_person_associates_to_delete.each do |asso|
|
||||
association_mock = mock
|
||||
association_mock.should_receive(:delete_all)
|
||||
bob.person.should_receive(asso).and_return(association_mock)
|
||||
end
|
||||
|
||||
@account_deletion.delete_standard_person_associations
|
||||
end
|
||||
end
|
||||
|
||||
describe "#disassociate_invitations" do
|
||||
it "sets invitations_from_me to be admin invitations" do
|
||||
invites = [mock]
|
||||
bob.stub(:invitations_from_me).and_return(invites)
|
||||
invites.first.should_receive(:convert_to_admin!)
|
||||
@account_deletion.disassociate_invitations
|
||||
end
|
||||
end
|
||||
|
||||
context 'person associations' do
|
||||
describe '#disconnect_contacts' do
|
||||
it "deletes all of user's contacts" do
|
||||
bob.contacts.should_receive(:destroy_all)
|
||||
@account_deletion.disconnect_contacts
|
||||
end
|
||||
end
|
||||
|
||||
describe '#delete_contacts_of_me' do
|
||||
it 'deletes all the local contact objects where deleted account is the person' do
|
||||
contacts = mock
|
||||
Contact.should_receive(:all_contacts_of_person).with(bob.person).and_return(contacts)
|
||||
contacts.should_receive(:destroy_all)
|
||||
@account_deletion.delete_contacts_of_me
|
||||
end
|
||||
end
|
||||
|
||||
describe '#tombstone_person_and_profile' do
|
||||
it 'calls clear_profile! on person' do
|
||||
@account_deletion.person.should_receive(:clear_profile!)
|
||||
@account_deletion.tombstone_person_and_profile
|
||||
end
|
||||
|
||||
it 'calls lock_access! on person' do
|
||||
@account_deletion.person.should_receive(:lock_access!)
|
||||
@account_deletion.tombstone_person_and_profile
|
||||
end
|
||||
end
|
||||
describe "#remove_conversation_visibilities" do
|
||||
it "removes the conversation visibility for the deleted user" do
|
||||
vis = stub
|
||||
ConversationVisibility.should_receive(:where).with(hash_including(:person_id => bob.person.id)).and_return(vis)
|
||||
vis.should_receive(:destroy_all)
|
||||
@account_deletion.remove_conversation_visibilities
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#remove_person_share_visibilities" do
|
||||
it 'removes the share visibilities for a person ' do
|
||||
@s_vis = stub
|
||||
ShareVisibility.should_receive(:for_contacts_of_a_person).with(bob.person).and_return(@s_vis)
|
||||
@s_vis.should_receive(:destroy_all)
|
||||
|
||||
@account_deletion.remove_share_visibilities_on_persons_posts
|
||||
end
|
||||
end
|
||||
|
||||
describe "#remove_share_visibilities_by_contacts_of_user" do
|
||||
it 'removes the share visibilities for a user' do
|
||||
@s_vis = stub
|
||||
ShareVisibility.should_receive(:for_a_users_contacts).with(bob).and_return(@s_vis)
|
||||
@s_vis.should_receive(:destroy_all)
|
||||
|
||||
@account_deletion.remove_share_visibilities_on_contacts_posts
|
||||
end
|
||||
end
|
||||
|
||||
describe "#tombstone_user" do
|
||||
it 'calls strip_model on user' do
|
||||
bob.should_receive(:clear_account!)
|
||||
@account_deletion.tombstone_user
|
||||
end
|
||||
end
|
||||
|
||||
it 'has all user association keys accounted for' do
|
||||
all_keys = (@account_deletion.normal_ar_user_associates_to_delete + @account_deletion.special_ar_user_associations + @account_deletion.ignored_ar_user_associations)
|
||||
all_keys.sort{|x, y| x.to_s <=> y.to_s}.should == User.reflections.keys.sort{|x, y| x.to_s <=> y.to_s}
|
||||
end
|
||||
|
||||
it 'has all person association keys accounted for' do
|
||||
all_keys = (@account_deletion.normal_ar_person_associates_to_delete + @account_deletion.ignored_or_special_ar_person_associations)
|
||||
all_keys.sort{|x, y| x.to_s <=> y.to_s}.should == Person.reflections.keys.sort{|x, y| x.to_s <=> y.to_s}
|
||||
end
|
||||
end
|
||||
|
||||
86
spec/models/account_deletion_spec.rb
Normal file
86
spec/models/account_deletion_spec.rb
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
# Copyright (c) 2010-2011, Diaspora Inc. This file is
|
||||
# licensed under the Affero General Public License version 3 or later. See
|
||||
# the COPYRIGHT file.
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe AccountDeletion do
|
||||
it 'assigns the diaspora_handle from the person object' do
|
||||
a = AccountDeletion.new(:person => alice.person)
|
||||
a.diaspora_handle.should == alice.person.diaspora_handle
|
||||
end
|
||||
|
||||
it 'fires a resque job after creation'do
|
||||
Resque.should_receive(:enqueue).with(Jobs::DeleteAccount, anything)
|
||||
|
||||
AccountDeletion.create(:person => alice.person)
|
||||
end
|
||||
|
||||
describe "#perform!" do
|
||||
before do
|
||||
@ad = AccountDeletion.new(:person => alice.person)
|
||||
end
|
||||
|
||||
it 'creates a deleter' do
|
||||
AccountDeleter.should_receive(:new).with(alice.person.diaspora_handle).and_return(stub(:perform! => true))
|
||||
@ad.perform!
|
||||
end
|
||||
|
||||
it 'dispatches the account deletion if the user exists' do
|
||||
@ad.should_receive(:dispatch)
|
||||
@ad.perform!
|
||||
end
|
||||
|
||||
it 'does not dispatch an account deletion for non-local people' do
|
||||
deletion = AccountDeletion.new(:person => remote_raphael)
|
||||
deletion.should_not_receive(:dispatch)
|
||||
deletion.perform!
|
||||
end
|
||||
end
|
||||
|
||||
describe '#dispatch' do
|
||||
it "sends the account deletion xml" do
|
||||
@ad = AccountDeletion.new(:person => alice.person)
|
||||
@ad.send(:dispatch)
|
||||
end
|
||||
|
||||
it 'creates a public postzord' do
|
||||
Postzord::Dispatcher::Public.should_receive(:new).and_return(stub.as_null_object)
|
||||
@ad = AccountDeletion.new(:person => alice.person)
|
||||
@ad.send(:dispatch)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#subscribers" do
|
||||
it 'includes all remote contacts' do
|
||||
@ad = AccountDeletion.new(:person => alice.person)
|
||||
alice.share_with(remote_raphael, alice.aspects.first)
|
||||
|
||||
@ad.subscribers(alice).should == [remote_raphael]
|
||||
end
|
||||
|
||||
it 'includes remote resharers' do
|
||||
@ad = AccountDeletion.new(:person => alice.person)
|
||||
sm = Factory( :status_message, :public => true, :author => alice.person)
|
||||
r1 = Factory( :reshare, :author => remote_raphael, :root => sm)
|
||||
r2 = Factory( :reshare, :author => local_luke.person, :root => sm)
|
||||
|
||||
@ad.subscribers(alice).should == [remote_raphael]
|
||||
end
|
||||
end
|
||||
|
||||
describe 'serialization' do
|
||||
before do
|
||||
account_deletion = AccountDeletion.new(:person => alice.person)
|
||||
@xml = account_deletion.to_xml.to_s
|
||||
end
|
||||
|
||||
it 'should have a diaspora_handle' do
|
||||
@xml.include?(alice.person.diaspora_handle).should == true
|
||||
end
|
||||
|
||||
it 'marshals the xml' do
|
||||
AccountDeletion.from_xml(@xml).should be_valid
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -44,6 +44,15 @@ describe Contact do
|
|||
contact.person = person
|
||||
contact.should_not be_valid
|
||||
end
|
||||
|
||||
it "validates that the person's account is not closed" do
|
||||
person = Factory.create(:person, :closed_account => true)
|
||||
|
||||
contact = alice.contacts.new(:person=>person)
|
||||
|
||||
contact.should_not be_valid
|
||||
contact.errors.full_messages.should include "Cannot be in contact with a closed account"
|
||||
end
|
||||
end
|
||||
|
||||
context 'scope' do
|
||||
|
|
@ -81,6 +90,16 @@ describe Contact do
|
|||
}.by(2)
|
||||
end
|
||||
end
|
||||
|
||||
describe "all_contacts_of_person" do
|
||||
it 'returns all contacts where the person is the passed in person' do
|
||||
person = Factory.create(:person)
|
||||
contact1 = Factory(:contact, :person => person)
|
||||
contact2 = Factory(:contact)
|
||||
contacts = Contact.all_contacts_of_person(person)
|
||||
contacts.should == [contact1]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#contacts' do
|
||||
|
|
|
|||
|
|
@ -75,6 +75,17 @@ describe Invitation do
|
|||
}.should_not change(User, :count)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#convert_to_admin!' do
|
||||
it 'reset sender and aspect to nil, and sets admin flag to true' do
|
||||
invite = Factory(:invitation)
|
||||
invite.convert_to_admin!
|
||||
invite.reload
|
||||
invite.admin?.should be_true
|
||||
invite.sender_id.should be_nil
|
||||
invite.aspect_id.should be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe '.batch_invite' do
|
||||
before do
|
||||
|
|
|
|||
|
|
@ -6,25 +6,12 @@ require 'spec_helper'
|
|||
|
||||
describe Jobs::DeleteAccount do
|
||||
describe '#perform' do
|
||||
it 'calls remove_all_traces' do
|
||||
stub_find_for(bob)
|
||||
bob.should_receive(:remove_all_traces)
|
||||
Jobs::DeleteAccount.perform(bob.id)
|
||||
end
|
||||
|
||||
it 'calls destroy' do
|
||||
stub_find_for(bob)
|
||||
bob.should_receive(:destroy)
|
||||
Jobs::DeleteAccount.perform(bob.id)
|
||||
end
|
||||
def stub_find_for model
|
||||
model.class.stub!(:find) do |id, conditions|
|
||||
if id == model.id
|
||||
model
|
||||
else
|
||||
model.class.find_by_id(id)
|
||||
end
|
||||
end
|
||||
it 'performs the account deletion' do
|
||||
account_deletion = stub
|
||||
AccountDeletion.stub(:find).and_return(account_deletion)
|
||||
account_deletion.should_receive(:perform!)
|
||||
|
||||
Jobs::DeleteAccount.perform(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -91,6 +91,14 @@ describe Person do
|
|||
Person.all_from_aspects(aspect_ids, bob).map(&:id).should == []
|
||||
end
|
||||
end
|
||||
|
||||
describe ".who_have_reshared a user's posts" do
|
||||
it 'pulls back users who reshared the status message of a user' do
|
||||
sm = Factory.create(:status_message, :author => alice.person, :public => true)
|
||||
reshare = Factory.create(:reshare, :root => sm)
|
||||
Person.who_have_reshared_a_users_posts(alice).should == [reshare.author]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "delegating" do
|
||||
|
|
@ -536,4 +544,22 @@ describe Person do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#lock_access!' do
|
||||
it 'sets the closed_account flag' do
|
||||
@person.lock_access!
|
||||
@person.reload.closed_account.should be_true
|
||||
end
|
||||
end
|
||||
|
||||
describe "#clear_profile!!" do
|
||||
before do
|
||||
@person = Factory(:person)
|
||||
end
|
||||
|
||||
it 'calls Profile#tombstone!' do
|
||||
@person.profile.should_receive(:tombstone!)
|
||||
@person.clear_profile!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -264,7 +264,6 @@ describe Profile do
|
|||
end
|
||||
|
||||
describe '#receive' do
|
||||
|
||||
it 'updates the profile in place' do
|
||||
local_luke, local_leia, remote_raphael = set_up_friends
|
||||
new_profile = Factory.build :profile
|
||||
|
|
@ -275,4 +274,43 @@ describe Profile do
|
|||
end
|
||||
|
||||
end
|
||||
|
||||
describe "#tombstone!" do
|
||||
before do
|
||||
@profile = bob.person.profile
|
||||
end
|
||||
it "clears the profile fields" do
|
||||
attributes = @profile.send(:clearable_fields)
|
||||
|
||||
@profile.tombstone!
|
||||
@profile.reload
|
||||
attributes.each{ |attr|
|
||||
@profile[attr.to_sym].should be_blank
|
||||
}
|
||||
end
|
||||
|
||||
it 'removes all the tags from the profile' do
|
||||
@profile.taggings.should_receive(:delete_all)
|
||||
@profile.tombstone!
|
||||
end
|
||||
end
|
||||
|
||||
describe "#clearable_fields" do
|
||||
it 'returns the current profile fields' do
|
||||
profile = Factory.build :profile
|
||||
profile.send(:clearable_fields).sort.should ==
|
||||
["diaspora_handle",
|
||||
"first_name",
|
||||
"last_name",
|
||||
"image_url",
|
||||
"image_url_small",
|
||||
"image_url_medium",
|
||||
"birthday",
|
||||
"gender",
|
||||
"bio",
|
||||
"searchable",
|
||||
"location",
|
||||
"full_name"].sort
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -25,5 +25,28 @@ describe ShareVisibility do
|
|||
ShareVisibility.batch_import([@contact.id], @post)
|
||||
}.should_not raise_error
|
||||
end
|
||||
|
||||
context "scopes" do
|
||||
describe '.for_a_users_contacts' do
|
||||
before do
|
||||
alice.post(:status_message, :text => "Hey", :to => alice.aspects.first)
|
||||
end
|
||||
|
||||
it 'searches for share visibilies for all users contacts' do
|
||||
contact_ids = alice.contacts.map{|c| c.id}
|
||||
ShareVisibility.for_a_users_contacts(alice).should == ShareVisibility.where(:contact_id => contact_ids).all
|
||||
end
|
||||
end
|
||||
|
||||
describe '.for_contacts_of_a_person' do
|
||||
it 'searches for share visibilties generated by a person' do
|
||||
|
||||
contact_ids = alice.person.contacts.map{|c| c.id}
|
||||
|
||||
ShareVisibility.for_contacts_of_a_person(alice.person) == ShareVisibility.where(:contact_id => contact_ids).all
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -66,6 +66,15 @@ describe StatusMessage do
|
|||
end
|
||||
end
|
||||
|
||||
describe ".guids_for_author" do
|
||||
it 'returns an array of the status_message guids' do
|
||||
sm1 = Factory(:status_message, :author => alice.person)
|
||||
sm2 = Factory(:status_message, :author => bob.person)
|
||||
guids = StatusMessage.guids_for_author(alice.person)
|
||||
guids.should == [sm1.guid]
|
||||
end
|
||||
end
|
||||
|
||||
describe '.before_create' do
|
||||
it 'calls build_tags' do
|
||||
status = Factory.build(:status_message)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ require 'spec_helper'
|
|||
|
||||
describe TagFollowing do
|
||||
before do
|
||||
@tag = ActsAsTaggableOn::Tag.create(:name => "partytimeexcellent")
|
||||
@tag = Factory.create(:tag)
|
||||
TagFollowing.create!(:tag => @tag, :user => alice)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -625,7 +625,8 @@ describe User do
|
|||
|
||||
describe '#disconnect_everyone' do
|
||||
it 'has no error on a local friend who has deleted his account' do
|
||||
Jobs::DeleteAccount.perform(alice.id)
|
||||
d = Factory(:account_deletion, :person => alice.person)
|
||||
Jobs::DeleteAccount.perform(d.id)
|
||||
lambda {
|
||||
bob.disconnect_everyone
|
||||
}.should_not raise_error
|
||||
|
|
@ -907,6 +908,7 @@ describe User do
|
|||
fantasy_resque do
|
||||
@invitation = Factory.create(:invitation, :sender => eve, :identifier => 'invitee@example.org', :aspect => eve.aspects.first)
|
||||
end
|
||||
|
||||
@invitation.reload
|
||||
@form_params = {
|
||||
:invitation_token => "abc",
|
||||
|
|
@ -1005,4 +1007,79 @@ describe User do
|
|||
user.send_reset_password_instructions
|
||||
end
|
||||
end
|
||||
|
||||
context "close account" do
|
||||
before do
|
||||
@user = bob
|
||||
end
|
||||
|
||||
describe "#close_account!" do
|
||||
it 'locks the user out' do
|
||||
@user.close_account!
|
||||
@user.reload.access_locked?.should be_true
|
||||
end
|
||||
|
||||
it 'creates an account deletion' do
|
||||
expect{
|
||||
@user.close_account!
|
||||
}.to change(AccountDeletion, :count).by(1)
|
||||
end
|
||||
|
||||
it 'calls person#lock_access!' do
|
||||
@user.person.should_receive(:lock_access!)
|
||||
@user.close_account!
|
||||
end
|
||||
end
|
||||
|
||||
describe "#clear_account!" do
|
||||
it 'resets the password to a random string' do
|
||||
random_pass = "12345678909876543210"
|
||||
ActiveSupport::SecureRandom.should_receive(:hex).and_return(random_pass)
|
||||
@user.clear_account!
|
||||
@user.valid_password?(random_pass)
|
||||
end
|
||||
|
||||
it 'clears all the clearable fields' do
|
||||
@user.reload
|
||||
attributes = @user.send(:clearable_fields)
|
||||
@user.clear_account!
|
||||
|
||||
@user.reload
|
||||
attributes.each do |attr|
|
||||
@user.send(attr.to_sym).should be_blank
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#clearable_attributes" do
|
||||
it 'returns the clearable fields' do
|
||||
user = Factory.create :user
|
||||
user.send(:clearable_fields).sort.should == %w{
|
||||
getting_started
|
||||
disable_mail
|
||||
language
|
||||
email
|
||||
invitation_token
|
||||
invitation_sent_at
|
||||
reset_password_token
|
||||
remember_token
|
||||
remember_created_at
|
||||
sign_in_count
|
||||
current_sign_in_at
|
||||
last_sign_in_at
|
||||
current_sign_in_ip
|
||||
last_sign_in_ip
|
||||
invitation_service
|
||||
invitation_identifier
|
||||
invitation_limit
|
||||
invited_by_id
|
||||
invited_by_type
|
||||
authentication_token
|
||||
unconfirmed_email
|
||||
confirm_email_token
|
||||
show_community_spotlight_in_stream
|
||||
}.sort
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
42
spec/shared_behaviors/account_deletion.rb
Normal file
42
spec/shared_behaviors/account_deletion.rb
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
# Copyright (c) 2010-2011, Diaspora Inc. This file is
|
||||
# licensed under the Affero General Public License version 3 or later. See
|
||||
# the COPYRIGHT file.
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe 'deleteing your account' do
|
||||
shared_examples_for 'it removes the person associations' do
|
||||
it "removes all of the person's posts" do
|
||||
Post.where(:author_id => @person.id).count.should == 0
|
||||
end
|
||||
|
||||
it 'deletes all person contacts' do
|
||||
Contact.where(:person_id => @person.id).should be_empty
|
||||
end
|
||||
|
||||
it 'deletes all mentions' do
|
||||
@person.mentions.should be_empty
|
||||
end
|
||||
|
||||
it "removes all of the person's photos" do
|
||||
Photo.where(:author_id => @person.id).should be_empty
|
||||
end
|
||||
|
||||
it 'sets the person object as closed and the profile is cleared' do
|
||||
@person.reload.closed_account.should be_true
|
||||
|
||||
@person.profile.reload.first_name.should be_blank
|
||||
@person.profile.reload.last_name.should be_blank
|
||||
end
|
||||
|
||||
it 'deletes only the converersation visibility for the deleted user' do
|
||||
ConversationVisibility.where(:person_id => alice.person.id).should_not be_empty
|
||||
ConversationVisibility.where(:person_id => @person.id).should be_empty
|
||||
end
|
||||
|
||||
it "deletes the share visibilities on the person's posts" do
|
||||
ShareVisibility.for_contacts_of_a_person(@person).should be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Loading…
Reference in a new issue