Skip to content
20 changes: 16 additions & 4 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module.exports = {
// extends: [
// '@nextcloud',
// ],
extends: [
'@nextcloud',
],
// some unused toolgit files
ignorePatterns: [
'src/toolkit/util/file-download.js',
Expand Down Expand Up @@ -38,13 +38,25 @@ module.exports = {
},
overrides: [
{
// Vue files with TypeScript need the TypeScript parser
files: ['*.vue'],
parser: 'vue-eslint-parser',
parserOptions: {
parser: '@typescript-eslint/parser',
sourceType: 'module',
ecmaVersion: 2022,
},
rules: {
semi: ['error', 'never'],
// False positive with TypeScript generics like defineEmits<{...}>()
'func-call-spacing': 'off',
'@typescript-eslint/func-call-spacing': 'off',
// Webpack ?raw query not recognized by eslint resolver
'import/no-unresolved': ['error', { ignore: ['\\?raw$'] }],
},
},
{
files: ['*.ts', '*.cts', '*.mts', '*.tsx', '*.vue'],
files: ['*.ts', '*.cts', '*.mts', '*.tsx'],
rules: {
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
},
Expand Down
12 changes: 12 additions & 0 deletions lib/Controller/SettingsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ class SettingsController extends Controller
Config::FORCE_SSO => [ 'rw' => true, 'default' => Config::FORCE_SSO_DEFAULT, ],
Config::SHOW_TOP_LINE => [ 'rw' => true, 'default' => Config::SHOW_TOP_LINE_DEFAULT, ],
Config::ENABLE_SSL_VERIFY => [ 'rw' => true, 'default' => Config::ENABLE_SSL_VERIFY_DEFAULT, ],
Config::ENABLE_TLS_CLIENT_CERTIFICATES => [ 'rw' => true, 'default' => Config::ENABLE_TLS_CLIENT_CERTIFICATES_DEFAULT, ],
Config::CLIENT_TLS_KEY_FILE => [ 'rw' => true, 'default' => Config::CLIENT_TLS_KEY_FILE_DEFAULT, ],
Config::CLIENT_TLS_CERTIFICATE_FILE => [ 'rw' => true, 'default' => Config::CLIENT_TLS_CERTIFICATE_FILE_DEFAULT, ],
Config::CLIENT_TLS_KEY_PASSWORD => [ 'rw' => true, 'default' => Config::CLIENT_TLS_KEY_PASSWORD_DEFAULT, ],
Config::PERSONAL_ENCRYPTION => [ 'rw' => true, 'default' => Config::PERSONAL_ENCRYPTION_DEFAULT, ],
Config::CARDDAV_PROVISIONG_TAG => [ 'rw' => true, 'default' => Config::CARDDAV_PROVISIONG_TAG_DEFAULT, ],
];
Expand Down Expand Up @@ -151,6 +155,9 @@ public function setAdmin(string $setting, mixed $value, bool $force = false):Dat
case Config::EMAIL_ADDRESS_CHOICE:
case Config::FIXED_SINGLE_EMAIL_ADDRESS:
case Config::CARDDAV_PROVISIONG_TAG:
case Config::CLIENT_TLS_KEY_FILE:
case Config::CLIENT_TLS_CERTIFICATE_FILE:
case Config::CLIENT_TLS_KEY_PASSWORD:
$newValue = $value;
break;
case Config::FIXED_SINGLE_EMAIL_PASSWORD:
Expand All @@ -162,6 +169,7 @@ public function setAdmin(string $setting, mixed $value, bool $force = false):Dat
case Config::SHOW_TOP_LINE:
case Config::ENABLE_SSL_VERIFY:
case Config::PERSONAL_ENCRYPTION:
case Config::ENABLE_TLS_CLIENT_CERTIFICATES:
$newValue = filter_var($value, FILTER_VALIDATE_BOOLEAN, ['flags' => FILTER_NULL_ON_FAILURE]);
if ($newValue === null) {
return self::grumble($this->l->t(
Expand Down Expand Up @@ -240,6 +248,9 @@ public function getAdmin(?string $setting = null):DataResponse
case Config::EMAIL_ADDRESS_CHOICE:
case Config::FIXED_SINGLE_EMAIL_ADDRESS:
case Config::CARDDAV_PROVISIONG_TAG:
case Config::CLIENT_TLS_KEY_FILE:
case Config::CLIENT_TLS_CERTIFICATE_FILE:
case Config::CLIENT_TLS_KEY_PASSWORD:
break;
case Config::FIXED_SINGLE_EMAIL_PASSWORD:
$humanValue = '●●●●●●●●';
Expand All @@ -249,6 +260,7 @@ public function getAdmin(?string $setting = null):DataResponse
case Config::SHOW_TOP_LINE:
case Config::ENABLE_SSL_VERIFY:
case Config::PERSONAL_ENCRYPTION:
case Config::ENABLE_TLS_CLIENT_CERTIFICATES:
if ($humanValue !== null) {
$humanValue = $humanValue ? $this->l->t('true') : $this->l->t('false');
}
Expand Down
14 changes: 14 additions & 0 deletions lib/Service/AuthRoundCube.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,13 @@ class AuthRoundCube

/** @var string */
private $appName;
private $clientTLSKeyFile;
private $clientTLSCertificateFile;
private $clientTLSKeyPassword;

/** @var bool */
private $enableSSLVerify;
private $enableClientTLSCertificates;

private $proto;
private $host;
Expand All @@ -80,6 +84,10 @@ public function __construct(
protected ILogger $logger,
) {
$this->enableSSLVerify = $this->config->getAppValue(Config::ENABLE_SSL_VERIFY);
$this->enableClientTLSCertificates = $this->config->getAppValue(Config::ENABLE_TLS_CLIENT_CERTIFICATES);
$this->clientTLSKeyFile = $this->config->getAppValue(Config::CLIENT_TLS_KEY_FILE);
$this->clientTLSCertificateFile = $this->config->getAppValue(Config::CLIENT_TLS_CERTIFICATE_FILE);
$this->clientTLSKeyPassword = $this->config->getAppValue(Config::CLIENT_TLS_KEY_PASSWORD);

$location = $this->config->getAppValue(Config::EXTERNAL_LOCATION);
if ($location[0] == '/') {
Expand Down Expand Up @@ -458,6 +466,12 @@ private function sendRequest(string $rcQuery = '', string $method = 'POST', ?arr
$curlOpts[CURLOPT_SSL_VERIFYPEER] = false;
$curlOpts[CURLOPT_SSL_VERIFYHOST] = 0;
}
if ($this->enableClientTLSCertificates) {
$curlOpts[CURLOPT_SSLKEY] = $this->clientTLSKeyFile;
$curlOpts[CURLOPT_SSLCERT] = $this->clientTLSCertificateFile;
$curlOpts[CURLOPT_SSLKEYPASSWD] = $this->clientTLSKeyPassword;
}

curl_setopt_array($curl, $curlOpts);

$rawResponse = curl_exec($curl);
Expand Down
12 changes: 12 additions & 0 deletions lib/Service/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ class Config
public const SHOW_TOP_LINE_DEFAULT = false;
public const ENABLE_SSL_VERIFY = 'enableSSLVerify';
public const ENABLE_SSL_VERIFY_DEFAULT = true;
public const ENABLE_TLS_CLIENT_CERTIFICATES = 'enableTLSClientCertificates';
public const ENABLE_TLS_CLIENT_CERTIFICATES_DEFAULT = false;
public const CLIENT_TLS_KEY_FILE = 'clientTLSKeyFile';
public const CLIENT_TLS_KEY_FILE_DEFAULT = '';
public const CLIENT_TLS_CERTIFICATE_FILE = 'clientTLSCertificateFile';
public const CLIENT_TLS_CERTIFICATE_FILE_DEFAULT = '';
public const CLIENT_TLS_KEY_PASSWORD = 'clientTLSKeyPassword';
public const CLIENT_TLS_KEY_PASSWORD_DEFAULT = '';
public const PERSONAL_ENCRYPTION = 'personalEncryption';
public const PERSONAL_ENCRYPTION_DEFAULT = false;
public const CARDDAV_PROVISIONG_TAG = 'cardDavProvisioningTag';
Expand All @@ -78,6 +86,10 @@ class Config
self::FORCE_SSO => self::FORCE_SSO_DEFAULT,
self::SHOW_TOP_LINE => self::SHOW_TOP_LINE_DEFAULT,
self::ENABLE_SSL_VERIFY => self::ENABLE_SSL_VERIFY_DEFAULT,
self::ENABLE_TLS_CLIENT_CERTIFICATES => self::ENABLE_TLS_CLIENT_CERTIFICATES_DEFAULT,
self::CLIENT_TLS_KEY_FILE => self::CLIENT_TLS_KEY_FILE_DEFAULT,
self::CLIENT_TLS_CERTIFICATE_FILE => self::CLIENT_TLS_CERTIFICATE_FILE_DEFAULT,
self::CLIENT_TLS_KEY_PASSWORD => self::CLIENT_TLS_KEY_PASSWORD_DEFAULT,
self::PERSONAL_ENCRYPTION => self::PERSONAL_ENCRYPTION_DEFAULT,
self::CARDDAV_PROVISIONG_TAG => self::CARDDAV_PROVISIONG_TAG_DEFAULT,
];
Expand Down
39 changes: 39 additions & 0 deletions src/AdminSettings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,41 @@
>
{{ t(appName, 'Enable SSL verification.') }}
</label>
<input id="enable-tls-client-certificates"
v-model="settings.enableTLSClientCertificates"
class="checkbox"
type="checkbox"
name="enableTLSClientCertificates"
value="1"
:disabled="loading"
@change="saveSetting('enableTLSClientCertificates')"
>
<label for="enable-tls-client-certificates"
:title="t(appName, 'Enable when mutual TLS is enforced on the webserver.')"
>
{{ t(appName, 'Enable TLS Client Certificate support (mutual TLS)') }}
</label>
<TextField :value.sync="settings.clientTLSKeyFile"
type="text"
:label="t(appName, 'Filename for the client TLS private key')"
:helper-text="t(appName, 'This is the filename of the private key that this addon will use to connect to roundcube to login with, it does not need to match the user but it needs to be accepted by the webserver as a valid client certificate.')"
:disabled="loading"
@submit="saveTextInput('clientTLSKeyFile')"
/>
<TextField :value.sync="settings.clientTLSCertificateFile"
type="text"
:label="t(appName, 'Filename for the client TLS certificate')"
:helper-text="t(appName, 'This is the filename of the matching certificate for the above private key')"
:disabled="loading"
@submit="saveTextInput('clientTLSCertificateFile')"
/>
<TextField :value.sync="settings.clientTLSKeyPassword"
type="text"
:label="t(appName, 'Password for the TLS private key file')"
:helper-text="t(appName, 'The password used to protect the private key, this can be blank')"
:disabled="loading"
@submit="saveTextInput('clientTLSKeyPassword')"
/>
<input id="personal-encryption"
v-model="settings.personalEncryption"
class="checkbox"
Expand Down Expand Up @@ -266,6 +301,10 @@ const settings = reactive({
forceSSO: false,
showTopLine: false,
enableSSLVerify: true,
enableTLSClientCertificates: false,
clientTLSKeyFile: '',
clientTLSCertificateFile: '',
clientTLSKeyPassword: '',
personalEncryption: false,
cardDavProvisioningTag: '',
})
Expand Down