Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F118550567
D925.1775891553.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
41 KB
Referenced Files
None
Subscribers
None
D925.1775891553.diff
View Options
diff --git a/src/app/Backends/IMAP.php b/src/app/Backends/IMAP.php
new file mode 100644
--- /dev/null
+++ b/src/app/Backends/IMAP.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace App\Backends;
+
+use App\Domain;
+use App\User;
+
+class IMAP
+{
+ /**
+ * Check if an account is set up
+ *
+ * @param string $username User login (email address)
+ *
+ * @return bool
+ */
+ public static function verifyAccount(string $username): bool
+ {
+ // TODO
+ return true;
+ }
+}
diff --git a/src/app/Domain.php b/src/app/Domain.php
--- a/src/app/Domain.php
+++ b/src/app/Domain.php
@@ -8,14 +8,18 @@
{
// we've simply never heard of this domain
public const STATUS_NEW = 1 << 0;
- // it's been activated -- mutually exclusive with new?
+ // it's been activated
public const STATUS_ACTIVE = 1 << 1;
- // ownership of the domain has been confirmed -- mutually exclusive with new?
- public const STATUS_CONFIRMED = 1 << 2;
// domain has been suspended.
- public const STATUS_SUSPENDED = 1 << 3;
- // domain has been deleted -- can not be active any more.
- public const STATUS_DELETED = 1 << 4;
+ public const STATUS_SUSPENDED = 1 << 2;
+ // domain has been deleted
+ public const STATUS_DELETED = 1 << 3;
+ // ownership of the domain has been confirmed
+ public const STATUS_CONFIRMED = 1 << 4;
+ // domain has been verified that it exists in DNS
+ public const STATUS_VERIFIED = 1 << 5;
+ // domain has been created in LDAP
+ public const STATUS_LDAP_READY = 1 << 6;
// open for public registration
public const TYPE_PUBLIC = 1 << 0;
@@ -33,10 +37,6 @@
'type'
];
- //protected $guarded = [
- // "status"
- //];
-
public function entitlement()
{
return $this->morphOne('App\Entitlement', 'entitleable');
@@ -124,6 +124,16 @@
return $this->type & self::TYPE_PUBLIC;
}
+ /**
+ * Returns whether this domain is registered in LDAP.
+ *
+ * @return bool
+ */
+ public function isLdapReady(): bool
+ {
+ return $this->status & self::STATUS_LDAP_READY;
+ }
+
/**
* Returns whether this domain is suspended.
*
@@ -134,6 +144,17 @@
return $this->status & self::STATUS_SUSPENDED;
}
+ /**
+ * Returns whether this (external) domain has been verified
+ * to exist in DNS.
+ *
+ * @return bool
+ */
+ public function isVerified(): bool
+ {
+ return $this->status & self::STATUS_VERIFIED;
+ }
+
/**
* Domain status mutator
*
@@ -149,6 +170,8 @@
self::STATUS_CONFIRMED,
self::STATUS_SUSPENDED,
self::STATUS_DELETED,
+ self::STATUS_LDAP_READY,
+ self::STATUS_VERIFIED,
];
foreach ($allowed_values as $value) {
@@ -164,4 +187,104 @@
$this->attributes['status'] = $new_status;
}
+
+ /**
+ * Ownership verification by checking for a TXT (or CNAME) record
+ * in the domain's DNS (that matches the verification hash).
+ *
+ * @return bool True if verification was successful, false otherwise
+ * @throws \Exception Throws exception on DNS or DB errors
+ */
+ public function confirm(): bool
+ {
+ if ($this->isConfirmed()) {
+ return true;
+ }
+
+ $hash = $this->hash();
+ $confirmed = false;
+
+ // Get DNS records and find a matching TXT entry
+ $records = \dns_get_record($this->namespace, DNS_TXT);
+
+ if ($records === false) {
+ throw new \Exception("Failed to get DNS record for $domain");
+ }
+
+ foreach ($records as $record) {
+ if ($record['txt'] === $hash) {
+ $confirmed = true;
+ break;
+ }
+ }
+
+ // Get DNS records and find a matching CNAME entry
+ // Note: some servers resolve every non-existing name
+ // so we need to define left and right side of the CNAME record
+ // i.e.: kolab-verify IN CNAME <hash>.domain.tld.
+ if (!$confirmed) {
+ $cname = $this->hash(true) . '.' . $this->namespace;
+ $records = \dns_get_record('kolab-verify.' . $this->namespace, DNS_CNAME);
+
+ if ($records === false) {
+ throw new \Exception("Failed to get DNS record for $domain");
+ }
+
+ foreach ($records as $records) {
+ if ($records['target'] === $cname) {
+ $confirmed = true;
+ break;
+ }
+ }
+ }
+
+ if ($confirmed) {
+ $this->status |= Domain::STATUS_CONFIRMED;
+ $this->save();
+ }
+
+ return $confirmed;
+ }
+
+ /**
+ * Generate a verification hash for this domain
+ *
+ * @param bool $short Return short version (with kolab-verify= prefix)
+ *
+ * @return string Verification hash
+ */
+ public function hash($short = false): string
+ {
+ $hash = \md5('hkccp-verify-' . $this->namespace . $this->id);
+
+ return $short ? $hash : "kolab-verify=$hash";
+ }
+
+ /**
+ * Verify if a domain exists in DNS
+ *
+ * @return bool True if registered, False otherwise
+ * @throws \Exception Throws exception on DNS or DB errors
+ */
+ public function verify(): bool
+ {
+ if ($this->isVerified()) {
+ return true;
+ }
+
+ $record = \dns_get_record($domain, DNS_SOA);
+
+ if ($record === false) {
+ throw new \Exception("Failed to get DNS record for $domain");
+ }
+
+ if (!empty($record)) {
+ $this->status |= Domain::STATUS_VERIFIED;
+ $this->save();
+
+ return true;
+ }
+
+ return false;
+ }
}
diff --git a/src/app/Http/Controllers/API/UsersController.php b/src/app/Http/Controllers/API/UsersController.php
--- a/src/app/Http/Controllers/API/UsersController.php
+++ b/src/app/Http/Controllers/API/UsersController.php
@@ -3,6 +3,7 @@
namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
+use App\Domain;
use App\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
@@ -73,7 +74,12 @@
*/
public function info()
{
- return response()->json($this->guard()->user());
+ $user = $this->guard()->user();
+ $response = $user->toArray();
+
+ $response['statusInfo'] = self::statusInfo($user);
+
+ return response()->json($response);
}
/**
@@ -170,6 +176,59 @@
return \App\User::find($id);
}
+ /**
+ * User status (extended) information
+ *
+ * @param \App\User $user User object
+ *
+ * @return array Status information
+ */
+ public static function statusInfo(User $user): array
+ {
+ $status = 'new';
+ $process = [];
+ $steps = [
+ 'user-new' => true,
+ 'user-ldap-ready' => 'isLdapReady',
+ 'user-imap-ready' => 'isImapReady',
+ ];
+
+ if ($user->isDeleted()) {
+ $status = 'deleted';
+ } elseif ($user->isSuspended()) {
+ $status = 'suspended';
+ } elseif ($user->isActive()) {
+ $status = 'active';
+ }
+
+ list ($local, $domain) = explode('@', $user->email);
+ $domain = Domain::where('namespace', $domain)->first();
+
+ // If that is not a public domain, add domain specific steps
+ if (!$domain->isPublic()) {
+ $steps['domain-new'] = true;
+ $steps['domain-ldap-ready'] = 'isLdapReady';
+ $steps['domain-verified'] = 'isVerified';
+ $steps['domain-confirmed'] = 'isConfirmed';
+ }
+
+ // Create a process check list
+ foreach ($steps as $step_name => $func) {
+ $object = strpos($step_name, 'user-') === 0 ? $user : $domain;
+
+ $process[] = [
+ 'label' => $step_name,
+ 'title' => __("app.process-{$step_name}"),
+ 'state' => is_bool($func) ? $func : $object->{$func}(),
+ ];
+ }
+
+ return [
+ 'process' => $process,
+ 'status' => $status,
+ ];
+ }
+
/**
* Get the guard to be used during authentication.
*
diff --git a/src/app/Jobs/ProcessDomainCreate.php b/src/app/Jobs/ProcessDomainCreate.php
--- a/src/app/Jobs/ProcessDomainCreate.php
+++ b/src/app/Jobs/ProcessDomainCreate.php
@@ -43,6 +43,11 @@
*/
public function handle()
{
- LDAP::createDomain($this->domain);
+ if (!$this->domain->isLdapReady()) {
+ LDAP::createDomain($this->domain);
+
+ $this->domain->status |= Domain::STATUS_LDAP_READY;
+ $this->domain->save();
+ }
}
}
diff --git a/src/app/Jobs/ProcessDomainCreate.php b/src/app/Jobs/ProcessDomainVerify.php
copy from src/app/Jobs/ProcessDomainCreate.php
copy to src/app/Jobs/ProcessDomainVerify.php
--- a/src/app/Jobs/ProcessDomainCreate.php
+++ b/src/app/Jobs/ProcessDomainVerify.php
@@ -2,15 +2,14 @@
namespace App\Jobs;
-use App\Backends\LDAP;
use App\Domain;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
-use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
+use Illuminate\Queue\SerializesModels;
-class ProcessDomainCreate implements ShouldQueue
+class ProcessDomainVerify implements ShouldQueue
{
use Dispatchable;
use InteractsWithQueue;
@@ -43,6 +42,10 @@
*/
public function handle()
{
- LDAP::createDomain($this->domain);
+ $this->domain->verify();
+
+ // TODO: What should happen if the domain is not registered yet?
+ // Should we start a new job with some specified delay?
+ // Or we just give the user a button to start verification again?
}
}
diff --git a/src/app/Jobs/ProcessUserCreate.php b/src/app/Jobs/ProcessUserCreate.php
--- a/src/app/Jobs/ProcessUserCreate.php
+++ b/src/app/Jobs/ProcessUserCreate.php
@@ -44,6 +44,11 @@
*/
public function handle()
{
- LDAP::createUser($this->user);
+ if (!$this->user->isLdapReady()) {
+ LDAP::createUser($this->user);
+
+ $this->user->status |= User::STATUS_LDAP_READY;
+ $this->user->save();
+ }
}
}
diff --git a/src/app/Jobs/ProcessUserCreate.php b/src/app/Jobs/ProcessUserVerify.php
copy from src/app/Jobs/ProcessUserCreate.php
copy to src/app/Jobs/ProcessUserVerify.php
--- a/src/app/Jobs/ProcessUserCreate.php
+++ b/src/app/Jobs/ProcessUserVerify.php
@@ -2,7 +2,6 @@
namespace App\Jobs;
-use App\Backends\LDAP;
use App\User;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
@@ -10,7 +9,7 @@
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
-class ProcessUserCreate implements ShouldQueue
+class ProcessUserVerify implements ShouldQueue
{
use Dispatchable;
use InteractsWithQueue;
@@ -44,6 +43,12 @@
*/
public function handle()
{
- LDAP::createUser($this->user);
+ if (!$this->user->isImapReady()) {
+ if (IMAP::verifyAccount($this->user->email)) {
+ $this->user->status |= User::STATUS_IMAP_READY;
+ $this->user->status |= User::STATUS_ACTIVE;
+ $this->user->save();
+ }
+ }
}
}
diff --git a/src/app/Observers/DomainObserver.php b/src/app/Observers/DomainObserver.php
--- a/src/app/Observers/DomainObserver.php
+++ b/src/app/Observers/DomainObserver.php
@@ -22,6 +22,8 @@
break;
}
}
+
+ $domain->status |= Domain::STATUS_NEW;
}
/**
@@ -33,7 +35,12 @@
*/
public function created(Domain $domain)
{
- \App\Jobs\ProcessDomainCreate::dispatch($domain);
+ // Create domain record in LDAP, then check if it exists in DNS
+ $chain = [
+ new \App\Jobs\ProcessDomainVerify($domain),
+ ];
+
+ \App\Jobs\ProcessDomainCreate::withChain($chain)->dispatch($domain);
}
/**
diff --git a/src/app/Observers/UserObserver.php b/src/app/Observers/UserObserver.php
--- a/src/app/Observers/UserObserver.php
+++ b/src/app/Observers/UserObserver.php
@@ -24,6 +24,9 @@
break;
}
}
+
+ $user->status |= User::STATUS_NEW;
+
// can't dispatch job here because it'll fail serialization
}
@@ -54,7 +57,12 @@
$user->wallets()->create();
- \App\Jobs\ProcessUserCreate::dispatch($user);
+ // Create user record in LDAP, then check if the account is created in IMAP
+ $chain = [
+ new \App\Jobs\ProcessUserVerify($user),
+ ];
+
+ \App\Jobs\ProcessUserCreate::withChain($chain)->dispatch($user);
}
/**
diff --git a/src/app/User.php b/src/app/User.php
--- a/src/app/User.php
+++ b/src/app/User.php
@@ -18,6 +18,20 @@
use NullableFields;
use UserSettingsTrait;
+ // a new user, default on creation
+ public const STATUS_NEW = 1 << 0;
+ // it's been activated
+ public const STATUS_ACTIVE = 1 << 1;
+ // user has been suspended
+ public const STATUS_SUSPENDED = 1 << 2;
+ // user has been deleted
+ public const STATUS_DELETED = 1 << 3;
+ // user has been created in LDAP
+ public const STATUS_LDAP_READY = 1 << 4;
+ // user mailbox has been created in IMAP
+ public const STATUS_IMAP_READY = 1 << 5;
+
+
// change the default primary key type
public $incrementing = false;
protected $keyType = 'bigint';
@@ -28,7 +42,11 @@
* @var array
*/
protected $fillable = [
- 'name', 'email', 'password', 'password_ldap'
+ 'name',
+ 'email',
+ 'password',
+ 'password_ldap',
+ 'status'
];
/**
@@ -37,7 +55,9 @@
* @var array
*/
protected $hidden = [
- 'password', 'password_ldap', 'remember_token',
+ 'password',
+ 'password_ldap',
+ 'remember_token',
];
protected $nullable = [
@@ -150,6 +170,82 @@
return $user;
}
+ public function getJWTIdentifier()
+ {
+ return $this->getKey();
+ }
+
+ public function getJWTCustomClaims()
+ {
+ return [];
+ }
+
+ /**
+ * Returns whether this domain is active.
+ *
+ * @return bool
+ */
+ public function isActive(): bool
+ {
+ return $this->status & self::STATUS_ACTIVE;
+ }
+
+ /**
+ * Returns whether this domain is deleted.
+ *
+ * @return bool
+ */
+ public function isDeleted(): bool
+ {
+ return $this->status & self::STATUS_DELETED;
+ }
+
+ /**
+ * Returns whether this (external) domain has been verified
+ * to exist in DNS.
+ *
+ * @return bool
+ */
+ public function isImapReady(): bool
+ {
+ return $this->status & self::STATUS_IMAP_READY;
+ }
+
+ /**
+ * Returns whether this user is registered in LDAP.
+ *
+ * @return bool
+ */
+ public function isLdapReady(): bool
+ {
+ return $this->status & self::STATUS_LDAP_READY;
+ }
+
+ /**
+ * Returns whether this user is new.
+ *
+ * @return bool
+ */
+ public function isNew(): bool
+ {
+ return $this->status & self::STATUS_NEW;
+ }
+
+ /**
+ * Returns whether this domain is suspended.
+ *
+ * @return bool
+ */
+ public function isSuspended(): bool
+ {
+ return $this->status & self::STATUS_SUSPENDED;
+ }
+
+ /**
+ * Any (additional) properties of this user.
+ *
+ * @return \App\UserSetting[]
+ */
public function settings()
{
return $this->hasMany('App\UserSetting', 'user_id');
@@ -175,16 +271,6 @@
return $this->hasMany('App\Wallet');
}
- public function getJWTIdentifier()
- {
- return $this->getKey();
- }
-
- public function getJWTCustomClaims()
- {
- return [];
- }
-
public function setPasswordAttribute($password)
{
if (!empty($password)) {
@@ -204,4 +290,36 @@
);
}
}
+
+ /**
+ * User status mutator
+ *
+ * @throws \Exception
+ */
+ public function setStatusAttribute($status)
+ {
+ $new_status = 0;
+
+ $allowed_values = [
+ self::STATUS_NEW,
+ self::STATUS_ACTIVE,
+ self::STATUS_SUSPENDED,
+ self::STATUS_DELETED,
+ self::STATUS_LDAP_READY,
+ self::STATUS_IMAP_READY,
+ ];
+
+ foreach ($allowed_values as $value) {
+ if ($status & $value) {
+ $new_status |= $value;
+ $status ^= $value;
+ }
+ }
+
+ if ($status > 0) {
+ throw new \Exception("Invalid user status: {$status}");
+ }
+
+ $this->attributes['status'] = $new_status;
+ }
}
diff --git a/src/database/migrations/2014_10_12_000000_create_users_table.php b/src/database/migrations/2014_10_12_000000_create_users_table.php
--- a/src/database/migrations/2014_10_12_000000_create_users_table.php
+++ b/src/database/migrations/2014_10_12_000000_create_users_table.php
@@ -22,6 +22,7 @@
$table->timestamp('email_verified_at')->nullable();
$table->string('password')->nullable();
$table->string('password_ldap')->nullable();
+ $table->smallinteger('status');
$table->rememberToken();
$table->timestamps();
diff --git a/src/resources/lang/en/app.php b/src/resources/lang/en/app.php
--- a/src/resources/lang/en/app.php
+++ b/src/resources/lang/en/app.php
@@ -12,4 +12,11 @@
'planbutton' => 'Choose :plan',
+ 'process-user-new' => 'User registered',
+ 'process-user-ldap-ready' => 'User created',
+ 'process-user-imap-ready' => 'User mailbox created',
+ 'process-domain-new' => 'Custom domain registered',
+ 'process-domain-ldap-ready' => 'Custom domain created',
+ 'process-domain-verified' => 'Custom domain verified',
+ 'process-domain-confirmed' => 'Custom domain ownership verified',
];
diff --git a/src/resources/vue/components/Dashboard.vue b/src/resources/vue/components/Dashboard.vue
--- a/src/resources/vue/components/Dashboard.vue
+++ b/src/resources/vue/components/Dashboard.vue
@@ -1,12 +1,23 @@
<template>
<div class="container">
- <div class="row">
- <div class="col-md-8 col-md-offset-2">
- <div class="panel panel-default">
- <div class="panel-heading">Dashboard</div>
- <div class="panel-body">
- <p>Data: {{ data }}</p>
- </div>
+ <div id="dashboard-box" class="card">
+ <div class="card-body">
+ <div class="card-title">Dashboard</div>
+ <div class="card-text">
+ <p>{{ data }}</p>
+ </div>
+ </div>
+ </div>
+ <div id="status-box" class="card">
+ <div class="card-body">
+ <div class="card-title">Status</div>
+ <div class="card-text">
+ <ul style="list-style: none; padding: 0">
+ <li v-for="item in statusProcess">
+ <span v-if="item.state">✓</span><span v-else>○</span>
+ {{ item.title }}
+ </li>
+ </ul>
</div>
</div>
</div>
@@ -17,7 +28,8 @@
export default {
data() {
return {
- data: 'nothing'
+ data: [],
+ statusProcess: []
}
},
mounted() {
@@ -27,8 +39,42 @@
}
})
.then(response => {
- this.data = response.data.data
+ this.data = response.data
+ this.parseStatusInfo(response.data.statusInfo)
+ delete this.data.statusInfo
})
+ },
+ methods: {
+ // Displays account status information
+ parseStatusInfo(info) {
+ this.statusProcess = info.process
+
+ // Update status process info every 10 seconds
+ // FIXME: This probably should have some limit, or the interval
+ // should grow (well, until it could be done with websocket notifications)
+ if (info.status != 'active') {
+ setTimeout(() => {
+ // Stop updates after user logged out
+ // FIXME: Definitely stopping such requests after logout
+ // adding the header to all axios requests calls
+ // for some more general solution
+ let token = localStorage.getItem('token')
+ if (!token) {
+ return;
+ }
+
+ axios.get('/api/auth/info', {
+ headers: { Authorization: 'Bearer ' + token }
+ }).then(response => {
+ this.parseStatusInfo(response.data.statusInfo)
+ }).catch(error => {
+ this.parseStatusInfo(info)
+ })
+ }, 10000);
+ }
+
+ return info
+ }
}
}
</script>
diff --git a/src/tests/Feature/Controller/UsersTest.php b/src/tests/Feature/Controller/UsersTest.php
--- a/src/tests/Feature/Controller/UsersTest.php
+++ b/src/tests/Feature/Controller/UsersTest.php
@@ -2,6 +2,8 @@
namespace Tests\Feature\Controller;
+use App\Http\Controllers\API\UsersController;
+use App\Domain;
use App\User;
use Illuminate\Support\Str;
use Tests\TestCase;
@@ -15,29 +17,32 @@
{
parent::setUp();
- $user = User::where('email', 'UsersControllerTest1@UsersControllerTest.com')->delete();
+ User::where('email', 'UsersControllerTest1@userscontroller.com')->delete();
+ Domain::where('namespace', 'userscontroller.com')->delete();
}
/**
- * {@inheritDoc}
+ * Test fetching current user info
*/
- public function tearDown(): void
- {
- $user = User::where('email', 'UsersControllerTest1@UsersControllerTest.com')->delete();
- }
-
- public function testListUsers(): void
+ public function testInfo(): void
{
- $user = $this->getTestUser('UsersControllerTest1@UsersControllerTest.com');
+ $user = $this->getTestUser('UsersControllerTest1@userscontroller.com');
+ $domain = $this->getTestDomain('userscontroller.com', [
+ 'status' => Domain::STATUS_NEW,
+ 'type' => Domain::TYPE_PUBLIC,
+ ]);
- $response = $this->actingAs($user)->get("api/v4/users");
-
- $response->assertJsonCount(1);
+ $response = $this->actingAs($user)->get("api/auth/info");
+ $json = $response->json();
$response->assertStatus(200);
+ $this->assertEquals($user->id, $json['id']);
+ $this->assertEquals($user->email, $json['email']);
+ $this->assertEquals(User::STATUS_NEW, $json['status']);
+ $this->assertTrue(is_array($json['statusInfo']));
}
- public function testUserEntitlements()
+ public function testIndex(): void
{
$userA = $this->getTestUser('UserEntitlement2A@UserEntitlement.com');
@@ -50,4 +55,91 @@
$response = $this->actingAs($user)->get("/api/v4/users/{$userA->id}");
$response->assertStatus(404);
}
+
+ public function testLogin(): void
+ {
+ // TODO
+ $this->markTestIncomplete();
+ }
+
+ public function testLogout(): void
+ {
+ // TODO
+ $this->markTestIncomplete();
+ }
+
+ public function testRefresh(): void
+ {
+ // TODO
+ $this->markTestIncomplete();
+ }
+
+ public function testShow(): void
+ {
+ $user = $this->getTestUser('UsersControllerTest1@userscontroller.com');
+ $domain = $this->getTestDomain('userscontroller.com', [
+ 'status' => Domain::STATUS_NEW,
+ 'type' => Domain::TYPE_PUBLIC,
+ ]);
+
+ $user->status = User::STATUS_NEW;
+ $user->save();
+
+ $result = UsersController::statusInfo($user);
+
+ $this->assertSame('new', $result['status']);
+ $this->assertCount(3, $result['process']);
+ $this->assertSame('user-new', $result['process'][0]['label']);
+ $this->assertSame(true, $result['process'][0]['state']);
+ $this->assertSame('user-ldap-ready', $result['process'][1]['label']);
+ $this->assertSame(false, $result['process'][1]['state']);
+ $this->assertSame('user-imap-ready', $result['process'][2]['label']);
+ $this->assertSame(false, $result['process'][2]['state']);
+
+ $user->status |= User::STATUS_LDAP_READY | User::STATUS_IMAP_READY;
+ $user->save();
+
+ $result = UsersController::statusInfo($user);
+
+ $this->assertSame('new', $result['status']);
+ $this->assertCount(3, $result['process']);
+ $this->assertSame('user-new', $result['process'][0]['label']);
+ $this->assertSame(true, $result['process'][0]['state']);
+ $this->assertSame('user-ldap-ready', $result['process'][1]['label']);
+ $this->assertSame(true, $result['process'][1]['state']);
+ $this->assertSame('user-imap-ready', $result['process'][2]['label']);
+ $this->assertSame(true, $result['process'][2]['state']);
+
+ $user->status |= User::STATUS_ACTIVE;
+ $user->save();
+ $domain->status |= Domain::STATUS_VERIFIED;
+ $domain->type = Domain::TYPE_EXTERNAL;
+ $domain->save();
+
+ $result = UsersController::statusInfo($user);
+
+ $this->assertSame('active', $result['status']);
+ $this->assertCount(7, $result['process']);
+ $this->assertSame('user-new', $result['process'][0]['label']);
+ $this->assertSame(true, $result['process'][0]['state']);
+ $this->assertSame('user-ldap-ready', $result['process'][1]['label']);
+ $this->assertSame(true, $result['process'][1]['state']);
+ $this->assertSame('user-imap-ready', $result['process'][2]['label']);
+ $this->assertSame(true, $result['process'][2]['state']);
+ $this->assertSame('domain-new', $result['process'][3]['label']);
+ $this->assertSame(true, $result['process'][3]['state']);
+ $this->assertSame('domain-ldap-ready', $result['process'][4]['label']);
+ $this->assertSame(false, $result['process'][4]['state']);
+ $this->assertSame('domain-verified', $result['process'][5]['label']);
+ $this->assertSame(true, $result['process'][5]['state']);
+ $this->assertSame('domain-confirmed', $result['process'][6]['label']);
+ $this->assertSame(false, $result['process'][6]['state']);
+
+ $user->status |= User::STATUS_DELETED;
+ $user->save();
+
+ $result = UsersController::statusInfo($user);
+
+ $this->assertSame('deleted', $result['status']);
+ }
}
diff --git a/src/tests/Feature/DomainTest.php b/src/tests/Feature/DomainTest.php
--- a/src/tests/Feature/DomainTest.php
+++ b/src/tests/Feature/DomainTest.php
@@ -17,7 +17,51 @@
{
parent::setUp();
- Domain::where('namespace', 'public-active.com')->delete();
+ Domain::where('namespace', 'public-active.com')
+ ->orWhere('namespace', 'gmail.com')->delete();
+ }
+
+ /**
+ * Test domain creating jobs
+ */
+ public function testCreateJobs(): void
+ {
+ // Fake the queue, assert that no jobs were pushed...
+ Queue::fake();
+ Queue::assertNothingPushed();
+
+ $domain = Domain::create([
+ 'namespace' => 'gmail.com',
+ 'status' => Domain::STATUS_NEW,
+ 'type' => Domain::TYPE_EXTERNAL,
+ ]);
+
+ Queue::assertPushed(\App\Jobs\ProcessDomainCreate::class, 1);
+ Queue::assertPushed(\App\Jobs\ProcessDomainCreate::class, function ($job) use ($domain) {
+ $job_domain = TestCase::getObjectProperty($job, 'domain');
+
+ return $job_domain->id === $domain->id
+ && $job_domain->namespace === $domain->namespace;
+ });
+
+ Queue::assertPushedWithChain(\App\Jobs\ProcessDomainCreate::class, [
+ \App\Jobs\ProcessDomainVerify::class,
+ ]);
+
+/*
+ FIXME: Looks like we can't really do detailed assertions on chained jobs
+ Another thing to consider is if we maybe should run these jobs
+ independently (not chained) and make sure there's no race-condition
+ in status update
+
+ Queue::assertPushed(\App\Jobs\ProcessDomainVerify::class, 1);
+ Queue::assertPushed(\App\Jobs\ProcessDomainVerify::class, function ($job) use ($domain) {
+ $job_domain = TestCase::getObjectProperty($job, 'domain');
+
+ return $job_domain->id === $domain->id
+ && $job_domain->namespace === $domain->namespace;
+ });
+*/
}
/**
@@ -29,9 +73,7 @@
$this->assertNotContains('public-active.com', $public_domains);
- // Fake the queue, assert that no jobs were pushed...
Queue::fake();
- Queue::assertNothingPushed();
$domain = Domain::create([
'namespace' => 'public-active.com',
@@ -43,14 +85,6 @@
$public_domains = Domain::getPublicDomains();
$this->assertNotContains('public-active.com', $public_domains);
- Queue::assertPushed(\App\Jobs\ProcessDomainCreate::class, 1);
- Queue::assertPushed(\App\Jobs\ProcessDomainCreate::class, function ($job) use ($domain) {
- $job_domain = TestCase::getObjectProperty($job, 'domain');
-
- return $job_domain->id === $domain->id
- && $job_domain->namespace === $domain->namespace;
- });
-
$domain = Domain::where('namespace', 'public-active.com')->first();
$domain->status = Domain::STATUS_ACTIVE;
$domain->save();
@@ -59,4 +93,26 @@
$public_domains = Domain::getPublicDomains();
$this->assertContains('public-active.com', $public_domains);
}
+
+ /**
+ * Test domain confirmation
+ *
+ * @group dns
+ */
+ public function testConfirm(): void
+ {
+ // TODO
+ $this->markTestIncomplete();
+ }
+
+ /**
+ * Test domain verification
+ *
+ * @group dns
+ */
+ public function testVerify(): void
+ {
+ // TODO
+ $this->markTestIncomplete();
+ }
}
diff --git a/src/tests/Feature/Jobs/ProcessDomainCreateTest.php b/src/tests/Feature/Jobs/ProcessDomainCreateTest.php
new file mode 100644
--- /dev/null
+++ b/src/tests/Feature/Jobs/ProcessDomainCreateTest.php
@@ -0,0 +1,50 @@
+<?php
+
+namespace Tests\Feature\Jobs;
+
+use App\Jobs\ProcessDomainCreate;
+use App\Domain;
+use Illuminate\Support\Facades\Mail;
+use Tests\TestCase;
+
+class DomainCreateTest extends TestCase
+{
+ use \Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;
+
+ /**
+ * {@inheritDoc}
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+
+ Domain::where('namespace', 'domain-create-test.com')->delete();
+ }
+
+ /**
+ * Test job handle
+ */
+ public function testHandle(): void
+ {
+ $domain = $this->getTestDomain(
+ 'domain-create-test.com',
+ [
+ 'status' => Domain::STATUS_NEW,
+ 'type' => Domain::TYPE_EXTERNAL,
+ ]
+ );
+
+ $this->assertFalse($domain->isLdapReady());
+
+ $mock = \Mockery::mock('alias:App\Backends\LDAP');
+ $mock->shouldReceive('createDomain')
+ ->once()
+ ->with($domain)
+ ->andReturn(null);
+
+ $job = new ProcessDomainCreate($domain);
+ $job->handle();
+
+ $this->assertTrue($domain->fresh()->isLdapReady());
+ }
+}
diff --git a/src/tests/Feature/Jobs/ProcessDomainVerifyTest.php b/src/tests/Feature/Jobs/ProcessDomainVerifyTest.php
new file mode 100644
--- /dev/null
+++ b/src/tests/Feature/Jobs/ProcessDomainVerifyTest.php
@@ -0,0 +1,69 @@
+<?php
+
+namespace Tests\Feature\Jobs;
+
+use App\Jobs\ProcessDomainVerify;
+use App\Domain;
+use Illuminate\Support\Facades\Mail;
+use Tests\TestCase;
+
+class DomainVerifyTest extends TestCase
+{
+ /**
+ * {@inheritDoc}
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+
+ Domain::where('namespace', 'gmail.com')
+ ->orWhere('namespace', 'some-non-existing-domain.fff')
+ ->delete();
+ }
+
+ /**
+ * Test job handle (existing domain)
+ *
+ * @group dns
+ */
+ public function testHandle(): void
+ {
+ $domain = $this->getTestDomain(
+ 'gmail.com',
+ [
+ 'status' => Domain::STATUS_NEW,
+ 'type' => Domain::TYPE_EXTERNAL,
+ ]
+ );
+
+ $this->assertFalse($domain->isVerified());
+
+ $job = new ProcessDomainVerify($domain);
+ $job->handle();
+
+ $this->assertTrue($domain->fresh()->isVerified());
+ }
+
+ /**
+ * Test job handle (non-existing domain)
+ *
+ * @group dns
+ */
+ public function testHandleNonExisting(): void
+ {
+ $domain = $this->getTestDomain(
+ 'some-non-existing-domain.fff',
+ [
+ 'status' => Domain::STATUS_NEW,
+ 'type' => Domain::TYPE_EXTERNAL,
+ ]
+ );
+
+ $this->assertFalse($domain->isVerified());
+
+ $job = new ProcessDomainVerify($domain);
+ $job->handle();
+
+ $this->assertFalse($domain->fresh()->isVerified());
+ }
+}
diff --git a/src/tests/Feature/Jobs/UserCreateTest.php b/src/tests/Feature/Jobs/UserCreateTest.php
new file mode 100644
--- /dev/null
+++ b/src/tests/Feature/Jobs/UserCreateTest.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace Tests\Feature\Jobs;
+
+use App\Jobs\ProcessUserCreate;
+use App\User;
+use Illuminate\Support\Facades\Mail;
+use Tests\TestCase;
+
+class UserCreateTest extends TestCase
+{
+ use \Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;
+
+ /**
+ * {@inheritDoc}
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+
+ User::where('email', 'new-job-user@' . \config('app.domain'))->delete();
+ }
+
+ /**
+ * Test job handle
+ */
+ public function testHandle(): void
+ {
+ $user = $this->getTestUser('new-job-user@' . \config('app.domain'));
+
+ $this->assertFalse($user->isLdapReady());
+
+ $mock = \Mockery::mock('alias:App\Backends\LDAP');
+ $mock->shouldReceive('createUser')
+ ->once()
+ ->with($user)
+ ->andReturn(null);
+
+ $job = new ProcessUserCreate($user);
+ $job->handle();
+
+ $this->assertTrue($user->fresh()->isLdapReady());
+ }
+}
diff --git a/src/tests/Feature/Jobs/UserVerify.php b/src/tests/Feature/Jobs/UserVerify.php
new file mode 100644
--- /dev/null
+++ b/src/tests/Feature/Jobs/UserVerify.php
@@ -0,0 +1,53 @@
+<?php
+
+namespace Tests\Feature\Jobs;
+
+use App\Jobs\ProcessUserVerify;
+use App\User;
+use Illuminate\Support\Facades\Mail;
+use Tests\TestCase;
+
+class UserVerifyTest extends TestCase
+{
+ use \Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;
+
+ /**
+ * {@inheritDoc}
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+
+ User::where('email', 'new-job-user@' . \config('app.domain'))->delete();
+ }
+
+ /**
+ * Test job handle
+ */
+ public function testHandle(): void
+ {
+ $user = $this->getTestUser('new-job-user@' . \config('app.domain'));
+
+ $this->assertFalse($user->isImapReady());
+
+ $mock = \Mockery::mock('alias:App\Backends\IMAP');
+ $mock->shouldReceive('verifyAccount')
+ ->once()
+ ->with($user->email)
+ ->andReturn(false);
+
+ $job = new ProcessUserVerify($user);
+ $job->handle();
+
+ $this->assertTrue($user->fresh()->isImapReady() === false);
+
+ $mock->shouldReceive('verifyAccount')
+ ->once()
+ ->with($user->email)
+ ->andReturn(true);
+
+ $job->handle();
+
+ $this->assertTrue($user->fresh()->isImapReady());
+ }
+}
diff --git a/src/tests/Feature/UserTest.php b/src/tests/Feature/UserTest.php
--- a/src/tests/Feature/UserTest.php
+++ b/src/tests/Feature/UserTest.php
@@ -35,6 +35,24 @@
return $job_user->id === $user->id
&& $job_user->email === $user->email;
});
+
+ Queue::assertPushedWithChain(\App\Jobs\ProcessUserCreate::class, [
+ \App\Jobs\ProcessUserVerify::class,
+ ]);
+/*
+ FIXME: Looks like we can't really do detailed assertions on chained jobs
+ Another thing to consider is if we maybe should run these jobs
+ independently (not chained) and make sure there's no race-condition
+ in status update
+
+ Queue::assertPushed(\App\Jobs\ProcessUserVerify::class, 1);
+ Queue::assertPushed(\App\Jobs\ProcessUserVerify::class, function ($job) use ($user) {
+ $job_user = TestCase::getObjectProperty($job, 'user');
+
+ return $job_user->id === $user->id
+ && $job_user->email === $user->email;
+ });
+*/
}
/**
diff --git a/src/tests/Unit/DomainTest.php b/src/tests/Unit/DomainTest.php
--- a/src/tests/Unit/DomainTest.php
+++ b/src/tests/Unit/DomainTest.php
@@ -10,7 +10,7 @@
/**
* Test basic Domain funtionality
*/
- public function testDomainStatus()
+ public function testDomainStatus(): void
{
$statuses = [
Domain::STATUS_NEW,
@@ -18,6 +18,8 @@
Domain::STATUS_CONFIRMED,
Domain::STATUS_SUSPENDED,
Domain::STATUS_DELETED,
+ Domain::STATUS_LDAP_READY,
+ Domain::STATUS_VERIFIED,
];
$domains = \App\Utils::powerSet($statuses);
@@ -36,13 +38,30 @@
$this->assertTrue($domain->isConfirmed() === in_array(Domain::STATUS_CONFIRMED, $domain_statuses));
$this->assertTrue($domain->isSuspended() === in_array(Domain::STATUS_SUSPENDED, $domain_statuses));
$this->assertTrue($domain->isDeleted() === in_array(Domain::STATUS_DELETED, $domain_statuses));
+ $this->assertTrue($domain->isLdapReady() === in_array(Domain::STATUS_LDAP_READY, $domain_statuses));
+ $this->assertTrue($domain->isVerified() === in_array(Domain::STATUS_VERIFIED, $domain_statuses));
}
}
+ /**
+ * Test setStatusAttribute exception
+ */
+ public function testDomainStatusInvalid(): void
+ {
+ $this->expectException(\Exception::class);
+
+ $domain = new Domain(
+ [
+ 'namespace' => 'test.com',
+ 'status' => 1234567,
+ ]
+ );
+ }
+
/**
* Test basic Domain funtionality
*/
- public function testDomainType()
+ public function testDomainType(): void
{
$types = [
Domain::TYPE_PUBLIC,
@@ -66,4 +85,25 @@
$this->assertTrue($domain->isExternal() === in_array(Domain::TYPE_EXTERNAL, $domain_types));
}
}
+
+ /**
+ * Test domain hash generation
+ */
+ public function testHash(): void
+ {
+ $domain = new Domain([
+ 'namespace' => 'test.com',
+ 'status' => Domain::STATUS_NEW,
+ ]);
+
+ $hash1 = $domain->hash(true);
+
+ $this->assertRegExp('/^[a-f0-9]{32}$/', $hash1);
+
+ $hash2 = $domain->hash();
+
+ $this->assertRegExp('/^kolab-verify=[a-f0-9]{32}$/', $hash2);
+
+ $this->assertSame($hash1, str_replace('kolab-verify=', '', $hash2));
+ }
}
diff --git a/src/tests/Unit/UserTest.php b/src/tests/Unit/UserTest.php
new file mode 100644
--- /dev/null
+++ b/src/tests/Unit/UserTest.php
@@ -0,0 +1,57 @@
+<?php
+
+namespace Tests\Unit;
+
+use App\User;
+use Tests\TestCase;
+
+class UserTest extends TestCase
+{
+ /**
+ * Test basic User funtionality
+ */
+ public function testUserStatus()
+ {
+ $statuses = [
+ User::STATUS_NEW,
+ User::STATUS_ACTIVE,
+ User::STATUS_SUSPENDED,
+ User::STATUS_DELETED,
+ User::STATUS_IMAP_READY,
+ User::STATUS_LDAP_READY,
+ ];
+
+ $users = \App\Utils::powerSet($statuses);
+
+ foreach ($users as $user_statuses) {
+ $user = new User(
+ [
+ 'email' => 'user@email.com',
+ 'status' => \array_sum($user_statuses),
+ ]
+ );
+
+ $this->assertTrue($user->isNew() === in_array(User::STATUS_NEW, $user_statuses));
+ $this->assertTrue($user->isActive() === in_array(User::STATUS_ACTIVE, $user_statuses));
+ $this->assertTrue($user->isSuspended() === in_array(User::STATUS_SUSPENDED, $user_statuses));
+ $this->assertTrue($user->isDeleted() === in_array(User::STATUS_DELETED, $user_statuses));
+ $this->assertTrue($user->isLdapReady() === in_array(User::STATUS_LDAP_READY, $user_statuses));
+ $this->assertTrue($user->isImapReady() === in_array(User::STATUS_IMAP_READY, $user_statuses));
+ }
+ }
+
+ /**
+ * Test setStatusAttribute exception
+ */
+ public function testUserStatusInvalid(): void
+ {
+ $this->expectException(\Exception::class);
+
+ $user = new User(
+ [
+ 'email' => 'user@email.com',
+ 'status' => 1234567,
+ ]
+ );
+ }
+}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, Apr 11, 7:12 AM (12 h, 39 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18859228
Default Alt Text
D925.1775891553.diff (41 KB)
Attached To
Mode
D925: Add more statuses for user/domain creation process
Attached
Detach File
Event Timeline