diff --git a/docker/webapp/Dockerfile b/docker/webapp/Dockerfile --- a/docker/webapp/Dockerfile +++ b/docker/webapp/Dockerfile @@ -4,7 +4,7 @@ USER root -RUN dnf -y install findutils gnupg2 git rsync procps-ng +RUN dnf -y install findutils gnupg2 git rsync procps-ng php-sodium EXPOSE 8000 diff --git a/src/app/Http/Controllers/API/V4/VPNController.php b/src/app/Http/Controllers/API/V4/VPNController.php new file mode 100644 --- /dev/null +++ b/src/app/Http/Controllers/API/V4/VPNController.php @@ -0,0 +1,40 @@ +issuedAt(Carbon::now()->toImmutable()) + // The entitlement is hardcoded for now to default. + // Can be extended in the future based on user entitlements. + ->withClaim('entitlement', "default") + ->getToken(new Rsa\Sha256(), InMemory::plainText($signingKey)); + + return response()->json(['status' => 'ok', 'token' => $token->toString()]); + } +} diff --git a/src/composer.json b/src/composer.json --- a/src/composer.json +++ b/src/composer.json @@ -34,7 +34,8 @@ "sabre/vobject": "^4.5", "spatie/laravel-translatable": "^6.3", "spomky-labs/otphp": "~10.0.0", - "stripe/stripe-php": "^10.7" + "stripe/stripe-php": "^10.7", + "lcobucci/jwt": "^5.0" }, "require-dev": { "code-lts/doctum": "^5.5.1", diff --git a/src/config/app.php b/src/config/app.php --- a/src/config/app.php +++ b/src/config/app.php @@ -280,5 +280,9 @@ 'companion_download_link' => env( 'COMPANION_DOWNLOAD_LINK', "https://mirror.apheleia-it.ch/pub/companion-app-beta.apk" - ) + ), + + 'vpn' => [ + 'signing_key' => env('VPN_TOKEN_SIGNING_KEY', 0), + ], ]; diff --git a/src/routes/api.php b/src/routes/api.php --- a/src/routes/api.php +++ b/src/routes/api.php @@ -188,6 +188,8 @@ Route::post('support/request', [API\V4\SupportController::class, 'request']) ->withoutMiddleware(['auth:api', 'scope:api']) ->middleware(['api']); + + Route::get('vpn/token', [API\V4\VPNController::class, 'token']); } ); diff --git a/src/tests/Feature/Controller/VPNTest.php b/src/tests/Feature/Controller/VPNTest.php new file mode 100644 --- /dev/null +++ b/src/tests/Feature/Controller/VPNTest.php @@ -0,0 +1,130 @@ + $privateKey]); + + $john = $this->getTestUser('john@kolab.org'); + + $response = $this->get("api/v4/vpn/token"); + $response->assertStatus(401); + + $response = $this->actingAs($john)->get("api/v4/vpn/token"); + $response->assertStatus(200); + + $json = $response->json(); + $jwt = $json['token']; + + $parser = new Parser(new JoseEncoder()); + + /** @var \Lcobucci\JWT\UnencryptedToken $token */ + $token = $parser->parse($jwt); + + $this->assertSame("default", $token->claims()->get('entitlement')); + $this->assertSame("2022-02-02T13:00:00+00:00", $token->claims()->get( + RegisteredClaims::ISSUED_AT + )->format(\DateTimeImmutable::RFC3339)); + $this->assertSame(0, Carbon::now()->diffInSeconds(new Carbon($token->claims()->get( + RegisteredClaims::ISSUED_AT + )))); + + $validator = new Validator(); + $key = InMemory::plainText($publicKey); + $validator->assert($token, new Constraint\SignedWith(new Sha256(), $key)); + + $invalidKey = <<expectException(RequiredConstraintsViolated::class); + $key = InMemory::plainText($invalidKey); + $validator->assert($token, new Constraint\SignedWith(new Sha256(), $key)); + } +}