Page MenuHomePhorge

D4899.diff
No OneTemporary

D4899.diff

diff --git a/config.demo/src/database/seeds/PassportSeeder.php b/config.demo/src/database/seeds/PassportSeeder.php
--- a/config.demo/src/database/seeds/PassportSeeder.php
+++ b/config.demo/src/database/seeds/PassportSeeder.php
@@ -31,6 +31,21 @@
$client->id = \config('auth.proxy.client_id');
$client->save();
+ // Create a client for Webmail SSO
+ $client = Passport::client()->forceFill([
+ 'user_id' => null,
+ 'name' => 'Webmail SSO client',
+ 'secret' => \config('auth.sso.client_secret'),
+ 'provider' => 'users',
+ 'redirect' => 'https://' . \config('app.website_domain') . '/roundcubemail/index.php/login/oauth',
+ 'personal_access_client' => 0,
+ 'password_client' => 0,
+ 'revoked' => false,
+ 'allowed_scopes' => ['email', 'auth.token'],
+ ]);
+ $client->id = \config('auth.sso.client_id');
+ $client->save();
+
// Create a client for synapse oauth
$client = Passport::client()->forceFill([
'user_id' => null,
diff --git a/src/app/Auth/IdentityEntity.php b/src/app/Auth/IdentityEntity.php
--- a/src/app/Auth/IdentityEntity.php
+++ b/src/app/Auth/IdentityEntity.php
@@ -35,8 +35,16 @@
// TODO: Other claims
// TODO: Should we use this in AuthController::oauthUserInfo() for some de-duplicaton?
- return [
+ $claims = [
'email' => $this->user->email,
];
+
+ // Short living password for IMAP/SMTP
+ // We use same TTL as for the OAuth tokens, so clients can get a new password on token refresh
+ // TODO: We should create the password only when the access token scope requests it
+ $ttl = config('auth.token_expiry_minutes') * 60;
+ $claims['auth.token'] = Utils::tokenCreate((string) $this->user->id, $ttl);
+
+ return $claims;
}
}
diff --git a/src/app/Auth/Utils.php b/src/app/Auth/Utils.php
--- a/src/app/Auth/Utils.php
+++ b/src/app/Auth/Utils.php
@@ -10,10 +10,11 @@
* Create a simple authentication token
*
* @param string $userid User identifier
+ * @param int $ttl Token's time to live (in seconds)
*
* @return string|null Encrypted token, Null on failure
*/
- public static function tokenCreate($userid): ?string
+ public static function tokenCreate($userid, $ttl = 10): ?string
{
// Note: Laravel's Crypt::encryptString() creates output that is too long
// We need output string to be max. 127 characters. For that reason
@@ -23,7 +24,7 @@
$key = config('app.key');
$iv = random_bytes(openssl_cipher_iv_length($cipher));
- $data = $userid . '!' . now()->addSeconds(10)->format('YmdHis');
+ $data = $userid . '!' . now()->addSeconds($ttl)->format('YmdHis');
$value = openssl_encrypt($data, $cipher, $key, 0, $iv, $tag);
diff --git a/src/app/Http/Controllers/API/AuthController.php b/src/app/Http/Controllers/API/AuthController.php
--- a/src/app/Http/Controllers/API/AuthController.php
+++ b/src/app/Http/Controllers/API/AuthController.php
@@ -111,7 +111,10 @@
}
try {
- // league/oauth2-server/src/Grant/ code expects GET parameters, but we're using POST here
+ // OpenID handler reads parameters from the request query string (GET)
+ $request->query->replace($request->input());
+
+ // OAuth2 server's code also expects GET parameters, but we're using POST here
$psrRequest = $psrRequest->withQueryParams($request->input());
$authRequest = $server->validateAuthorizationRequest($psrRequest);
diff --git a/src/config/auth.php b/src/config/auth.php
--- a/src/config/auth.php
+++ b/src/config/auth.php
@@ -140,6 +140,11 @@
'client_secret' => env('PASSPORT_SYNAPSE_OAUTH_CLIENT_SECRET'),
],
+ 'sso' => [
+ 'client_id' => env('PASSPORT_WEBMAIL_SSO_CLIENT_ID'),
+ 'client_secret' => env('PASSPORT_WEBMAIL_SSO_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/config/openid.php b/src/config/openid.php
--- a/src/config/openid.php
+++ b/src/config/openid.php
@@ -14,6 +14,7 @@
// 'phone' => 'Information about your phone numbers',
// 'address' => 'Information about your address',
// 'login' => 'See your login information',
+ 'auth.token' => 'Kolab authentication token',
],
],
@@ -30,6 +31,9 @@
// 'company_phone',
// 'company_email',
// ],
+ 'auth.token' => [
+ 'auth.token',
+ ]
],
/**
diff --git a/src/resources/js/utils.js b/src/resources/js/utils.js
--- a/src/resources/js/utils.js
+++ b/src/resources/js/utils.js
@@ -52,7 +52,7 @@
return result
}
-const loader = '<div class="app-loader"><div class="spinner-border" role="status"><span class="visually-hidden">Loading</span></div></div>'
+const loader = '<div class="app-loader"><div class="spinner-border" role="status"><span class="visually-hidden">Loading</span></div><div class="text"></div></div>'
let isLoading = 0
@@ -63,7 +63,7 @@
* - DOMElement or jQuery collection or selector string: for element-level loader inside
* - array: for element-level loader inside the element specified in the first array element
* - undefined, null or true: for page-level loader
- * @param object $style Additional element style
+ * @param object $style Additional element style (and loader text)
*/
const startLoading = (element, style = null) => {
let small = false
@@ -101,6 +101,8 @@
loaderElement.addClass('small')
}
+ loaderElement.find('.text').text(style && style.text ? style.text : '')
+
$(element).append(loaderElement)
return loaderElement
diff --git a/src/resources/lang/en/auth.php b/src/resources/lang/en/auth.php
--- a/src/resources/lang/en/auth.php
+++ b/src/resources/lang/en/auth.php
@@ -21,7 +21,7 @@
'error.password' => "Invalid password",
'error.invalidrequest' => "Invalid authorization request.",
'error.geolocation' => "Country code mismatch",
- 'error.nofound' => "User not found",
+ 'error.notfound' => "User not found",
'error.2fa' => "Second factor failure",
'error.2fa-generic' => "Second factor failure",
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
@@ -346,6 +346,7 @@
'notfound' => "Resource not found.",
'info' => "Information",
'error' => "Error",
+ 'redirecting' => "Redirecting...",
'uploading' => "Uploading...",
'warning' => "Warning",
'success' => "Success",
diff --git a/src/resources/themes/app.scss b/src/resources/themes/app.scss
--- a/src/resources/themes/app.scss
+++ b/src/resources/themes/app.scss
@@ -73,6 +73,7 @@
display: flex;
align-items: center;
justify-content: center;
+ flex-direction: column;
z-index: 8;
.spinner-border {
@@ -82,6 +83,13 @@
color: #b2aa99;
}
+ .text {
+ width: 100%;
+ text-align: center;
+ color: #b2aa99;
+ margin-top: 1em;
+ }
+
&.small .spinner-border {
width: 25px;
height: 25px;
diff --git a/src/resources/vue/Authorize.vue b/src/resources/vue/Authorize.vue
--- a/src/resources/vue/Authorize.vue
+++ b/src/resources/vue/Authorize.vue
@@ -1,5 +1,5 @@
<template>
- <div class="container">
+ <div class="container" id="auth-container">
</div>
</template>
@@ -20,8 +20,10 @@
axios.post('/api/oauth/approve', post, { loading: true })
.then(response => {
+ // Display loading widget, redirecting may take a while
+ this.$root.startLoading(['#auth-container', { small: false, text: this.$t('msg.redirecting') }])
// Follow the redirect to the external page
- window.location.href = response.data.redirectUrl;
+ window.location.href = response.data.redirectUrl
})
.catch(this.$root.errorHandler)
}
diff --git a/src/tests/Feature/Controller/AuthTest.php b/src/tests/Feature/Controller/AuthTest.php
--- a/src/tests/Feature/Controller/AuthTest.php
+++ b/src/tests/Feature/Controller/AuthTest.php
@@ -471,15 +471,15 @@
public function testOIDCAuthorizationCodeFlow(): void
{
$user = $this->getTestUser('john@kolab.org');
- $client = \App\Auth\PassportClient::find(\config('auth.synapse.client_id'));
+ $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 scope
+ // This is essentially the same as for OAuth2, but with extended scopes
$post = [
'client_id' => $client->id,
'response_type' => 'code',
- 'scope' => 'openid email',
+ 'scope' => 'openid email auth.token',
'state' => 'state',
'nonce' => 'nonce',
];
@@ -522,8 +522,10 @@
$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
diff --git a/src/tests/Feature/Controller/WellKnownTest.php b/src/tests/Feature/Controller/WellKnownTest.php
--- a/src/tests/Feature/Controller/WellKnownTest.php
+++ b/src/tests/Feature/Controller/WellKnownTest.php
@@ -11,7 +11,7 @@
*/
public function testOpenidConfiguration(): void
{
- $href = \App\Utils::serviceUrl("/");
+ $href = \App\Utils::serviceUrl('/');
$response = $this->get('.well-known/openid-configuration');
$response->assertStatus(200)

File Metadata

Mime Type
text/plain
Expires
Fri, Sep 20, 6:25 PM (18 h, 26 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
9476454
Default Alt Text
D4899.diff (10 KB)

Event Timeline