Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F15414033
D4899.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
10 KB
Referenced Files
None
Subscribers
None
D4899.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D4899: Single-Sign-On for Webmail
Attached
Detach File
Event Timeline
Log In to Comment