+
diff --git a/src/tests/Browser/AuthorizeTest.php b/src/tests/Browser/AuthorizeTest.php
index 5c898343..9eeddeab 100644
--- a/src/tests/Browser/AuthorizeTest.php
+++ b/src/tests/Browser/AuthorizeTest.php
@@ -1,80 +1,107 @@
client = \App\Auth\PassportClient::firstOrCreate(
['id' => 'test'],
[
'user_id' => null,
'name' => 'Test',
'secret' => '123',
'provider' => 'users',
- 'redirect' => 'https://kolab.org',
+ 'redirect' => \App\Utils::serviceUrl('support'),
'personal_access_client' => 0,
'password_client' => 0,
'revoked' => false,
- 'allowed_scopes' => ['email'],
+ 'allowed_scopes' => ['email', 'auth.token'],
]
);
}
/**
* {@inheritDoc}
*/
public function tearDown(): void
{
$this->client->delete();
parent::tearDown();
}
/**
* Test /oauth/authorize page
*/
public function testAuthorize(): void
{
+ $user = $this->getTestUser('john@kolab.org');
+
$url = '/oauth/authorize?' . http_build_query([
'client_id' => $this->client->id,
'response_type' => 'code',
- 'scope' => 'email',
+ 'scope' => 'email auth.token',
'state' => 'state',
+ 'redirect_uri' => $this->client->redirect,
]);
- $this->browse(function (Browser $browser) use ($url) {
- $redirect_check = "window.location.host == 'kolab.org'"
- . " && window.location.search.match(/^\?code=[a-f0-9]+&state=state/)";
+ Cache::forget("oauth-seen-{$user->id}-{$this->client->id}");
- // Unauthenticated user
+ $this->browse(function (Browser $browser) use ($url, $user) {
+ // Visit the page and expect logon form, then log in
$browser->visit($url)
->on(new Home())
- ->submitLogon('john@kolab.org', 'simple123')
- ->waitUntil($redirect_check);
+ ->submitLogon($user->email, 'simple123');
+
+ // Expect the claims form
+ $browser->waitFor('#auth-form')
+ ->assertSeeIn('#auth-form h1', "Test is asking for permission")
+ ->assertSeeIn('#auth-email', $user->email)
+ ->assertVisible('#auth-header')
+ ->assertElementsCount('#auth-claims li', 2)
+ ->assertSeeIn('#auth-claims li:nth-child(1)', "See your email address")
+ ->assertSeeIn('#auth-claims li:nth-child(2)', "Have read and write access to")
+ ->assertSeeIn('#auth-footer', $this->client->redirect)
+ ->assertSeeIn('#auth-form button.btn-success', 'Allow access')
+ ->assertSeeIn('#auth-form button.btn-danger', 'No, thanks');
+
+ // Click the "No, thanks" button
+ $browser->click('#auth-form button.btn-danger')
+ ->waitForLocation('/support')
+ ->assertScript("location.search.match(/^\?error=access_denied&state=state/) !== null");
+
+ // Visit the page again and click the "Allow access" button
+ $browser->visit($url)
+ ->waitFor('#auth-form button.btn-success')
+ ->click('#auth-form button.btn-success')
+ ->waitForLocation('/support')
+ ->assertScript("location.search.match(/^\?code=[a-f0-9]+&state=state/) !== null");
- // Authenticated user
+ // Visit the page and expect an immediate redirect
$browser->visit($url)
- ->waitUntil($redirect_check);
+ ->waitForLocation('/support')
+ ->assertScript("location.search.match(/^\?code=[a-f0-9]+&state=state/) !== null");
// Error handling (invalid response_type)
- $browser->visit('oauth/authorize?response_type=invalid')
- ->assertErrorPage(422)
- ->assertToast(Toast::TYPE_ERROR, 'Invalid value of request property: response_type.');
+ $browser->visit(str_replace('response_type=code', 'response_type=invalid', $url))
+ ->waitForLocation('/support')
+ ->assertScript("location.search.match(/^\?error=unsupported_response_type&state=state/) !== null");
});
}
}
diff --git a/src/tests/BrowserAddonTrait.php b/src/tests/BrowserAddonTrait.php
index 7fa8a34f..f2f87570 100644
--- a/src/tests/BrowserAddonTrait.php
+++ b/src/tests/BrowserAddonTrait.php
@@ -1,85 +1,85 @@
driver();
}, 50);
$this->browser = new Browser($driver);
$screenshots_dir = __DIR__ . '/Browser/screenshots/';
Browser::$storeScreenshotsAt = $screenshots_dir;
if (!file_exists($screenshots_dir)) {
mkdir($screenshots_dir, 0777, true);
}
return $this->browser;
}
/**
* (Automatically) stop the browser and driver process
*
* @afterClass
*/
protected function stopBrowser(): void
{
if ($this->browser) {
$this->browser->quit();
static::stopChromeDriver();
$this->browser = null;
}
}
/**
* Initialize and start Chrome driver
*/
protected function driver()
{
- static::startChromeDriver();
+ static::startChromeDriver(['--port=9515']);
$options = (new ChromeOptions())->addArguments([
'--lang=en_US',
'--disable-gpu',
'--headless',
'--no-sandbox',
]);
return RemoteWebDriver::create(
'http://localhost:9515',
DesiredCapabilities::chrome()->setCapability(
ChromeOptions::CAPABILITY,
$options
)
);
}
/**
* Register an "after class" tear down callback.
*
* @param \Closure $callback
*/
public static function afterClass(\Closure $callback): void
{
// This method is required by SupportsChrome trait
}
}
diff --git a/src/tests/Feature/Controller/AuthTest.php b/src/tests/Feature/Controller/AuthTest.php
index 9915f03b..4d511d54 100644
--- a/src/tests/Feature/Controller/AuthTest.php
+++ b/src/tests/Feature/Controller/AuthTest.php
@@ -1,586 +1,650 @@
app['auth']->forgetGuards();
}
/**
* {@inheritDoc}
*/
public function setUp(): void
{
parent::setUp();
$this->deleteTestUser('UsersControllerTest1@userscontroller.com');
$this->deleteTestDomain('userscontroller.com');
$this->expectedExpiry = \config('auth.token_expiry_minutes') * 60;
\App\IP4Net::where('net_number', inet_pton('127.0.0.0'))->delete();
$user = $this->getTestUser('john@kolab.org');
$user->setSetting('limit_geo', null);
}
/**
* {@inheritDoc}
*/
public function tearDown(): void
{
$this->deleteTestUser('UsersControllerTest1@userscontroller.com');
$this->deleteTestDomain('userscontroller.com');
\App\IP4Net::where('net_number', inet_pton('127.0.0.0'))->delete();
$user = $this->getTestUser('john@kolab.org');
$user->setSetting('limit_geo', null);
parent::tearDown();
}
/**
* Test fetching current user info (/api/auth/info)
*/
public function testInfo(): void
{
$user = $this->getTestUser('UsersControllerTest1@userscontroller.com', ['status' => User::STATUS_NEW]);
$domain = $this->getTestDomain('userscontroller.com', [
'status' => Domain::STATUS_NEW,
'type' => Domain::TYPE_PUBLIC,
]);
$response = $this->get("api/auth/info");
$response->assertStatus(401);
$response = $this->actingAs($user)->get("api/auth/info");
$response->assertStatus(200);
$json = $response->json();
$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']));
$this->assertTrue(is_array($json['settings']));
$this->assertTrue(!isset($json['access_token']));
// Note: Details of the content are tested in testUserResponse()
// Test token refresh via the info request
// First we log in to get the refresh token
$post = ['email' => 'john@kolab.org', 'password' => 'simple123'];
$user = $this->getTestUser('john@kolab.org');
$response = $this->post("api/auth/login", $post);
$json = $response->json();
$response = $this->actingAs($user)
->post("api/auth/info?refresh=1", ['refresh_token' => $json['refresh_token']]);
$response->assertStatus(200);
$json = $response->json();
$this->assertEquals('john@kolab.org', $json['email']);
$this->assertTrue(is_array($json['statusInfo']));
$this->assertTrue(is_array($json['settings']));
$this->assertTrue(!empty($json['access_token']));
$this->assertTrue(!empty($json['expires_in']));
}
/**
* Test fetching current user location (/api/auth/location)
*/
public function testLocation(): void
{
$user = $this->getTestUser('UsersControllerTest1@userscontroller.com');
// Authentication required
$response = $this->get("api/auth/location");
$response->assertStatus(401);
$headers = ['X-Client-IP' => '127.0.0.2'];
$response = $this->actingAs($user)->withHeaders($headers)->get("api/auth/location");
$response->assertStatus(200);
$json = $response->json();
$this->assertSame('127.0.0.2', $json['ipAddress']);
$this->assertSame('', $json['countryCode']);
\App\IP4Net::create([
'net_number' => '127.0.0.0',
'net_broadcast' => '127.255.255.255',
'net_mask' => 8,
'country' => 'US',
'rir_name' => 'test',
'serial' => 1,
]);
$response = $this->actingAs($user)->withHeaders($headers)->get("api/auth/location");
$response->assertStatus(200);
$json = $response->json();
$this->assertSame('127.0.0.2', $json['ipAddress']);
$this->assertSame('US', $json['countryCode']);
}
/**
* Test /api/auth/login
*/
public function testLogin(): string
{
$user = $this->getTestUser('john@kolab.org');
// Request with no data
$response = $this->post("api/auth/login", []);
$response->assertStatus(422);
$json = $response->json();
$this->assertSame('error', $json['status']);
$this->assertCount(2, $json['errors']);
$this->assertArrayHasKey('email', $json['errors']);
$this->assertArrayHasKey('password', $json['errors']);
// Request with invalid password
$post = ['email' => 'john@kolab.org', 'password' => 'wrong'];
$response = $this->post("api/auth/login", $post);
$response->assertStatus(401);
$json = $response->json();
$this->assertSame('error', $json['status']);
$this->assertSame('Invalid username or password.', $json['message']);
// Valid user+password
$post = ['email' => 'john@kolab.org', 'password' => 'simple123'];
$response = $this->post("api/auth/login", $post);
$json = $response->json();
$response->assertStatus(200);
$this->assertTrue(!empty($json['access_token']));
$this->assertTrue(
($this->expectedExpiry - 5) < $json['expires_in'] &&
$json['expires_in'] < ($this->expectedExpiry + 5)
);
$this->assertEquals('bearer', $json['token_type']);
$this->assertEquals($user->id, $json['id']);
$this->assertEquals($user->email, $json['email']);
$this->assertTrue(is_array($json['statusInfo']));
$this->assertTrue(is_array($json['settings']));
// Valid user+password (upper-case)
$post = ['email' => 'John@Kolab.org', 'password' => 'simple123'];
$response = $this->post("api/auth/login", $post);
$json = $response->json();
$response->assertStatus(200);
$this->assertTrue(!empty($json['access_token']));
$this->assertTrue(
($this->expectedExpiry - 5) < $json['expires_in'] &&
$json['expires_in'] < ($this->expectedExpiry + 5)
);
$this->assertEquals('bearer', $json['token_type']);
// TODO: We have browser tests for 2FA but we should probably also test it here
return $json['access_token'];
}
/**
* Test /api/auth/login with geo-lockin
*/
public function testLoginGeoLock(): void
{
$user = $this->getTestUser('john@kolab.org');
$user->setConfig(['limit_geo' => ['US']]);
$headers['X-Client-IP'] = '127.0.0.2';
$post = ['email' => 'john@kolab.org', 'password' => 'simple123'];
$response = $this->withHeaders($headers)->post("api/auth/login", $post);
$response->assertStatus(401);
$json = $response->json();
$this->assertSame("Invalid username or password.", $json['message']);
$this->assertSame('error', $json['status']);
\App\IP4Net::create([
'net_number' => '127.0.0.0',
'net_broadcast' => '127.255.255.255',
'net_mask' => 8,
'country' => 'US',
'rir_name' => 'test',
'serial' => 1,
]);
$response = $this->withHeaders($headers)->post("api/auth/login", $post);
$response->assertStatus(200);
$json = $response->json();
$this->assertTrue(!empty($json['access_token']));
$this->assertEquals($user->id, $json['id']);
}
/**
* Test /api/auth/logout
*
* @depends testLogin
*/
public function testLogout($token): void
{
// Request with no token, testing that it requires auth
$response = $this->post("api/auth/logout");
$response->assertStatus(401);
// Test the same using JSON mode
$response = $this->json('POST', "api/auth/logout", []);
$response->assertStatus(401);
// Request with invalid token
$response = $this->withHeaders(['Authorization' => 'Bearer ' . "foobar"])->post("api/auth/logout");
$response->assertStatus(401);
// Request with valid token
$response = $this->withHeaders(['Authorization' => 'Bearer ' . $token])->post("api/auth/logout");
$response->assertStatus(200);
$json = $response->json();
$this->assertEquals('success', $json['status']);
$this->assertEquals('Successfully logged out.', $json['message']);
$this->resetAuth();
// Check if it really destroyed the token?
$response = $this->withHeaders(['Authorization' => 'Bearer ' . $token])->get("api/auth/info");
$response->assertStatus(401);
}
/**
* Test /api/auth/refresh
*/
public function testRefresh(): void
{
// Request with no token, testing that it requires auth
$response = $this->post("api/auth/refresh");
$response->assertStatus(401);
// Test the same using JSON mode
$response = $this->json('POST', "api/auth/refresh", []);
$response->assertStatus(401);
// Login the user to get a valid token
$post = ['email' => 'john@kolab.org', 'password' => 'simple123'];
$response = $this->post("api/auth/login", $post);
$response->assertStatus(200);
$json = $response->json();
$token = $json['access_token'];
$user = $this->getTestUser('john@kolab.org');
// Request with a valid token
$response = $this->actingAs($user)->post("api/auth/refresh", ['refresh_token' => $json['refresh_token']]);
$response->assertStatus(200);
$json = $response->json();
$this->assertTrue(!empty($json['access_token']));
$this->assertTrue($json['access_token'] != $token);
$this->assertTrue(
($this->expectedExpiry - 5) < $json['expires_in'] &&
$json['expires_in'] < ($this->expectedExpiry + 5)
);
$this->assertEquals('bearer', $json['token_type']);
$new_token = $json['access_token'];
// TODO: Shall we invalidate the old token?
// And if the new token is working
$response = $this->withHeaders(['Authorization' => 'Bearer ' . $new_token])->get("api/auth/info");
$response->assertStatus(200);
}
/**
* Test OAuth2 Authorization Code Flow
*/
public function testOAuthAuthorizationCodeFlow(): void
{
$user = $this->getTestUser('john@kolab.org');
// Request unauthenticated, testing that it requires auth
$response = $this->post("api/oauth/approve");
$response->assertStatus(401);
// Request authenticated, invalid POST data
$post = ['response_type' => 'unknown'];
$response = $this->actingAs($user)->post("api/oauth/approve", $post);
$response->assertStatus(422);
$json = $response->json();
$this->assertSame('error', $json['status']);
- $this->assertSame('Invalid value of request property: response_type.', $json['message']);
+ $this->assertSame('unsupported_response_type', $json['error']);
+ $this->assertSame('Invalid authorization request.', $json['message']);
// Request authenticated, invalid POST data
$post = [
'client_id' => 'unknown',
'response_type' => 'code',
'scope' => 'email', // space-separated
'state' => 'state', // optional
'nonce' => 'nonce', // optional
];
$response = $this->actingAs($user)->post("api/oauth/approve", $post);
$response->assertStatus(422);
$json = $response->json();
$this->assertSame('error', $json['status']);
+ $this->assertSame('invalid_client', $json['error']);
$this->assertSame('Client authentication failed', $json['message']);
$client = \App\Auth\PassportClient::find(\config('auth.synapse.client_id'));
$post['client_id'] = $client->id;
// Request authenticated, invalid scope
$post['scope'] = 'unknown';
$response = $this->actingAs($user)->post("api/oauth/approve", $post);
$response->assertStatus(422);
$json = $response->json();
$this->assertSame('error', $json['status']);
+ $this->assertSame('invalid_scope', $json['error']);
$this->assertSame('The requested scope is invalid, unknown, or malformed', $json['message']);
// Request authenticated, valid POST data
$post['scope'] = 'email';
$response = $this->actingAs($user)->post("api/oauth/approve", $post);
$response->assertStatus(200);
$json = $response->json();
$url = $json['redirectUrl'];
parse_str(parse_url($url, \PHP_URL_QUERY), $params);
$this->assertTrue(str_starts_with($url, $client->redirect . '?'));
$this->assertCount(2, $params);
$this->assertSame('state', $params['state']);
$this->assertMatchesRegularExpression('/^[a-f0-9]{50,}$/', $params['code']);
$this->assertSame('success', $json['status']);
// Note: We do not validate the code trusting Passport to do the right thing. Should we not?
// Token endpoint tests
// Valid authorization code, but invalid secret
$post = [
'grant_type' => 'authorization_code',
'client_id' => $client->id,
'client_secret' => 'invalid',
// 'redirect_uri' => '',
'code' => $params['code'],
];
// Note: This is a 'web' route, not 'api'
$this->resetAuth(); // reset guards
$response = $this->post("/oauth/token", $post);
$response->assertStatus(401);
$json = $response->json();
$this->assertSame('invalid_client', $json['error']);
$this->assertTrue(!empty($json['error_description']));
// Valid authorization code
$post['client_secret'] = \config('auth.synapse.client_secret');
$response = $this->post("/oauth/token", $post);
$response->assertStatus(200);
$params = $response->json();
$this->assertSame('Bearer', $params['token_type']);
$this->assertTrue(!empty($params['access_token']));
$this->assertTrue(!empty($params['refresh_token']));
$this->assertTrue(!empty($params['expires_in']));
$this->assertTrue(empty($params['id_token']));
// Invalid authorization code
// Note: The code is being revoked on use, so we expect it does not work anymore
$response = $this->post("/oauth/token", $post);
$response->assertStatus(400);
$json = $response->json();
$this->assertSame('invalid_request', $json['error']);
$this->assertTrue(!empty($json['error_description']));
// Token refresh
unset($post['code']);
$post['grant_type'] = 'refresh_token';
$post['refresh_token'] = $params['refresh_token'];
$response = $this->post("/oauth/token", $post);
$response->assertStatus(200);
$json = $response->json();
$this->assertSame('Bearer', $json['token_type']);
$this->assertTrue(!empty($json['access_token']));
$this->assertTrue(!empty($json['refresh_token']));
$this->assertTrue(!empty($json['expires_in']));
$this->assertTrue(empty($json['id_token']));
$this->assertNotEquals($json['access_token'], $params['access_token']);
$this->assertNotEquals($json['refresh_token'], $params['refresh_token']);
$token = $json['access_token'];
// Validate the access token works on /oauth/userinfo endpoint
$this->resetAuth(); // reset guards
$headers = ['Authorization' => 'Bearer ' . $token];
$response = $this->withHeaders($headers)->get("/oauth/userinfo");
$response->assertStatus(200);
$json = $response->json();
$this->assertEquals($user->id, $json['sub']);
$this->assertEquals($user->email, $json['email']);
// Validate that the access token does not give access to API other than /oauth/userinfo
$this->resetAuth(); // reset guards
$response = $this->withHeaders($headers)->get("/api/auth/location");
$response->assertStatus(403);
}
+ /**
+ * Test Oauth approve end-point in ifSeen mode
+ */
+ public function testOAuthApprovePrompt(): void
+ {
+ // HTTP_HOST is not set in tests for some reason, but it's required down the line
+ $host = parse_url(\App\Utils::serviceUrl('/'), \PHP_URL_HOST);
+ $_SERVER['HTTP_HOST'] = $host;
+
+ $user = $this->getTestUser('UsersControllerTest1@userscontroller.com');
+ $client = \App\Auth\PassportClient::find(\config('auth.sso.client_id'));
+
+ $post = [
+ 'client_id' => $client->id,
+ 'response_type' => 'code',
+ 'scope' => 'openid email auth.token',
+ 'state' => 'state',
+ 'nonce' => 'nonce',
+ 'ifSeen' => '1',
+ ];
+
+ $response = $this->actingAs($user)->post("api/oauth/approve", $post);
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $claims = [
+ 'email' => 'See your email address',
+ 'auth.token' => 'Have read and write access to all your data',
+ ];
+
+ $this->assertSame('prompt', $json['status']);
+ $this->assertSame($client->name, $json['client']['name']);
+ $this->assertSame($client->redirect, $json['client']['url']);
+ $this->assertSame($claims, $json['client']['claims']);
+
+ // Approve the request
+ $post['ifSeen'] = 0;
+ $response = $this->actingAs($user)->post("api/oauth/approve", $post);
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $this->assertSame('success', $json['status']);
+ $this->assertTrue(!empty($json['redirectUrl']));
+
+ // Second request with ifSeen=1 should succeed with the code
+ $post['ifSeen'] = 1;
+ $response = $this->actingAs($user)->post("api/oauth/approve", $post);
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $this->assertSame('success', $json['status']);
+ $this->assertTrue(!empty($json['redirectUrl']));
+ }
+
/**
* Test OpenID-Connect Authorization Code Flow
*/
public function testOIDCAuthorizationCodeFlow(): void
{
+ // HTTP_HOST is not set in tests for some reason, but it's required down the line
+ $host = parse_url(\App\Utils::serviceUrl('/'), \PHP_URL_HOST);
+ $_SERVER['HTTP_HOST'] = $host;
+
$user = $this->getTestUser('john@kolab.org');
$client = \App\Auth\PassportClient::find(\config('auth.sso.client_id'));
// Note: Invalid input cases were tested above, we omit them here
// This is essentially the same as for OAuth2, but with extended scopes
$post = [
'client_id' => $client->id,
'response_type' => 'code',
'scope' => 'openid email auth.token',
'state' => 'state',
'nonce' => 'nonce',
];
$response = $this->actingAs($user)->post("api/oauth/approve", $post);
$response->assertStatus(200);
$json = $response->json();
$url = $json['redirectUrl'];
parse_str(parse_url($url, \PHP_URL_QUERY), $params);
$this->assertTrue(str_starts_with($url, $client->redirect . '?'));
$this->assertCount(2, $params);
$this->assertSame('state', $params['state']);
$this->assertMatchesRegularExpression('/^[a-f0-9]{50,}$/', $params['code']);
$this->assertSame('success', $json['status']);
// Token endpoint tests
$post = [
'grant_type' => 'authorization_code',
'client_id' => $client->id,
'client_secret' => \config('auth.synapse.client_secret'),
'code' => $params['code'],
];
$this->resetAuth(); // reset guards state
$response = $this->post("/oauth/token", $post);
$response->assertStatus(200);
$params = $response->json();
$this->assertSame('Bearer', $params['token_type']);
$this->assertTrue(!empty($params['access_token']));
$this->assertTrue(!empty($params['refresh_token']));
$this->assertTrue(!empty($params['id_token']));
$this->assertTrue(!empty($params['expires_in']));
$token = $this->parseIdToken($params['id_token']);
$this->assertSame('JWT', $token['typ']);
$this->assertSame('RS256', $token['alg']);
$this->assertSame('nonce', $token['nonce']);
$this->assertSame(url('/'), $token['iss']);
$this->assertSame($user->email, $token['email']);
$this->assertSame((string) $user->id, \App\Auth\Utils::tokenValidate($token['auth.token']));
// TODO: Validate JWT token properly
// Token refresh
unset($post['code']);
$post['grant_type'] = 'refresh_token';
$post['refresh_token'] = $params['refresh_token'];
$response = $this->post("/oauth/token", $post);
$response->assertStatus(200);
$json = $response->json();
$this->assertSame('Bearer', $json['token_type']);
$this->assertTrue(!empty($json['access_token']));
$this->assertTrue(!empty($json['refresh_token']));
$this->assertTrue(!empty($json['id_token']));
$this->assertTrue(!empty($json['expires_in']));
// Validate the access token works on /oauth/userinfo endpoint
$this->resetAuth(); // reset guards state
$headers = ['Authorization' => 'Bearer ' . $json['access_token']];
$response = $this->withHeaders($headers)->get("/oauth/userinfo");
$response->assertStatus(200);
$json = $response->json();
$this->assertEquals($user->id, $json['sub']);
$this->assertEquals($user->email, $json['email']);
// Validate that the access token does not give access to API other than /oauth/userinfo
$this->resetAuth(); // reset guards state
$response = $this->withHeaders($headers)->get("/api/auth/location");
$response->assertStatus(403);
}
/**
* Test to make sure Passport routes are disabled
*/
public function testPassportDisabledRoutes(): void
{
$this->post("/oauth/authorize", [])->assertStatus(405);
$this->post("/oauth/token/refresh", [])->assertStatus(405);
}
/**
* Parse JWT token into an array
*/
private function parseIdToken($token): array
{
[$headb64, $bodyb64, $cryptob64] = explode('.', $token);
$header = json_decode(base64_decode(strtr($headb64, '-_', '+/'), true), true);
$body = json_decode(base64_decode(strtr($bodyb64, '-_', '+/'), true), true);
return array_merge($header, $body);
}
}
diff --git a/src/tests/Feature/Controller/WellKnownTest.php b/src/tests/Feature/Controller/WellKnownTest.php
index 5cf7fa93..d0a40c73 100644
--- a/src/tests/Feature/Controller/WellKnownTest.php
+++ b/src/tests/Feature/Controller/WellKnownTest.php
@@ -1,54 +1,58 @@
get('.well-known/openid-configuration');
$response->assertStatus(200)
->assertJson([
'issuer' => $href,
'authorization_endpoint' => $href . '/oauth/authorize',
'token_endpoint' => $href . '/oauth/token',
'userinfo_endpoint' => $href . '/oauth/userinfo',
'grant_types_supported' => [
'authorization_code',
'client_credentials',
'refresh_token',
'password',
],
'response_types_supported' => [
'code'
],
'id_token_signing_alg_values_supported' => [
'RS256'
],
'scopes_supported' => [
'openid',
'email',
],
]);
}
/**
* Test ./well-known/mta-sts.txt
*/
public function testMtaSts(): void
{
$domain = \config('app.domain');
$response = $this->get('.well-known/mta-sts.txt');
$response->assertStatus(200)
->assertHeader('Content-Type', 'text/plain; charset=UTF-8')
->assertContent("version: STSv1\nmode: enforce\nmx: {$domain}\nmax_age: 604800");
}
}
diff --git a/src/tests/TestCaseDusk.php b/src/tests/TestCaseDusk.php
index b6f5a5f4..d71b7677 100644
--- a/src/tests/TestCaseDusk.php
+++ b/src/tests/TestCaseDusk.php
@@ -1,140 +1,141 @@
= 8 with PHPUnit v9. It can be removed
* when we switch to PHPUnit v10.
*
* @afterClass
* @return void
*/
public static function tearDownDuskClass()
{
static::closeAll();
foreach (static::$afterClassCallbacks as $callback) {
$callback();
}
}
/**
* Create the RemoteWebDriver instance.
*
* @return \Facebook\WebDriver\Remote\RemoteWebDriver
*/
protected function driver()
{
$options = (new ChromeOptions())->addArguments([
'--lang=en_US',
'--disable-gpu',
'--headless',
'--no-sandbox',
'--disable-dev-shm-usage',
'--use-fake-ui-for-media-stream',
'--use-fake-device-for-media-stream',
'--enable-usermedia-screen-capturing',
// '--auto-select-desktop-capture-source="Entire screen"',
'--ignore-certificate-errors',
'--incognito',
]);
// For file download handling
$prefs = [
'profile.default_content_settings.popups' => 0,
'download.default_directory' => __DIR__ . '/Browser/downloads',
];
$options->setExperimentalOption('prefs', $prefs);
if (getenv('TESTS_MODE') == 'phone') {
// Fake User-Agent string for mobile mode
$ua = 'Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/537.36'
. ' (KHTML, like Gecko) Chrome/60.0.3112.90 Mobile Safari/537.36';
$options->setExperimentalOption('mobileEmulation', ['userAgent' => $ua]);
$options->addArguments(['--window-size=375,667']);
} elseif (getenv('TESTS_MODE') == 'tablet') {
- // Fake User-Agent string for mobile mode
+ // Fake User-Agent string for tablet mode
$ua = 'Mozilla/5.0 (Linux; Android 6.0.1; vivo 1603 Build/MMB29M) AppleWebKit/537.36 '
. ' (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36';
$options->setExperimentalOption('mobileEmulation', ['userAgent' => $ua]);
$options->addArguments(['--window-size=800,640']);
} else {
$options->addArguments(['--window-size=1280,1024']);
}
// Make sure downloads dir exists and is empty
if (!file_exists(__DIR__ . '/Browser/downloads')) {
mkdir(__DIR__ . '/Browser/downloads', 0777, true);
} else {
foreach (glob(__DIR__ . '/Browser/downloads/*') as $file) {
@unlink($file);
}
}
return RemoteWebDriver::create(
'http://localhost:9515',
DesiredCapabilities::chrome()->setCapability(
ChromeOptions::CAPABILITY,
$options
)
);
}
/**
* Replace Dusk's Browser with our (extended) Browser
*/
protected function newBrowser($driver)
{
return new Browser($driver);
}
/**
* Set baseURL to the admin UI location
*/
protected static function useAdminUrl(): void
{
// This will set baseURL for all tests in this file
// If we wanted to visit both user and admin in one test
// we can also just call visit() with full url
Browser::$baseUrl = str_replace('//', '//admin.', \config('app.url'));
}
/**
* Set baseURL to the reseller UI location
*/
protected static function useResellerUrl(): void
{
// This will set baseURL for all tests in this file
// If we wanted to visit both user and admin in one test
// we can also just call visit() with full url
Browser::$baseUrl = str_replace('//', '//reseller.', \config('app.url'));
}
}