Merge pull request #2538 from techAPJ/patch-1

FEATURE: disposable invite tokens
This commit is contained in:
Robin Ward 2014-07-14 12:13:44 -04:00
commit c12780eb2b
9 changed files with 188 additions and 16 deletions

View File

@ -41,6 +41,38 @@ class InvitesController < ApplicationController
end
end
def create_disposable_invite
guardian.ensure_can_create_disposable_invite!(current_user)
params.permit(:username, :email, :quantity, :group_names)
username_or_email = params[:username] ? fetch_username : fetch_email
user = User.find_by_username_or_email(username_or_email)
# generate invite tokens
invite_tokens = Invite.generate_disposable_tokens(user, params[:quantity], params[:group_names])
render_json_dump(invite_tokens)
end
def redeem_disposable_invite
params.require(:email)
params.permit(:username, :name)
invite = Invite.find_by(invite_key: params[:token])
if invite.present?
user = Invite.redeem_from_token(params[:token], params[:email], params[:username], params[:name])
if user.present?
log_on_user(user)
# Send a welcome message if required
user.enqueue_welcome_message('welcome_invite') if user.send_welcome_message
end
end
redirect_to "/"
end
def destroy
params.require(:email)
@ -95,4 +127,14 @@ class InvitesController < ApplicationController
render nothing: true
end
def fetch_username
params.require(:username)
params[:username]
end
def fetch_email
params.require(:email)
params[:email]
end
end

View File

@ -9,15 +9,14 @@ class Invite < ActiveRecord::Base
has_many :groups, through: :invited_groups
has_many :topic_invites
has_many :topics, through: :topic_invites, source: :topic
validates_presence_of :email
validates_presence_of :invited_by_id
before_create do
self.invite_key ||= SecureRandom.hex
end
before_save do
self.email = Email.downcase(email)
before_validation do
self.email = Email.downcase(email) unless email.nil?
end
validate :user_doesnt_already_exist
@ -96,6 +95,37 @@ class Invite < ActiveRecord::Base
invite
end
# generate invite tokens without email
def self.generate_disposable_tokens(invited_by, quantity=nil, group_names=nil)
invite_tokens = []
quantity ||= 1
group_ids = get_group_ids(group_names)
quantity.to_i.times do
invite = Invite.create!(invited_by: invited_by)
group_ids = group_ids - invite.invited_groups.pluck(:group_id)
group_ids.each do |group_id|
invite.invited_groups.create!(group_id: group_id)
end
invite_tokens.push(invite.invite_key)
end
invite_tokens
end
def self.get_group_ids(group_names)
group_ids = []
if group_names
group_names = group_names.split(',')
group_names.each { |group_name|
group_detail = Group.find_by_name(group_name)
group_ids.push(group_detail.id) if group_detail
}
end
group_ids
end
def self.find_all_invites_from(inviter)
Invite.where(invited_by_id: inviter.id)
.includes(:user => :user_stat)
@ -138,6 +168,15 @@ class Invite < ActiveRecord::Base
invite
end
def self.redeem_from_token(token, email, username=nil, name=nil)
invite = Invite.find_by(invite_key: token)
if invite
invite.update_column(:email, email)
user = InviteRedeemer.new(invite, username, name).redeem
end
user
end
def self.base_directory
File.join(Rails.root, "public", "csv", RailsMultisite::ConnectionManagement.current_db)
end
@ -153,7 +192,7 @@ end
#
# id :integer not null, primary key
# invite_key :string(32) not null
# email :string(255) not null
# email :string(255)
# invited_by_id :integer not null
# user_id :integer
# redeemed_at :datetime

View File

@ -1,4 +1,4 @@
InviteRedeemer = Struct.new(:invite) do
InviteRedeemer = Struct.new(:invite, :username, :name) do
def redeem
Invite.transaction do
@ -18,19 +18,23 @@ InviteRedeemer = Struct.new(:invite) do
end
# extracted from User cause it is very specific to invites
def self.create_user_from_invite(invite)
def self.create_user_from_invite(invite, username, name)
user_exists = User.find_by_email(invite.email)
return user if user_exists
username = UserNameSuggester.suggest(invite.email)
if username && User.username_available?(username)
available_username = username
else
available_username = UserNameSuggester.suggest(invite.email)
end
available_name = name || available_username
DiscourseHub.username_operation do
match, available, suggestion = DiscourseHub.username_match?(username, invite.email)
username = suggestion unless match || available
match, available, suggestion = DiscourseHub.username_match?(available_username, invite.email)
available_username = suggestion unless match || available
end
user = User.new(email: invite.email, username: username, name: username, active: true, trust_level: SiteSetting.default_invitee_trust_level)
user = User.new(email: invite.email, username: available_username, name: available_name, active: true, trust_level: SiteSetting.default_invitee_trust_level)
user.save!
DiscourseHub.username_operation { DiscourseHub.register_username(username, invite.email) }
@ -65,7 +69,7 @@ InviteRedeemer = Struct.new(:invite) do
def get_invited_user
result = get_existing_user
result ||= InviteRedeemer.create_user_from_invite(invite)
result ||= InviteRedeemer.create_user_from_invite(invite, username, name)
result.send_welcome_message = false
result
end

View File

@ -377,6 +377,8 @@ Discourse::Application.routes.draw do
post "upload" => "invites#upload_csv_chunk"
end
end
post "invites/disposable" => "invites#create_disposable_invite"
get "invites/redeem/:token" => "invites#redeem_disposable_invite"
delete "invites" => "invites#destroy"
get "onebox" => "onebox#show"

View File

@ -0,0 +1,9 @@
class RemoveNotNullFromEmail < ActiveRecord::Migration
def up
execute "ALTER TABLE invites ALTER COLUMN email DROP NOT NULL"
end
def down
execute "ALTER TABLE invites ALTER COLUMN email SET NOT NULL"
end
end

View File

@ -210,6 +210,10 @@ class Guardian
user.admin?
end
def can_create_disposable_invite?(user)
user.admin?
end
def can_see_private_messages?(user_id)
is_admin? || (authenticated? && @user.id == user_id)
end

View File

@ -150,6 +150,60 @@ describe InvitesController do
end
context '.create_disposable_invite' do
it 'requires you to be logged in' do
lambda {
post :create, email: 'jake@adventuretime.ooo'
}.should raise_error(Discourse::NotLoggedIn)
end
context 'while logged in as normal user' do
let(:user) { Fabricate(:user) }
it "does not create disposable invitation" do
log_in
post :create_disposable_invite, email: user.email
response.should_not be_success
end
end
context 'while logged in as admin' do
before do
log_in(:admin)
end
let(:user) { Fabricate(:user) }
it "creates disposable invitation" do
post :create_disposable_invite, email: user.email
response.should be_success
Invite.where(invited_by_id: user.id).count.should == 1
end
it "creates multiple disposable invitations" do
post :create_disposable_invite, email: user.email, quantity: 10
response.should be_success
Invite.where(invited_by_id: user.id).count.should == 10
end
it "allows group invite" do
group = Fabricate(:group)
post :create_disposable_invite, email: user.email, group_names: group.name
response.should be_success
Invite.find_by(invited_by_id: user.id).invited_groups.count.should == 1
end
it "allows multiple group invite" do
group_1 = Fabricate(:group, name: "security")
group_2 = Fabricate(:group, name: "support")
post :create_disposable_invite, email: user.email, group_names: "security,support"
response.should be_success
Invite.find_by(invited_by_id: user.id).invited_groups.count.should == 2
end
end
end
context '.check_csv_chunk' do
it 'requires you to be logged in' do
lambda {

View File

@ -3,10 +3,10 @@ require 'spec_helper'
describe InviteRedeemer do
describe '#create_for_email' do
let(:user) { InviteRedeemer.create_user_from_invite(Fabricate(:invite, email: 'walter.white@email.com')) }
let(:user) { InviteRedeemer.create_user_from_invite(Fabricate(:invite, email: 'walter.white@email.com'), 'walter', 'Walter White') }
it "should be created correctly" do
user.username.should == 'walter_white'
user.name.should == 'walter_white'
user.username.should == 'walter'
user.name.should == 'Walter White'
user.should be_active
user.email.should == 'walter.white@email.com'
end

View File

@ -2,7 +2,6 @@ require 'spec_helper'
describe Invite do
it { should validate_presence_of :email }
it { should validate_presence_of :invited_by_id }
let(:iceking) { 'iceking@adventuretime.ooo' }
@ -369,4 +368,23 @@ describe Invite do
end
describe '.redeem_from_token' do
let(:inviter) { Fabricate(:user) }
let(:invite) { Fabricate(:invite, invited_by: inviter, email: 'test@example.com', user_id: nil) }
let(:user) { Fabricate(:user, email: invite.email) }
it 'redeems the invite from token' do
result = Invite.redeem_from_token(invite.invite_key, user.email)
invite.reload
invite.should be_redeemed
end
it 'does not redeem the invite if token does not match' do
result = Invite.redeem_from_token("bae0071f995bb4b6f756e80b383778b5", user.email)
invite.reload
invite.should_not be_redeemed
end
end
end