Page MenuHomePhorge

D3029.1775166640.diff
No OneTemporary

Authored By
Unknown
Size
26 KB
Referenced Files
None
Subscribers
None

D3029.1775166640.diff

diff --git a/src/.env.example b/src/.env.example
--- a/src/.env.example
+++ b/src/.env.example
@@ -161,6 +161,10 @@
#PASSPORT_PROXY_OAUTH_CLIENT_ID=
#PASSPORT_PROXY_OAUTH_CLIENT_SECRET=
+# Generate with ./artisan passport:client --password
+#PASSPORT_COMPANIONAPP_OAUTH_CLIENT_ID=
+#PASSPORT_COMPANIONAPP_OAUTH_CLIENT_SECRET=
+
PASSPORT_PRIVATE_KEY=
PASSPORT_PUBLIC_KEY=
diff --git a/src/app/Http/Controllers/API/V4/CompanionAppsController.php b/src/app/Http/Controllers/API/V4/CompanionAppsController.php
--- a/src/app/Http/Controllers/API/V4/CompanionAppsController.php
+++ b/src/app/Http/Controllers/API/V4/CompanionAppsController.php
@@ -2,11 +2,14 @@
namespace App\Http\Controllers\API\V4;
-use App\Http\Controllers\Controller;
+use App\Http\Controllers\ResourceController;
+use App\Utils;
+use App\Tenant;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
+use BaconQrCode;
-class CompanionAppsController extends Controller
+class CompanionAppsController extends ResourceController
{
/**
* Register a companion app.
@@ -24,6 +27,7 @@
[
'notificationToken' => 'required|min:4|max:512',
'deviceId' => 'required|min:4|max:64',
+ 'name' => 'required|max:512',
]
);
@@ -33,8 +37,9 @@
$notificationToken = $request->notificationToken;
$deviceId = $request->deviceId;
+ $name = $request->name;
- \Log::info("Registering app. Notification token: {$notificationToken} Device id: {$deviceId}");
+ \Log::info("Registering app. Notification token: {$notificationToken} Device id: {$deviceId} Name: {$name}");
$app = \App\CompanionApp::where('device_id', $deviceId)->first();
if (!$app) {
@@ -42,6 +47,7 @@
$app->user_id = $user->id;
$app->device_id = $deviceId;
$app->mfa_enabled = true;
+ $app->name = $name;
} else {
//FIXME this allows a user to probe for another users deviceId
if ($app->user_id != $user->id) {
@@ -55,4 +61,139 @@
return response()->json(['status' => 'success']);
}
+
+
+ /**
+ * Generate a QR-code image for a string
+ *
+ * @param string $data data to encode
+ *
+ * @return string
+ */
+ private static function generateQRCode($data)
+ {
+ $renderer_style = new BaconQrCode\Renderer\RendererStyle\RendererStyle(300, 1);
+ $renderer_image = new BaconQrCode\Renderer\Image\SvgImageBackEnd();
+ $renderer = new BaconQrCode\Renderer\ImageRenderer($renderer_style, $renderer_image);
+ $writer = new BaconQrCode\Writer($renderer);
+
+ return 'data:image/svg+xml;base64,' . base64_encode($writer->writeString($data));
+ }
+
+ /**
+ * Delete a companion app.
+ *
+ * @param string $id Resource identifier
+ *
+ * @return \Illuminate\Http\JsonResponse The response
+ */
+ public function destroy($id)
+ {
+ $result = \App\CompanionApp::find($id);
+ if (!$result) {
+ return $this->errorResponse(404);
+ }
+
+ $user = $this->guard()->user();
+ if ($user->id != $result->user_id) {
+ return $this->errorResponse(403);
+ }
+
+ $result->delete();
+ return response()->json([
+ 'status' => 'success',
+ 'message' => \trans("app.companion-delete-success"),
+ ]);
+ }
+
+ /**
+ * List devices.
+ *
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function index()
+ {
+ $user = $this->guard()->user();
+ $search = trim(request()->input('search'));
+ $page = intval(request()->input('page')) ?: 1;
+ $pageSize = 20;
+ $hasMore = false;
+
+ $result = \App\CompanionApp::where('user_id', $user->id);
+
+ $result = $result->orderBy('created_at')
+ ->limit($pageSize + 1)
+ ->offset($pageSize * ($page - 1))
+ ->get();
+
+ if (count($result) > $pageSize) {
+ $result->pop();
+ $hasMore = true;
+ }
+
+ // Process the result
+ $result = $result->map(
+ function ($device) {
+ return $device->toArray();
+ }
+ );
+
+ $result = [
+ 'list' => $result,
+ 'count' => count($result),
+ 'hasMore' => $hasMore,
+ ];
+
+ return response()->json($result);
+ }
+
+ /**
+ * Get the information about the specified companion app.
+ *
+ * @param string $id CompanionApp identifier
+ *
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function show($id)
+ {
+ $result = \App\CompanionApp::find($id);
+ if (!$result) {
+ return $this->errorResponse(404);
+ }
+
+ $user = $this->guard()->user();
+ if ($user->id != $result->user_id) {
+ return $this->errorResponse(403);
+ }
+
+ return response()->json($result->toArray());
+ }
+
+ /**
+ * Retrieve the pairing information encoded into a qrcode image.
+ *
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function pairing()
+ {
+ $user = $this->guard()->user();
+
+ $clientIdentifier = \App\Tenant::getConfig($user->tenant_id, 'auth.companion_app.client_id');
+ $clientSecret = \App\Tenant::getConfig($user->tenant_id, 'auth.companion_app.client_secret');
+ if (empty($clientIdentifier) || empty($clientSecret)) {
+ \Log::warning("Empty client identifier or secret. Can't generate qr-code.");
+ return $this->errorResponse(500);
+ }
+
+ $response['qrcode'] = self::generateQRCode(
+ json_encode([
+ "serverUrl" => Utils::serviceUrl('', $user->tenant_id),
+ "clientIdentifier" => \App\Tenant::getConfig($user->tenant_id, 'auth.companion_app.client_id'),
+ "clientSecret" => \App\Tenant::getConfig($user->tenant_id, 'auth.companion_app.client_secret'),
+ "username" => $user->email
+ ])
+ );
+
+ return response()->json($response);
+ }
}
diff --git a/src/app/Http/Controllers/API/V4/UsersController.php b/src/app/Http/Controllers/API/V4/UsersController.php
--- a/src/app/Http/Controllers/API/V4/UsersController.php
+++ b/src/app/Http/Controllers/API/V4/UsersController.php
@@ -192,6 +192,7 @@
'enableSettings' => $isController,
'enableUsers' => $isController,
'enableWallets' => $isController,
+ 'enableCompanionapps' => $isController && in_array('beta', $skus),
];
return array_merge($process, $result);
diff --git a/src/composer.json b/src/composer.json
--- a/src/composer.json
+++ b/src/composer.json
@@ -15,6 +15,7 @@
],
"require": {
"php": "^7.3",
+ "bacon/bacon-qr-code": "^2.0",
"barryvdh/laravel-dompdf": "^0.8.6",
"doctrine/dbal": "^2.13",
"dyrynda/laravel-nullable-fields": "*",
diff --git a/src/config/auth.php b/src/config/auth.php
--- a/src/config/auth.php
+++ b/src/config/auth.php
@@ -118,6 +118,11 @@
'client_secret' => env('PASSPORT_PROXY_OAUTH_CLIENT_SECRET'),
],
+ 'companion_app' => [
+ 'client_id' => env('PASSPORT_COMPANIONAPP_OAUTH_CLIENT_ID'),
+ 'client_secret' => env('PASSPORT_COMPANIONAPP_OAUTH_CLIENT_SECRET'),
+ ],
+
'token_expiry_minutes' => env('OAUTH_TOKEN_EXPIRY', 60),
'refresh_token_expiry_minutes' => env('OAUTH_REFRESH_TOKEN_EXPIRY', 30 * 24 * 60),
];
diff --git a/src/database/seeds/local/OauthClientSeeder.php b/src/database/seeds/local/OauthClientSeeder.php
--- a/src/database/seeds/local/OauthClientSeeder.php
+++ b/src/database/seeds/local/OauthClientSeeder.php
@@ -30,5 +30,20 @@
$client->id = \config('auth.proxy.client_id');
$client->save();
+
+ $companionAppClient = Passport::client()->forceFill([
+ 'user_id' => null,
+ 'name' => "CompanionApp Password Grant Client",
+ 'secret' => \config('auth.companion_app.client_secret'),
+ 'provider' => 'users',
+ 'redirect' => 'https://' . \config('app.website_domain'),
+ 'personal_access_client' => 0,
+ 'password_client' => 1,
+ 'revoked' => false,
+ ]);
+
+ $companionAppClient->id = \config('auth.companion_app.client_id');
+
+ $companionAppClient->save();
}
}
diff --git a/src/resources/js/fontawesome.js b/src/resources/js/fontawesome.js
--- a/src/resources/js/fontawesome.js
+++ b/src/resources/js/fontawesome.js
@@ -22,6 +22,7 @@
faExclamationCircle,
faInfoCircle,
faLock,
+ faMobile,
faKey,
faPlus,
faSearch,
@@ -58,6 +59,7 @@
faGlobe,
faInfoCircle,
faLock,
+ faMobile,
faKey,
faPlus,
faSearch,
diff --git a/src/resources/js/user/routes.js b/src/resources/js/user/routes.js
--- a/src/resources/js/user/routes.js
+++ b/src/resources/js/user/routes.js
@@ -8,6 +8,7 @@
// Note: you can pack multiple components into the same chunk, webpackChunkName
// is also used to get a sensible file name instead of numbers
+const CompanionAppComponent = () => import(/* webpackChunkName: "../user/pages" */ '../../vue/CompanionApp')
const DashboardComponent = () => import(/* webpackChunkName: "../user/pages" */ '../../vue/Dashboard')
const DistlistInfoComponent = () => import(/* webpackChunkName: "../user/pages" */ '../../vue/Distlist/Info')
const DistlistListComponent = () => import(/* webpackChunkName: "../user/pages" */ '../../vue/Distlist/List')
@@ -45,6 +46,12 @@
component: DistlistListComponent,
meta: { requiresAuth: true, perm: 'distlists' }
},
+ {
+ path: '/companion',
+ name: 'companion',
+ component: CompanionAppComponent,
+ meta: { requiresAuth: true, perm: 'companionapps' }
+ },
{
path: '/domain/:domain',
name: 'domain',
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
@@ -19,6 +19,8 @@
'chart-income' => 'Income in :currency - last 8 weeks',
'chart-users' => 'Users - last 8 weeks',
+ 'companion-delete-success' => 'The companion app has been removed.',
+
'mandate-delete-success' => 'The auto-payment has been removed.',
'mandate-update-success' => 'The auto-payment has been updated.',
diff --git a/src/resources/lang/en/ui.php b/src/resources/lang/en/ui.php
--- a/src/resources/lang/en/ui.php
+++ b/src/resources/lang/en/ui.php
@@ -38,10 +38,23 @@
'verify' => "Verify",
],
+ 'companion' => [
+ 'title' => "Companion App",
+ 'name' => "Name",
+ 'description' => "Use the Companion App on your mobile phone for advanced two factor authentication.",
+ 'pair-new' => "Pair new device",
+ 'paired' => "Paired devices",
+ 'pairing-instructions' => "Pair a new device using the following QR-Code:",
+ 'deviceid' => "Device ID",
+ 'nodevices' => "There are currently no devices",
+ 'forget-device' => "Forget Device"
+ ],
+
'dashboard' => [
'beta' => "beta",
'distlists' => "Distribution lists",
'chat' => "Video chat",
+ 'companion' => "Companion app",
'domains' => "Domains",
'invitations' => "Invitations",
'profile' => "Your profile",
diff --git a/src/resources/vue/CompanionApp.vue b/src/resources/vue/CompanionApp.vue
new file mode 100644
--- /dev/null
+++ b/src/resources/vue/CompanionApp.vue
@@ -0,0 +1,78 @@
+<template>
+ <div class="container" dusk="companionapp-component">
+ <div class="card">
+ <div class="card-body">
+ <div class="card-title">
+ <small><sup class="badge bg-primary">{{ $t('dashboard.beta') }}</sup></small>
+ {{ $t('companion.title') }}
+ </div>
+ <div class="card-text">
+ <p>
+ {{ $t('companion.description') }}
+ </p>
+ </div>
+ </div>
+ </div>
+
+ <ul class="nav nav-tabs mt-2" role="tablist">
+ <li class="nav-item">
+ <a class="nav-link active" id="tab-qrcode" href="#companion-qrcode" role="tab" aria-controls="companion-qrcode" aria-selected="true" @click="$root.tab">
+ {{ $t('companion.pair-new') }}
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" id="tab-list" href="#companion-list" role="tab" aria-controls="companion-list" aria-selected="false" @click="$root.tab">
+ {{ $t('companion.paired') }}
+ </a>
+ </li>
+ </ul>
+
+ <div class="tab-content">
+ <div class="tab-pane active" id="companion-qrcode" role="tabpanel" aria-labelledby="tab-qrcode">
+ <div class="card-body">
+ <div class="card-text">
+ <p>
+ {{ $t('companion.pairing-instructions') }}
+ </p>
+ <p>
+ <img :src="qrcode" />
+ </p>
+ </div>
+ </div>
+ </div>
+ <div class="tab-pane" id="companion-list" role="tabpanel" aria-labelledby="tab-list">
+ <div class="card-body">
+ <companionapp-list class="card-text"></companionapp-list>
+ </div>
+ </div>
+ </div>
+
+ </div>
+</template>
+
+<script>
+ import CompanionappList from './Widgets/CompanionappList'
+
+ export default {
+ components: {
+ CompanionappList
+ },
+ data() {
+ return {
+ qrcode: ""
+ }
+ },
+ mounted() {
+ this.$root.startLoading()
+
+ axios.get('/api/v4/companion/pairing')
+ .then(response => {
+ this.$root.stopLoading()
+ this.qrcode = response.data.qrcode
+ })
+ .catch(this.$root.errorHandler)
+ },
+ methods: {
+ },
+ }
+</script>
diff --git a/src/resources/vue/Dashboard.vue b/src/resources/vue/Dashboard.vue
--- a/src/resources/vue/Dashboard.vue
+++ b/src/resources/vue/Dashboard.vue
@@ -38,6 +38,10 @@
<a v-if="webmailURL" class="card link-webmail" :href="webmailURL">
<svg-icon icon="envelope"></svg-icon><span class="name">{{ $t('dashboard.webmail') }}</span>
</a>
+ <router-link v-if="status.enableCompanionapps" class="card link-companionapp" :to="{ name: 'companion' }">
+ <svg-icon icon="mobile"></svg-icon><span class="name">{{ $t('dashboard.companion') }}</span>
+ <span class="badge bg-primary">{{ $t('dashboard.beta') }}</span>
+ </router-link>
</div>
</div>
</template>
diff --git a/src/resources/vue/Widgets/CompanionappList.vue b/src/resources/vue/Widgets/CompanionappList.vue
new file mode 100644
--- /dev/null
+++ b/src/resources/vue/Widgets/CompanionappList.vue
@@ -0,0 +1,59 @@
+<template>
+ <div>
+ <table class="table table-sm m-0 entries">
+ <thead>
+ <tr>
+ <th scope="col">{{ $t('companion.name') }}</th>
+ <th scope="col">{{ $t('companion.deviceid') }}</th>
+ <th scope="col"></th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr v-for="entry in entries" :id="'entry' + entry.id" :key="entry.id">
+ <td class="description">{{ entry.name }}</td>
+ <td class="description">{{ entry.device_id }}</td>
+ <td class="selection">
+ <button class="btn btn-lg btn-link btn-action" title="$t('companion.forget-device')" type="button" @click="remove(entry.id)">
+ <svg-icon icon="trash-alt"></svg-icon>
+ </button>
+ </td>
+ </tr>
+ </tbody>
+ <list-foot :text="$t('companion.nodevices')" :colspan="3"></list-foot>
+ </table>
+ <list-more v-if="hasMore" :on-click="loadMore"></list-more>
+ </div>
+</template>
+
+<script>
+ import ListTools from './ListTools'
+
+ export default {
+ mixins: [ ListTools ],
+ props: {
+ },
+ data() {
+ return {
+ entries: []
+ }
+ },
+ mounted() {
+ this.loadMore({ reset: true })
+ },
+ methods: {
+ loadMore(params) {
+ this.listSearch('entries', '/api/v4/companion/', params)
+ },
+ remove(id) {
+ axios.delete('/api/v4/companion/' + id)
+ .then(response => {
+ if (response.data.status == 'success') {
+ this.$toast.success(response.data.message)
+ }
+ this.loadMore({ reset: true })
+ })
+ .catch(this.$root.errorHandler)
+ },
+ }
+ }
+</script>
diff --git a/src/routes/api.php b/src/routes/api.php
--- a/src/routes/api.php
+++ b/src/routes/api.php
@@ -63,13 +63,15 @@
'prefix' => $prefix . 'api/v4'
],
function () {
- Route::post('companion/register', 'API\V4\CompanionAppsController@register');
-
Route::post('auth-attempts/{id}/confirm', 'API\V4\AuthAttemptsController@confirm');
Route::post('auth-attempts/{id}/deny', 'API\V4\AuthAttemptsController@deny');
Route::get('auth-attempts/{id}/details', 'API\V4\AuthAttemptsController@details');
Route::get('auth-attempts', 'API\V4\AuthAttemptsController@index');
+ Route::get('companion/pairing', 'API\V4\CompanionAppsController@pairing');
+ Route::apiResource('companion', 'API\V4\CompanionAppsController');
+ Route::post('companion/register', 'API\V4\CompanionAppsController@register');
+
Route::apiResource('domains', 'API\V4\DomainsController');
Route::get('domains/{id}/confirm', 'API\V4\DomainsController@confirm');
Route::get('domains/{id}/skus', 'API\V4\SkusController@domainSkus');
diff --git a/src/tests/Feature/Controller/CompanionAppsTest.php b/src/tests/Feature/Controller/CompanionAppsTest.php
--- a/src/tests/Feature/Controller/CompanionAppsTest.php
+++ b/src/tests/Feature/Controller/CompanionAppsTest.php
@@ -15,8 +15,9 @@
{
parent::setUp();
- $this->deleteTestUser('UsersControllerTest1@userscontroller.com');
- $this->deleteTestDomain('userscontroller.com');
+ $this->deleteTestUser('CompanionAppsTest1@userscontroller.com');
+ $this->deleteTestUser('CompanionAppsTest2@userscontroller.com');
+ $this->deleteTestCompanionApp('testdevice');
}
/**
@@ -24,8 +25,9 @@
*/
public function tearDown(): void
{
- $this->deleteTestUser('UsersControllerTest1@userscontroller.com');
- $this->deleteTestDomain('userscontroller.com');
+ $this->deleteTestUser('CompanionAppsTest1@userscontroller.com');
+ $this->deleteTestUser('CompanionAppsTest2@userscontroller.com');
+ $this->deleteTestCompanionApp('testdevice');
parent::tearDown();
}
@@ -39,10 +41,11 @@
$notificationToken = "notificationToken";
$deviceId = "deviceId";
+ $name = "testname";
$response = $this->actingAs($user)->post(
"api/v4/companion/register",
- ['notificationToken' => $notificationToken, 'deviceId' => $deviceId]
+ ['notificationToken' => $notificationToken, 'deviceId' => $deviceId, 'name' => $name]
);
$response->assertStatus(200);
@@ -50,13 +53,14 @@
$companionApp = \App\CompanionApp::where('device_id', $deviceId)->first();
$this->assertTrue($companionApp != null);
$this->assertEquals($deviceId, $companionApp->device_id);
+ $this->assertEquals($name, $companionApp->name);
$this->assertEquals($notificationToken, $companionApp->notification_token);
// Test a token update
$notificationToken = "notificationToken2";
$response = $this->actingAs($user)->post(
"api/v4/companion/register",
- ['notificationToken' => $notificationToken, 'deviceId' => $deviceId]
+ ['notificationToken' => $notificationToken, 'deviceId' => $deviceId, 'name' => $name]
);
$response->assertStatus(200);
@@ -75,8 +79,102 @@
$user2 = $this->getTestUser('CompanionAppsTest2@userscontroller.com');
$response = $this->actingAs($user2)->post(
"api/v4/companion/register",
- ['notificationToken' => $notificationToken, 'deviceId' => $deviceId]
+ ['notificationToken' => $notificationToken, 'deviceId' => $deviceId, 'name' => $name]
);
$response->assertStatus(403);
}
+
+ public function testIndex(): void
+ {
+ $response = $this->get("api/v4/companion");
+ $response->assertStatus(401);
+
+ $user = $this->getTestUser('CompanionAppsTest1@userscontroller.com');
+
+ $companionApp = $this->getTestCompanionApp(
+ 'testdevice',
+ $user,
+ [
+ 'notification_token' => 'notificationtoken',
+ 'mfa_enabled' => 1,
+ 'name' => 'testname',
+ ]
+ );
+
+ $response = $this->actingAs($user)->get("api/v4/companion");
+ $response->assertStatus(200);
+
+ $json = $response->json();
+ $this->assertSame(1, $json['count']);
+ $this->assertCount(1, $json['list']);
+ $this->assertSame($user->id, $json['list'][0]['user_id']);
+ $this->assertSame($companionApp['device_id'], $json['list'][0]['device_id']);
+ $this->assertSame($companionApp['name'], $json['list'][0]['name']);
+ $this->assertSame($companionApp['notification_token'], $json['list'][0]['notification_token']);
+ $this->assertSame($companionApp['mfa_enabled'], $json['list'][0]['mfa_enabled']);
+
+ $user2 = $this->getTestUser('CompanionAppsTest2@userscontroller.com');
+ $response = $this->actingAs($user2)->get(
+ "api/v4/companion"
+ );
+ $response->assertStatus(200);
+
+ $json = $response->json();
+ $this->assertSame(0, $json['count']);
+ $this->assertCount(0, $json['list']);
+ }
+
+ public function testShow(): void
+ {
+ $user = $this->getTestUser('CompanionAppsTest1@userscontroller.com');
+ $companionApp = $this->getTestCompanionApp('testdevice', $user);
+
+ $response = $this->get("api/v4/companion/{$companionApp->id}");
+ $response->assertStatus(401);
+
+ $response = $this->actingAs($user)->get("api/v4/companion/aaa");
+ $response->assertStatus(404);
+
+ $response = $this->actingAs($user)->get("api/v4/companion/{$companionApp->id}");
+ $response->assertStatus(200);
+
+ $json = $response->json();
+ $this->assertSame($companionApp->id, $json['id']);
+
+ $user2 = $this->getTestUser('CompanionAppsTest2@userscontroller.com');
+ $response = $this->actingAs($user2)->get("api/v4/companion/{$companionApp->id}");
+ $response->assertStatus(403);
+ }
+
+ public function testPairing(): void
+ {
+ $response = $this->get("api/v4/companion/pairing");
+ $response->assertStatus(401);
+
+ $user = $this->getTestUser('CompanionAppsTest1@userscontroller.com');
+ $response = $this->actingAs($user)->get("api/v4/companion/pairing");
+ $response->assertStatus(200);
+
+ $json = $response->json();
+ $this->assertArrayHasKey('qrcode', $json);
+ $this->assertSame('data:image/svg+xml;base64,', substr($json['qrcode'], 0, 26));
+ }
+
+ public function testDelete(): void
+ {
+ $user = $this->getTestUser('CompanionAppsTest1@userscontroller.com');
+ $companionApp = $this->getTestCompanionApp('testdevice', $user);
+
+ $response = $this->delete("api/v4/companion/{$companionApp->id}");
+ $response->assertStatus(401);
+
+ $response = $this->actingAs($user)->delete("api/v4/companion/{$companionApp->id}");
+ $response->assertStatus(200);
+ $json = $response->json();
+ $this->assertSame('success', $json['status']);
+ $this->assertArrayHasKey('message', $json);
+
+ $companionApp = \App\CompanionApp::where('device_id', 'testdevice')->first();
+ $this->assertTrue($companionApp == null);
+ }
}
diff --git a/src/tests/TestCaseTrait.php b/src/tests/TestCaseTrait.php
--- a/src/tests/TestCaseTrait.php
+++ b/src/tests/TestCaseTrait.php
@@ -3,6 +3,7 @@
namespace Tests;
use App\Backends\LDAP;
+use App\CompanionApp;
use App\Domain;
use App\Group;
use App\Resource;
@@ -365,6 +366,24 @@
$user->forceDelete();
}
+ /**
+ * Delete a test companion app whatever it takes.
+ *
+ * @coversNothing
+ */
+ protected function deleteTestCompanionApp($deviceId)
+ {
+ Queue::fake();
+
+ $companionApp = CompanionApp::where('device_id', $deviceId)->first();
+
+ if (!$companionApp) {
+ return;
+ }
+
+ $companionApp->forceDelete();
+ }
+
/**
* Helper to access protected property of an object
*/
@@ -486,6 +505,28 @@
return $user;
}
+ /**
+ * Get CompanionApp object by deviceId, create it if needed.
+ * Skip LDAP jobs.
+ *
+ * @coversNothing
+ */
+ protected function getTestCompanionApp($deviceId, $user, $attrib = [])
+ {
+ // Disable jobs (i.e. skip LDAP oprations)
+ Queue::fake();
+ $companionApp = CompanionApp::firstOrCreate(
+ [
+ 'device_id' => $deviceId,
+ 'user_id' => $user->id,
+ 'notification_token' => '',
+ 'mfa_enabled' => 1
+ ],
+ $attrib
+ );
+ return $companionApp;
+ }
+
/**
* Call protected/private method of a class.
*

File Metadata

Mime Type
text/plain
Expires
Thu, Apr 2, 9:50 PM (15 h, 13 m ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18821059
Default Alt Text
D3029.1775166640.diff (26 KB)

Event Timeline