Page MenuHomePhorge

D4606.1775232388.diff
No OneTemporary

Authored By
Unknown
Size
25 KB
Referenced Files
None
Subscribers
None

D4606.1775232388.diff

diff --git a/src/app/Console/Commands/Data/Import/SignupTokensCommand.php b/src/app/Console/Commands/Data/Import/SignupTokensCommand.php
new file mode 100644
--- /dev/null
+++ b/src/app/Console/Commands/Data/Import/SignupTokensCommand.php
@@ -0,0 +1,110 @@
+<?php
+
+namespace App\Console\Commands\Data\Import;
+
+use App\Plan;
+use App\SignupToken;
+use App\Console\Command;
+
+class SignupTokensCommand extends Command
+{
+ /**
+ * The name and signature of the console command.
+ *
+ * @var string
+ */
+ protected $signature = 'data:import:signup-tokens {plan} {file}';
+
+ /**
+ * The console command description.
+ *
+ * @var string
+ */
+ protected $description = 'Imports signup tokens from a file.';
+
+ /**
+ * Execute the console command.
+ *
+ * @return mixed
+ */
+ public function handle()
+ {
+ $plan = $this->getObject(Plan::class, $this->argument('plan'), 'title', false);
+
+ if (!$plan) {
+ $this->error("Plan not found");
+ return 1;
+ }
+
+ if ($plan->mode != Plan::MODE_TOKEN) {
+ $this->error("The plan is not for tokens");
+ return 1;
+ }
+
+ $file = $this->argument('file');
+
+ if (!file_exists($file)) {
+ $this->error("File '$file' does not exist");
+ return 1;
+ }
+
+ $list = file($file);
+
+ if (empty($list)) {
+ $this->error("File '$file' is empty");
+ return 1;
+ }
+
+ $bar = $this->createProgressBar(count($list), "Validating tokens");
+
+ $list = array_map('trim', $list);
+ $list = array_map('strtoupper', $list);
+
+ // Validate tokens
+ foreach ($list as $idx => $token) {
+ if (!strlen($token)) {
+ // Skip empty lines
+ unset($list[$idx]);
+ } elseif (strlen($token) > 191) {
+ $bar->finish();
+ $this->error("Token '$token' is too long");
+ return 1;
+ } elseif (SignupToken::find($token)) {
+ // Skip existing tokens
+ unset($list[$idx]);
+ }
+
+ $bar->advance();
+ }
+
+ $bar->finish();
+
+ $this->info("DONE");
+
+ if (empty($list)) {
+ $this->info("Nothing to import");
+ return 0;
+ }
+
+ $list = array_unique($list); // remove duplicated tokens
+
+ $bar = $this->createProgressBar(count($list), "Importing tokens");
+
+ // Import tokens
+ foreach ($list as $token) {
+ $plan->signupTokens()->create([
+ 'id' => $token,
+ // This allows us to update counter when importing old tokens in migration.
+ // It can be removed later
+ 'counter' => \App\UserSetting::where('key', 'signup_token')
+ ->whereRaw('UPPER(value) = ?', [$token])->count(),
+ ]);
+
+ $bar->advance();
+ }
+
+ $bar->finish();
+
+ $this->info("DONE");
+ }
+}
diff --git a/src/app/Http/Controllers/API/SignupController.php b/src/app/Http/Controllers/API/SignupController.php
--- a/src/app/Http/Controllers/API/SignupController.php
+++ b/src/app/Http/Controllers/API/SignupController.php
@@ -9,7 +9,7 @@
use App\Plan;
use App\Providers\PaymentProvider;
use App\Rules\SignupExternalEmail;
-use App\Rules\SignupToken;
+use App\Rules\SignupToken as SignupTokenRule;
use App\Rules\Password;
use App\Rules\UserEmailDomain;
use App\Rules\UserEmailLocal;
@@ -95,7 +95,7 @@
$plan = $this->getPlan();
if ($plan->mode == Plan::MODE_TOKEN) {
- $rules['token'] = ['required', 'string', new SignupToken()];
+ $rules['token'] = ['required', 'string', new SignupTokenRule($plan)];
} else {
$rules['email'] = ['required', 'string', new SignupExternalEmail()];
}
@@ -241,7 +241,9 @@
// Direct signup by token
if ($request->token) {
- $rules['token'] = ['required', 'string', new SignupToken()];
+ // This will validate the token and the plan mode
+ $plan = $request->plan ? Plan::withEnvTenantContext()->where('title', $request->plan)->first() : null;
+ $rules['token'] = ['required', 'string', new SignupTokenRule($plan)];
}
// Validate input
@@ -254,13 +256,7 @@
$settings = [];
if (!empty($request->token)) {
- // Token mode, check the plan
- $plan = $request->plan ? Plan::withEnvTenantContext()->where('title', $request->plan)->first() : null;
-
- if (!$plan || $plan->mode != Plan::MODE_TOKEN) {
- $msg = self::trans('validation.exists', ['attribute' => 'plan']);
- return response()->json(['status' => 'error', 'errors' => ['plan' => $msg]], 422);
- }
+ $settings = ['signup_token' => strtoupper($request->token)];
} elseif (!empty($request->plan) && empty($request->code) && empty($request->invitation)) {
// Plan parameter is required/allowed in mandate mode
$plan = Plan::withEnvTenantContext()->where('title', $request->plan)->first();
@@ -315,7 +311,7 @@
];
if ($plan->mode == Plan::MODE_TOKEN) {
- $settings['signup_token'] = $code_data->email;
+ $settings['signup_token'] = strtoupper($code_data->email);
} else {
$settings['external_email'] = $code_data->email;
}
@@ -431,6 +427,11 @@
$request->code->save();
}
+ // Bump up counter on the signup token
+ if (!empty($request->settings['signup_token'])) {
+ \App\SignupToken::where('id', $request->settings['signup_token'])->increment('counter');
+ }
+
DB::commit();
$response = AuthController::logonResponse($user, $request->password);
diff --git a/src/app/Observers/SignupTokenObserver.php b/src/app/Observers/SignupTokenObserver.php
new file mode 100644
--- /dev/null
+++ b/src/app/Observers/SignupTokenObserver.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace App\Observers;
+
+use App\SignupToken;
+
+class SignupTokenObserver
+{
+ /**
+ * Ensure the token is uppercased.
+ *
+ * @param \App\SignupToken $token The SignupToken object
+ */
+ public function creating(SignupToken $token): void
+ {
+ $token->id = strtoupper(trim($token->id));
+ }
+}
diff --git a/src/app/Plan.php b/src/app/Plan.php
--- a/src/app/Plan.php
+++ b/src/app/Plan.php
@@ -136,4 +136,14 @@
return false;
}
+
+ /**
+ * The relationship to signup tokens.
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\HasMany
+ */
+ public function signupTokens()
+ {
+ return $this->hasMany(SignupToken::class);
+ }
}
diff --git a/src/app/Providers/AppServiceProvider.php b/src/app/Providers/AppServiceProvider.php
--- a/src/app/Providers/AppServiceProvider.php
+++ b/src/app/Providers/AppServiceProvider.php
@@ -56,6 +56,7 @@
\App\SharedFolderSetting::observe(\App\Observers\SharedFolderSettingObserver::class);
\App\SignupCode::observe(\App\Observers\SignupCodeObserver::class);
\App\SignupInvitation::observe(\App\Observers\SignupInvitationObserver::class);
+ \App\SignupToken::observe(\App\Observers\SignupTokenObserver::class);
\App\Transaction::observe(\App\Observers\TransactionObserver::class);
\App\User::observe(\App\Observers\UserObserver::class);
\App\UserAlias::observe(\App\Observers\UserAliasObserver::class);
diff --git a/src/app/Rules/SignupToken.php b/src/app/Rules/SignupToken.php
--- a/src/app/Rules/SignupToken.php
+++ b/src/app/Rules/SignupToken.php
@@ -2,11 +2,23 @@
namespace App\Rules;
+use App\Plan;
use Illuminate\Contracts\Validation\Rule;
class SignupToken implements Rule
{
protected $message;
+ protected $plan;
+
+ /**
+ * Class constructor.
+ *
+ * @param ?Plan $plan Signup plan
+ */
+ public function __construct($plan)
+ {
+ $this->plan = $plan;
+ }
/**
* Determine if the validation rule passes.
@@ -24,32 +36,14 @@
return false;
}
- // Check the list of tokens for token existence
- $file = storage_path('signup-tokens.txt');
- $list = [];
- $token = \strtoupper($token);
-
- if (file_exists($file)) {
- $list = file($file);
- $list = array_map('trim', $list);
- $list = array_map('strtoupper', $list);
- } else {
- \Log::error("Signup tokens file ({$file}) does not exist");
- }
-
- if (!in_array($token, $list)) {
+ // Sanity check on the plan
+ if (!$this->plan || $this->plan->mode != Plan::MODE_TOKEN) {
$this->message = \trans('validation.signuptokeninvalid');
return false;
}
- // Check if the token has been already used for registration (exclude deleted users)
- $used = \App\User::select()
- ->join('user_settings', 'users.id', '=', 'user_settings.user_id')
- ->where('user_settings.key', 'signup_token')
- ->where('user_settings.value', $token)
- ->exists();
-
- if ($used) {
+ // Check the token existence
+ if (!$this->plan->signupTokens()->find($token)) {
$this->message = \trans('validation.signuptokeninvalid');
return false;
}
diff --git a/src/app/SignupToken.php b/src/app/SignupToken.php
new file mode 100644
--- /dev/null
+++ b/src/app/SignupToken.php
@@ -0,0 +1,49 @@
+<?php
+
+namespace App;
+
+use Carbon\Carbon;
+use Illuminate\Database\Eloquent\Model;
+
+/**
+ * The eloquent definition of a SignupToken.
+ *
+ * @property \Carbon\Carbon $created_at The creation timestamp
+ * @property int $counter Count of signups on this token
+ * @property ?string $id Token
+ * @property ?string $plan_id Plan identifier
+ */
+class SignupToken extends Model
+{
+ /** @var bool Indicates if the IDs are auto-incrementing */
+ public $incrementing = false;
+
+ /** @var string The "type" of the auto-incrementing ID */
+ protected $keyType = 'string';
+
+ /** @var array<int, string> The attributes that are mass assignable */
+ protected $fillable = [
+ 'plan_id',
+ 'id',
+ 'counter',
+ ];
+
+ /** @var array<string, string> The attributes that should be cast */
+ protected $casts = [
+ 'created_at' => 'datetime:Y-m-d H:i:s',
+ 'counter' => 'integer',
+ ];
+
+ /** @var bool Indicates if the model should be timestamped. */
+ public $timestamps = false;
+
+ /**
+ * The plan this token applies to
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function plan()
+ {
+ return $this->belongsTo(Plan::class);
+ }
+}
diff --git a/src/database/migrations/2023_12_14_100000_create_signup_tokens_table.php b/src/database/migrations/2023_12_14_100000_create_signup_tokens_table.php
new file mode 100644
--- /dev/null
+++ b/src/database/migrations/2023_12_14_100000_create_signup_tokens_table.php
@@ -0,0 +1,38 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+ /**
+ * Run the migrations.
+ *
+ * @return void
+ */
+ public function up()
+ {
+ Schema::create(
+ 'signup_tokens',
+ function (Blueprint $table) {
+ $table->string('id')->primary();
+ $table->string('plan_id', 36);
+ $table->integer('counter')->unsigned()->default(0);
+ $table->timestamp('created_at')->useCurrent();
+
+ $table->index('plan_id');
+ }
+ );
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('signup_tokens');
+ }
+};
diff --git a/src/tests/Browser/SignupTest.php b/src/tests/Browser/SignupTest.php
--- a/src/tests/Browser/SignupTest.php
+++ b/src/tests/Browser/SignupTest.php
@@ -7,6 +7,7 @@
use App\Plan;
use App\SignupCode;
use App\SignupInvitation;
+use App\SignupToken;
use App\User;
use Tests\Browser;
use Tests\Browser\Components\Menu;
@@ -32,6 +33,7 @@
$this->deleteTestDomain('user-domain-signup.com');
Plan::whereNot('mode', Plan::MODE_EMAIL)->update(['mode' => Plan::MODE_EMAIL]);
+ SignupToken::truncate();
}
/**
@@ -46,8 +48,7 @@
Plan::whereNot('mode', Plan::MODE_EMAIL)->update(['mode' => Plan::MODE_EMAIL]);
Discount::where('discount', 100)->update(['code' => null]);
-
- @unlink(storage_path('signup-tokens.txt'));
+ SignupToken::truncate();
parent::tearDown();
}
@@ -669,18 +670,18 @@
public function testSignupToken(): void
{
// Test the individual plan
- Plan::where('title', 'individual')->update(['mode' => Plan::MODE_TOKEN]);
+ $plan = Plan::withEnvTenantContext()->where('title', 'individual')->first();
+ $plan->update(['mode' => Plan::MODE_TOKEN]);
- // Register some valid tokens
- $tokens = ['1234567890', 'abcdefghijk'];
- file_put_contents(storage_path('signup-tokens.txt'), implode("\n", $tokens));
+ // Register a valid token
+ $plan->signupTokens()->create(['id' => '1234567890']);
- $this->browse(function (Browser $browser) use ($tokens) {
+ $this->browse(function (Browser $browser) {
$browser->visit(new Signup())
->waitFor('@step0 .plan-individual button')
->click('@step0 .plan-individual button')
// Step 1
- ->whenAvailable('@step1', function ($browser) use ($tokens) {
+ ->whenAvailable('@step1', function ($browser) {
$browser->assertSeeIn('.card-title', 'Sign Up - Step 1/2')
->type('#signup_first_name', 'Test')
->type('#signup_last_name', 'User')
@@ -693,7 +694,7 @@
->assertFocused('#signup_token')
->assertToast(Toast::TYPE_ERROR, 'Form validation error')
// valid token
- ->type('#signup_token', $tokens[0])
+ ->type('#signup_token', '1234567890')
->click('[type=submit]');
})
// Step 2
@@ -718,18 +719,21 @@
});
$user = User::where('email', 'signuptestdusk@' . \config('app.domain'))->first();
- $this->assertSame($tokens[0], $user->getSetting('signup_token'));
$this->assertSame(null, $user->getSetting('external_email'));
// Test the group plan
- Plan::where('title', 'group')->update(['mode' => Plan::MODE_TOKEN]);
+ $plan = Plan::withEnvTenantContext()->where('title', 'group')->first();
+ $plan->update(['mode' => Plan::MODE_TOKEN]);
- $this->browse(function (Browser $browser) use ($tokens) {
+ // Register a valid token
+ $plan->signupTokens()->create(['id' => 'abcdefghijk']);
+
+ $this->browse(function (Browser $browser) {
$browser->visit(new Signup())
->waitFor('@step0 .plan-group button')
->click('@step0 .plan-group button')
// Step 1
- ->whenAvailable('@step1', function ($browser) use ($tokens) {
+ ->whenAvailable('@step1', function ($browser) {
$browser->assertSeeIn('.card-title', 'Sign Up - Step 1/2')
->type('#signup_first_name', 'Test')
->type('#signup_last_name', 'User')
@@ -742,7 +746,7 @@
->assertFocused('#signup_token')
->assertToast(Toast::TYPE_ERROR, 'Form validation error')
// valid token
- ->type('#signup_token', $tokens[1])
+ ->type('#signup_token', 'abcdefghijk')
->click('[type=submit]');
})
// Step 2
@@ -762,7 +766,6 @@
});
$user = User::where('email', 'admin@user-domain-signup.com')->first();
- $this->assertSame($tokens[1], $user->getSetting('signup_token'));
$this->assertSame(null, $user->getSetting('external_email'));
}
diff --git a/src/tests/Feature/Console/Data/Import/SignupTokensTest.php b/src/tests/Feature/Console/Data/Import/SignupTokensTest.php
new file mode 100644
--- /dev/null
+++ b/src/tests/Feature/Console/Data/Import/SignupTokensTest.php
@@ -0,0 +1,97 @@
+<?php
+
+namespace Tests\Feature\Console\Data\Import;
+
+use App\Plan;
+use App\SignupToken;
+use Tests\TestCase;
+
+class SignupTokensTest extends TestCase
+{
+ /**
+ * {@inheritDoc}
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+
+ Plan::where('title', 'test')->delete();
+ SignupToken::truncate();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function tearDown(): void
+ {
+ Plan::where('title', 'test')->delete();
+ SignupToken::truncate();
+
+ @unlink(storage_path('test-tokens.txt'));
+
+ parent::tearDown();
+ }
+
+ /**
+ * Test the command
+ */
+ public function testHandle(): void
+ {
+ $file = storage_path('test-tokens.txt');
+ file_put_contents($file, '');
+
+ // Unknown plan
+ $code = \Artisan::call("data:import:signup-tokens unknown {$file}");
+ $output = trim(\Artisan::output());
+
+ $this->assertSame(1, $code);
+ $this->assertSame("Plan not found", $output);
+
+ // Plan not for tokens
+ $code = \Artisan::call("data:import:signup-tokens individual {$file}");
+ $output = trim(\Artisan::output());
+
+ $this->assertSame(1, $code);
+ $this->assertSame("The plan is not for tokens", $output);
+
+ $plan = Plan::create([
+ 'title' => 'test',
+ 'name' => 'Test Account',
+ 'description' => 'Test',
+ 'mode' => Plan::MODE_TOKEN,
+ ]);
+
+ // Non-existent input file
+ $code = \Artisan::call("data:import:signup-tokens {$plan->title} nofile.txt");
+ $output = trim(\Artisan::output());
+
+ $this->assertSame(1, $code);
+ $this->assertSame("File 'nofile.txt' does not exist", $output);
+
+ // Empty input file
+ $code = \Artisan::call("data:import:signup-tokens {$plan->title} {$file}");
+ $output = trim(\Artisan::output());
+
+ $this->assertSame(1, $code);
+ $this->assertSame("File '{$file}' is empty", $output);
+
+ // Valid tokens
+ file_put_contents($file, "12345\r\nabcde");
+ $code = \Artisan::call("data:import:signup-tokens {$plan->id} {$file}");
+ $output = trim(\Artisan::output());
+
+ $this->assertSame(0, $code);
+ $this->assertStringContainsString("Validating tokens... DONE", $output);
+ $this->assertStringContainsString("Importing tokens... DONE", $output);
+ $this->assertSame(['12345', 'ABCDE'], $plan->signupTokens()->orderBy('id')->pluck('id')->all());
+
+ // Attempt the same tokens again
+ $code = \Artisan::call("data:import:signup-tokens {$plan->id} {$file}");
+ $output = trim(\Artisan::output());
+
+ $this->assertSame(0, $code);
+ $this->assertStringContainsString("Validating tokens... DONE", $output);
+ $this->assertStringContainsString("Nothing to import", $output);
+ $this->assertStringNotContainsString("Importing tokens...", $output);
+ }
+}
diff --git a/src/tests/Feature/Controller/SignupTest.php b/src/tests/Feature/Controller/SignupTest.php
--- a/src/tests/Feature/Controller/SignupTest.php
+++ b/src/tests/Feature/Controller/SignupTest.php
@@ -10,6 +10,7 @@
use App\Package;
use App\SignupCode;
use App\SignupInvitation as SI;
+use App\SignupToken;
use App\User;
use App\VatRate;
use Illuminate\Support\Facades\Queue;
@@ -39,7 +40,9 @@
$this->deleteTestDomain('signup-domain.com');
$this->deleteTestGroup('group-test@kolabnow.com');
+
SI::truncate();
+ SignupToken::truncate();
Plan::where('title', 'test')->delete();
IP4Net::where('net_number', inet_pton('127.0.0.0'))->delete();
VatRate::query()->delete();
@@ -59,13 +62,13 @@
$this->deleteTestDomain('signup-domain.com');
$this->deleteTestGroup('group-test@kolabnow.com');
+
SI::truncate();
+ SignupToken::truncate();
Plan::where('title', 'test')->delete();
IP4Net::where('net_number', inet_pton('127.0.0.0'))->delete();
VatRate::query()->delete();
- @unlink(storage_path('signup-tokens.txt'));
-
parent::tearDown();
}
@@ -953,18 +956,8 @@
$this->assertSame('error', $json['status']);
$this->assertSame(['token' => ["The signup token is invalid."]], $json['errors']);
- file_put_contents(storage_path('signup-tokens.txt'), "abc\n");
-
- // Test invalid plan (existing plan with another mode)
- $post['plan'] = 'individual';
- $response = $this->post('/api/auth/signup', $post);
- $response->assertStatus(422);
- $json = $response->json();
-
- $this->assertSame('error', $json['status']);
- $this->assertSame(['plan' => "The selected plan is invalid."], $json['errors']);
-
- // Test valid input
+ // Test valid token
+ $plan->signupTokens()->create(['id' => 'abc']);
$post['plan'] = $plan->title;
$response = $this->post('/api/auth/signup', $post);
$response->assertStatus(200);
@@ -976,9 +969,13 @@
// Check if the user has been created
$user = User::where('email', 'test-inv@kolabnow.com')->first();
-
$this->assertNotEmpty($user);
$this->assertSame($plan->id, $user->getSetting('plan_id'));
+ $this->assertSame($plan->signupTokens()->first()->id, $user->getSetting('signup_token'));
+ $this->assertSame(null, $user->getSetting('external_email'));
+
+ // Token's counter bumped up
+ $this->assertSame(1, $plan->signupTokens()->first()->counter);
}
/**
diff --git a/src/tests/Unit/Rules/SignupTokenTest.php b/src/tests/Unit/Rules/SignupTokenTest.php
--- a/src/tests/Unit/Rules/SignupTokenTest.php
+++ b/src/tests/Unit/Rules/SignupTokenTest.php
@@ -2,7 +2,9 @@
namespace Tests\Unit\Rules;
-use App\Rules\SignupToken;
+use App\Plan;
+use App\Rules\SignupToken as SignupTokenRule;
+use App\SignupToken;
use Illuminate\Support\Facades\Validator;
use Tests\TestCase;
@@ -11,25 +13,44 @@
/**
* {@inheritDoc}
*/
- public function tearDown(): void
+ public function setUp(): void
{
- @unlink(storage_path('signup-tokens.txt'));
+ parent::setUp();
- $john = $this->getTestUser('john@kolab.org');
- $john->settings()->where('key', 'signup_token')->delete();
+ Plan::where('title', 'test-plan')->delete();
+ SignupToken::truncate();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function tearDown(): void
+ {
+ Plan::where('title', 'test-plan')->delete();
+ SignupToken::truncate();
parent::tearDown();
}
/**
- * Tests the resource name validator
+ * Tests the signup token validator
*/
public function testValidation(): void
{
- $tokens = ['1234567890', 'abcdefghijk'];
- file_put_contents(storage_path('signup-tokens.txt'), implode("\n", $tokens));
+ $tokens = ['abcdefghijk', 'T-abcdefghijk'];
+
+ $plan = Plan::where('title', 'individual')->first();
+ $tokenPlan = Plan::create([
+ 'title' => 'test-plan',
+ 'description' => 'test',
+ 'name' => 'Test',
+ 'mode' => Plan::MODE_TOKEN,
+ ]);
- $rules = ['token' => [new SignupToken()]];
+ $plan->signupTokens()->create(['id' => $tokens[0]]);
+ $tokenPlan->signupTokens()->create(['id' => $tokens[1]]);
+
+ $rules = ['token' => [new SignupTokenRule(null)]];
// Empty input
$v = Validator::make(['token' => null], $rules);
@@ -39,22 +60,28 @@
$v = Validator::make(['token' => str_repeat('a', 192)], $rules);
$this->assertSame(['token' => ["The signup token is invalid."]], $v->errors()->toArray());
+ // Valid token, but no plan
+ $v = Validator::make(['token' => $tokens[1]], $rules);
+ $this->assertSame(['token' => ["The signup token is invalid."]], $v->errors()->toArray());
+
+ $rules = ['token' => [new SignupTokenRule($plan)]];
+
+ // Plan that does not support tokens
+ $v = Validator::make(['token' => $tokens[0]], $rules);
+ $this->assertSame(['token' => ["The signup token is invalid."]], $v->errors()->toArray());
+
+ $rules = ['token' => [new SignupTokenRule($tokenPlan)]];
+
// Non-existing token
$v = Validator::make(['token' => '123'], $rules);
$this->assertSame(['token' => ["The signup token is invalid."]], $v->errors()->toArray());
- // Valid tokens
- $v = Validator::make(['token' => $tokens[0]], $rules);
+ // Valid token
+ $v = Validator::make(['token' => $tokens[1]], $rules);
$this->assertSame([], $v->errors()->toArray());
+ // Valid token (uppercase)
$v = Validator::make(['token' => strtoupper($tokens[1])], $rules);
$this->assertSame([], $v->errors()->toArray());
-
- // Tokens already used
- $john = $this->getTestUser('john@kolab.org');
- $john->setSetting('signup_token', $tokens[0]);
-
- $v = Validator::make(['token' => $tokens[0]], $rules);
- $this->assertSame(['token' => ["The signup token is invalid."]], $v->errors()->toArray());
}
}

File Metadata

Mime Type
text/plain
Expires
Fri, Apr 3, 4:06 PM (21 h, 22 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18824638
Default Alt Text
D4606.1775232388.diff (25 KB)

Event Timeline