Unverified Commit b2fa3357 authored by Jonne Haß's avatar Jonne Haß

Merge pull request #6976 from SuperTux88/cleanup-invitations

Cleanup invitations
parents c28865e0 bc6c8a05
......@@ -138,6 +138,7 @@ The command will report queues that still have jobs and launch sidekiq process f
* Remove some unused routes [#6781](https://github.com/diaspora/diaspora/pull/6781)
* Consolidate sidekiq queues [#6950](https://github.com/diaspora/diaspora/pull/6950)
* Don't re-render the whole comment stream when adding comments [#6406](https://github.com/diaspora/diaspora/pull/6406)
* Drop legacy invitation system [#6976](https://github.com/diaspora/diaspora/pull/6976)
## Bug fixes
* Destroy Participation when removing interactions with a post [#5852](https://github.com/diaspora/diaspora/pull/5852)
......
......@@ -3,8 +3,8 @@
# the COPYRIGHT file.
class InvitationsController < ApplicationController
before_action :authenticate_user!, :only => [:new, :create]
before_action :authenticate_user!
before_action :check_invitations_available!, only: :create
def new
@invite_code = current_user.invitation_code
......@@ -14,79 +14,50 @@ class InvitationsController < ApplicationController
respond_to do |format|
format.html do
render 'invitations/new', layout: false
render "invitations/new", layout: false
end
end
end
# this is for legacy invites. We try to look the person who sent them the
# invite, and use their new invite code
# owe will be removing this eventually
# @depreciated
def edit
user = User.find_by_invitation_token(params[:invitation_token])
invitation_code = user.ugly_accept_invitation_code
redirect_to invite_code_path(invitation_code)
end
def email
@invitation_code =
if params[:invitation_token]
# this is for legacy invites.
user = User.find_by_invitation_token(params[:invitation_token])
user.ugly_accept_invitation_code if user
else
params[:invitation_code]
end
@inviter = user || InvitationCode.where(id: params[:invitation_code]).first.try(:user)
if @invitation_code.present?
render 'notifier/invite', :layout => false
else
flash[:error] = t('invitations.check_token.not_found')
redirect_to root_url
end
end
def create
emails = inviter_params[:emails].split(',').map(&:strip).uniq
emails = inviter_params[:emails].split(",").map(&:strip).uniq
valid_emails, invalid_emails = emails.partition { |email| valid_email?(email) }
valid_emails, invalid_emails = emails.partition {|email| valid_email?(email) }
session[:valid_email_invites] = valid_emails
session[:invalid_email_invites] = invalid_emails
unless valid_emails.empty?
Workers::Mail::InviteEmail.perform_async(valid_emails.join(','),
current_user.id,
inviter_params)
Workers::Mail::InviteEmail.perform_async(valid_emails.join(","), current_user.id, inviter_params)
end
if emails.empty?
flash[:error] = t('invitations.create.empty')
flash[:error] = t("invitations.create.empty")
elsif invalid_emails.empty?
flash[:notice] = t('invitations.create.sent', :emails => valid_emails.join(', '))
flash[:notice] = t("invitations.create.sent", emails: valid_emails.join(", "))
elsif valid_emails.empty?
flash[:error] = t('invitations.create.rejected') + invalid_emails.join(', ')
flash[:error] = t("invitations.create.rejected", emails: invalid_emails.join(", "))
else
flash[:error] = t('invitations.create.sent', :emails => valid_emails.join(', '))
flash[:error] << '. '
flash[:error] << t('invitations.create.rejected') + invalid_emails.join(', ')
flash[:error] = t("invitations.create.sent", emails: valid_emails.join(", ")) + ". " +
t("invitations.create.rejected", emails: invalid_emails.join(", "))
end
redirect_to :back
end
def check_if_invites_open
unless AppConfig.settings.invitations.open?
flash[:error] = I18n.t 'invitations.create.no_more'
private
redirect_to :back
end
def check_invitations_available!
return true if AppConfig.settings.enable_registrations? || current_user.invitation_code.can_be_used?
flash[:error] = if AppConfig.settings.invitations.open?
t("invitations.create.no_more")
else
t("invitations.create.closed")
end
redirect_to :back
end
private
def valid_email?(email)
User.email_regexp.match(email).present?
end
......@@ -94,9 +65,9 @@ class InvitationsController < ApplicationController
def html_safe_string_from_session_array(key)
return "" unless session[key].present?
return "" unless session[key].respond_to?(:join)
value = session[key].join(', ').html_safe
value = session[key].join(", ").html_safe
session[key] = nil
return value
value
end
def inviter_params
......
......@@ -3,16 +3,16 @@
# the COPYRIGHT file.
class RegistrationsController < Devise::RegistrationsController
before_action :check_registrations_open_or_valid_invite!, :check_valid_invite!
before_action :check_registrations_open_or_valid_invite!
layout ->(c) { request.format == :mobile ? "application" : "with_header" }, :only => [:new]
layout -> { request.format == :mobile ? "application" : "with_header" }
def create
@user = User.build(user_params)
@user.process_invite_acceptence(invite) if invite.present?
if @user.sign_up
flash[:notice] = I18n.t 'registrations.create.success'
flash[:notice] = t("registrations.create.success")
@user.process_invite_acceptence(invite) if invite.present?
@user.seed_aspects
@user.send_welcome_message
sign_in_and_redirect(:user, @user)
......@@ -22,40 +22,30 @@ class RegistrationsController < Devise::RegistrationsController
flash.now[:error] = @user.errors.full_messages.join(" - ")
logger.info "event=registration status=failure errors='#{@user.errors.full_messages.join(', ')}'"
render action: "new", layout: request.format == :mobile ? "application" : "with_header"
render action: "new"
end
end
def new
super
end
private
def check_valid_invite!
return true if AppConfig.settings.enable_registrations? #this sucks
return true if invite && invite.can_be_used?
flash[:error] = t('registrations.invalid_invite')
redirect_to new_user_session_path
end
def check_registrations_open_or_valid_invite!
return true if invite.present?
unless AppConfig.settings.enable_registrations?
flash[:error] = t('registrations.closed')
redirect_to new_user_session_path
end
return true if AppConfig.settings.enable_registrations? || invite.try(:can_be_used?)
flash[:error] = params[:invite] ? t("registrations.invalid_invite") : t("registrations.closed")
redirect_to new_user_session_path
end
def invite
if params[:invite].present?
@invite ||= InvitationCode.find_by_token(params[:invite][:token])
end
@invite ||= InvitationCode.find_by_token(params[:invite][:token]) if params[:invite].present?
end
helper_method :invite
def user_params
params.require(:user).permit(:username, :email, :getting_started, :password, :password_confirmation, :language, :disable_mail, :invitation_service, :invitation_identifier, :show_community_spotlight_in_stream, :auto_follow_back, :auto_follow_back_aspect_id, :remember_me, :captcha, :captcha_key)
params.require(:user).permit(
:username, :email, :getting_started, :password, :password_confirmation, :language, :disable_mail,
:show_community_spotlight_in_stream, :auto_follow_back, :auto_follow_back_aspect_id,
:remember_me, :captcha, :captcha_key
)
end
end
# Copyright (c) 2010-2011, Diaspora Inc. This file is
# licensed under the Affero General Public License version 3 or later. See
# the COPYRIGHT file.
#TODO: kill me
class Invitation < ActiveRecord::Base
belongs_to :sender, :class_name => 'User'
belongs_to :recipient, :class_name => 'User'
belongs_to :aspect
before_validation :set_email_as_default_service
# before_create :share_with_exsisting_user, :if => :recipient_id?
validates :identifier, :presence => true
validates :service, :presence => true
validate :valid_identifier?
validate :recipient_not_on_pod?
validates_presence_of :sender, :aspect, :unless => :admin?
validate :ensure_not_inviting_self, :on => :create, :unless => :admin?
validate :sender_owns_aspect?, :unless => :admin?
validates_uniqueness_of :sender_id, :scope => [:identifier, :service], :unless => :admin?
# @note options hash is passed through to [Invitation.new]
# @see [Invitation.new]
#
# @param [Array<String>] emails
# @option opts [User] :sender
# @option opts [Aspect] :aspect
# @option opts [String] :service
# @return [Array<Invitation>] An array of [Invitation] models
# the valid optsnes are saved, and the invalid ones are not.
def self.batch_invite(emails, opts)
users_on_pod = User.where(:email => emails, :invitation_token => nil)
#share with anyone whose email you entered who is on the pod
users_on_pod.each{|u| opts[:sender].share_with(u.person, opts[:aspect])}
emails.map! do |e|
user = users_on_pod.find{|u| u.email == e}
Invitation.create(opts.merge(:identifier => e, :recipient => user))
end
emails
end
# Downcases the incoming service identifier and assigns it
#
# @param ident [String] Service identifier
# @see super
def identifier=(ident)
ident.downcase! if ident
super
end
# Determine if we want to skip emailing the recipient.
#
# @return [Boolean]
# @return [void]
def skip_email?
!email_like_identifer
end
# Find or create user, and send that resultant User an
# invitation.
#
# @return [Invitation] self
def send!
if email_like_identifer
EmailInviter.new(self.identifier, sender).send!
else
puts "broken facebook invitation_token"
end
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!
end
# @return [String]
def recipient_identifier
case self.service
when 'email'
self.identifier
when'facebook'
I18n.t('invitations.a_facebook_user')
end
end
# @return [String]
def email_like_identifer
case self.service
when 'email'
self.identifier
when 'facebook'
false
end
end
# @note before_save
def set_email_as_default_service
self.service ||= 'email'
end
# @note Validation
def ensure_not_inviting_self
if self.identifier == self.sender.email
errors[:base] << 'You can not invite yourself.'
end
end
# @note Validation
def sender_owns_aspect?
if self.sender_id != self.aspect.user_id
errors[:base] << 'You do not own that aspect.'
end
end
def recipient_not_on_pod?
return true if self.recipient.nil?
if self.recipient.username?
errors[:recipient] << "The user '#{self.identifier}' (#{self.recipient.diaspora_handle}) is already on this pod, so we sent them a share request"
end
end
# @note Validation
def valid_identifier?
return false unless self.identifier
if self.service == 'email'
unless self.identifier.match(Devise.email_regexp)
errors[:base] << 'invalid email'
end
end
end
end
......@@ -6,13 +6,13 @@ class InvitationCode < ActiveRecord::Base
before_create :generate_token, :set_default_invite_count
delegate :name, to: :user, prefix: true
def to_param
token
token
end
def can_be_used?
self.count > 0
count > 0 && AppConfig.settings.invitations.open?
end
def add_invites!
......
......@@ -14,7 +14,7 @@ class User < ActiveRecord::Base
scope :daily_actives, ->(time = Time.now) { logged_in_since(time - 1.day) }
scope :yearly_actives, ->(time = Time.now) { logged_in_since(time - 1.year) }
scope :halfyear_actives, ->(time = Time.now) { logged_in_since(time - 6.month) }
scope :active, -> { joins(:person).where(people: {closed_account: false}).where.not(username: nil) }
scope :active, -> { joins(:person).where(people: {closed_account: false}) }
devise :token_authenticatable, :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
......@@ -34,7 +34,7 @@ class User < ActiveRecord::Base
validate :unconfirmed_email_quasiuniqueness
validates_presence_of :person, :unless => proc {|user| user.invitation_token.present?}
validates :person, presence: true
validates_associated :person
validate :no_person_with_same_username
......@@ -48,8 +48,6 @@ class User < ActiveRecord::Base
:first_name, :last_name, :gender, :participations, to: :person
delegate :id, :guid, to: :person, prefix: true
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') }
belongs_to :auto_follow_back_aspect, :class_name => 'Aspect'
......@@ -99,21 +97,11 @@ class User < ActiveRecord::Base
ConversationVisibility.where(person_id: self.person_id).sum(:unread)
end
#@deprecated
def ugly_accept_invitation_code
begin
self.invitations_to_me.first.sender.invitation_code
rescue Exception => e
nil
end
end
def process_invite_acceptence(invite)
self.invited_by = invite.user
invite.use!
invite.use! unless AppConfig.settings.enable_registrations?
end
def invitation_code
InvitationCode.find_or_create_by(user_id: self.id)
end
......
#paste_link
= t('.paste_link')
%span#codes_left
= '(' + t('.codes_left', count: @invite_code.count) + ')'
= "(" + t(".codes_left", count: @invite_code.count) + ")" unless AppConfig.settings.enable_registrations?
.form-horizontal
.control-group
= invite_link(@invite_code)
......@@ -15,8 +15,8 @@
.col-sm-10
= text_field_tag 'email_inviter[emails]', @invalid_emails, title: t('.comma_separated_plz'),
placeholder: 'foo@bar.com, max@foo.com...', class: "form-control"
#already_sent
= t('invitations.create.note_already_sent', emails: @valid_emails) unless @valid_emails.empty?
#already_sent
= t("invitations.create.note_already_sent", emails: @valid_emails) unless @valid_emails.empty?
.form-group
%label.col-sm-2.control-label{ for: 'email_inviter_locale' }
......
# Copyright (c) 2010-2011, Diaspora Inc. This file is
# licensed under the Affero General Public License version 3 or later. See
# the COPYRIGHT file.
module Workers
class ResendInvitation < Base
sidekiq_options queue: :low
def perform(invitation_id)
inv = Invitation.find(invitation_id)
inv.resend
end
end
end
......@@ -556,10 +556,11 @@ en:
invitations:
create:
sent: "Invitations have been sent to: %{emails}"
rejected: "The following email addresses had problems: "
rejected: "The following email addresses had problems: %{emails}"
no_more: "You have no more invitations."
empty: "Please enter at least one email address."
note_already_sent: "Invitations have already been sent to: %{emails}"
closed: "Invitations are closed on this diaspora* pod."
new:
language: "Language"
invite_someone_to_join: "Invite someone to join diaspora*!"
......@@ -571,9 +572,6 @@ en:
zero: "No invites left on this code"
one: "One invite left on this code"
other: "%{count} invites left on this code"
check_token:
not_found: "Invitation token not found"
a_facebook_user: "A Facebook user"
layouts:
header:
......
......@@ -122,11 +122,8 @@ Diaspora::Application.routes.draw do
post "/users" => "registrations#create", :as => :user_registration
end
#legacy routes to support old invite routes
get 'users/invitation/accept' => 'invitations#edit'
get 'invitations/email' => 'invitations#email', :as => 'invite_email'
get 'users/invitations' => 'invitations#new', :as => 'new_user_invitation'
post 'users/invitations' => 'invitations#create', :as => 'user_invitation'
get "users/invitations" => "invitations#new", :as => "new_user_invitation"
post "users/invitations" => "invitations#create", :as => "user_invitation"
get 'login' => redirect('/users/sign_in')
......
class CleanupInvitationColumnsFromUsers < ActiveRecord::Migration
class InvitationCode < ActiveRecord::Base
end
class User < ActiveRecord::Base
end
def change
remove_index :users, column: %i(invitation_service invitation_identifier),
name: :index_users_on_invitation_service_and_invitation_identifier,
unique: true, length: {invitation_service: 64}
remove_index :users, column: :invitation_token, name: :index_users_on_invitation_token
remove_index :users, column: :email, name: :index_users_on_email, length: 191
username_not_null
remove_column :users, :invitation_token, :string, limit: 60
remove_column :users, :invitation_sent_at, :datetime
remove_column :users, :invitation_service, :string, limit: 127
remove_column :users, :invitation_identifier, :string, limit: 127
remove_column :users, :invitation_limit, :integer
remove_column :users, :invited_by_type, :string
add_index :users, :email, name: :index_users_on_email, unique: true, length: 191
cleanup_invitations
end
def username_not_null
reversible do |dir|
dir.up do
User.delete_all(username: nil)
change_column :users, :username, :string, null: false
end
dir.down do
change_column :users, :username, :string, null: true
end
end
end
def cleanup_invitations
reversible do |dir|
dir.up do
drop_table :invitations
# reset negative invitation counters
new_counter = AppConfig.settings.enable_registrations? ? AppConfig["settings.invitations.count"] : 0
InvitationCode.where("count < 0").update_all(count: new_counter)
end
dir.down do
create_invitations_table
end
end
end
def create_invitations_table
# rubocop:disable Style/ExtraSpacing
create_table :invitations, force: :cascade do |t|
t.text :message, limit: 65_535
t.integer :sender_id, limit: 4
t.integer :recipient_id, limit: 4
t.integer :aspect_id, limit: 4
t.datetime :created_at, null: false
t.datetime :updated_at, null: false
t.string :service, limit: 255
t.string :identifier, limit: 255
t.boolean :admin, default: false
t.string :language, limit: 255, default: "en"
end
# rubocop:enable Style/ExtraSpacing
add_index :invitations, :aspect_id, name: :index_invitations_on_aspect_id, using: :btree
add_index :invitations, :recipient_id, name: :index_invitations_on_recipient_id, using: :btree
add_index :invitations, :sender_id, name: :index_invitations_on_sender_id, using: :btree
add_foreign_key :invitations, :users, column: :recipient_id, name: :invitations_recipient_id_fk, on_delete: :cascade
add_foreign_key :invitations, :users, column: :sender_id, name: :invitations_sender_id_fk, on_delete: :cascade
end
end
......@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20160807212443) do
ActiveRecord::Schema.define(version: 20160810230114) do
create_table "account_deletions", force: :cascade do |t|
t.string "diaspora_handle", limit: 255
......@@ -178,23 +178,6 @@ ActiveRecord::Schema.define(version: 20160807212443) do
t.datetime "updated_at", null: false
end
create_table "invitations", force: :cascade do |t|
t.text "message", limit: 65535
t.integer "sender_id", limit: 4
t.integer "recipient_id", limit: 4
t.integer "aspect_id", limit: 4
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "service", limit: 255
t.string "identifier", limit: 255
t.boolean "admin", default: false
t.string "language", limit: 255, default: "en"
end
add_index "invitations", ["aspect_id"], name: "index_invitations_on_aspect_id", using: :btree
add_index "invitations", ["recipient_id"], name: "index_invitations_on_recipient_id", using: :btree
add_index "invitations", ["sender_id"], name: "index_invitations_on_sender_id", using: :btree
create_table "like_signatures", id: false, force: :cascade do |t|
t.integer "like_id", limit: 4, null: false
t.text "author_signature", limit: 65535, null: false
......@@ -624,15 +607,13 @@ ActiveRecord::Schema.define(version: 20160807212443) do
end
create_table "users", force: :cascade do |t|
t.string "username", limit: 255
t.string "username", limit: 255, null: false
t.text "serialized_private_key", limit: 65535
t.boolean "getting_started", default: true, null: false
t.boolean "disable_mail", default: false, null: false
t.string "language", limit: 255
t.string "email", limit: 255, default: "", null: false
t.string "encrypted_password", limit: 255, default: "", null: false
t.string "invitation_token", limit: 60
t.datetime "invitation_sent_at"
t.string "reset_password_token", limit: 255
t.datetime "remember_created_at"
t.integer "sign_in_count", limit: 4, default: 0
......@@ -642,11 +623,7 @@ ActiveRecord::Schema.define(version: 20160807212443) do
t.string "last_sign_in_ip", limit: 255
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "invitation_service", limit: 127
t.string "invitation_identifier", limit: 127
t.integer "invitation_limit", limit: 4
t.integer "invited_by_id", limit: 4
t.string "invited_by_type", limit: 255
t.string "authentication_token", limit: 30
t.string "unconfirmed_email", limit: 255
t.string "confirm_email_token", limit: 30
......@@ -669,9 +646,7 @@ ActiveRecord::Schema.define(version: 20160807212443) do
end
add_index "users", ["authentication_token"], name: "index_users_on_authentication_token", unique: true, using: :btree
add_index "users", ["email"], name: "index_users_on_email", length: {"email"=>191}, using: :btree
add_index "users", ["invitation_service", "invitation_identifier"], name: "index_users_on_invitation_service_and_invitation_identifier", unique: true, length: {"invitation_service"=>64, "invitation_identifier"=>nil}, using: :btree
add_index "users", ["invitation_token"], name: "index_users_on_invitation_token", using: :btree
add_index "users", ["email"], name: "index_users_on_email", unique: true, length: {"email"=>191}, using: :btree
add_index "users", ["username"], name: "index_users_on_username", unique: true, length: {"username"=>191}, using: :btree
add_foreign_key "aspect_memberships", "aspects", name: "aspect_memberships_aspect_id_fk", on_delete: :cascade
......@@ -687,8 +662,6 @@ ActiveRecord::Schema.define(version: 20160807212443) do
add_foreign_key "conversation_visibilities", "people", name: "conversation_visibilities_person_id_fk", on_delete: :cascade
add_foreign_key "conversations", "people", column: "author_id", name: "conversations_author_id_fk", on_delete: :cascade
add_foreign_key "id_tokens", "authorizations"
add_foreign_key "invitations", "users", column: "recipient_id", name: "invitations_recipient_id_fk", on_delete: :cascade
add_foreign_key "invitations", "users", column: "sender_id", name: "invitations_sender_id_fk", on_delete: :cascade
add_foreign_key "like_signatures", "likes", name: "like_signatures_like_id_fk", on_delete: :cascade
add_foreign_key "like_signatures", "signature_orders", name: "like_signatures_signature_orders_id_fk"
add_foreign_key "likes", "people", column: "author_id", name: "likes_author_id_fk", on_delete: :cascade
......
......@@ -47,6 +47,7 @@ Given /^I have been invited by an admin$/ do
end
Given /^I have been invited by "([^\"]+)"$/ do |email|
AppConfig.settings.enable_registrations = false
@inviter = User.find_by_email(email)
@inviter_invite_count = @inviter.invitation_code.count
i = EmailInviter.new("new_invitee@example.com", @inviter)
......
......@@ -70,6 +70,9 @@ Before do |scenario|
page.driver.headers = if scenario.source_tag_names.include? "@mobile"
{"User-Agent" => "Mozilla/5.0 (Mobile; rv:18.0) Gecko/18.0 Firefox/18.0"}
else
page.driver.headers = {}
{}
end
# Reset overridden settings
AppConfig.reset_dynamic!
end
......@@ -34,7 +34,6 @@ class AccountDeleter
#user deletion methods
remove_share_visibilities_on_contacts_posts
delete_standard_user_associations
disassociate_invitations
disconnect_contacts
tombstone_user
end
......@@ -45,12 +44,12 @@ class AccountDeleter
#user deletions
def normal_ar_user_associates_to_delete
%i(tag_followings invitations_to_me services aspects user_preferences
%i(tag_followings services aspects user_preferences
notifications blocks authorizations o_auth_applications pairwise_pseudonymous_identifiers)
end
def special_ar_user_associations
%i(invitations_from_me person profile contacts auto_follow_back_aspect)
%i(person profile contacts auto_follow_back_aspect)
end
def ignored_ar_user_associations
......@@ -70,12 +69,6 @@ class AccountDeleter
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
......