Initial Web-based management of email certificate

This commit is contained in:
Francis Lachapelle
2017-12-21 16:41:06 -05:00
parent 429a3bedb9
commit 4b355f532d
10 changed files with 391 additions and 139 deletions
@@ -76,6 +76,10 @@
"Yes" = "Yes";
"No" = "No";
/* generic messages */
"Error" = "Error";
"Success" = "Success";
/* alarms */
"Reminder" = "Reminder";
"Start" = "Start";
+73 -1
View File
@@ -20,8 +20,20 @@
#import <Foundation/NSDictionary.h>
#import <NGHttp/NGHttpRequest.h>
#import <NGObjWeb/NSException+HTTP.h>
#import <NGObjWeb/WOContext+SoObjects.h>
#define COMPILING_NGOBJWEB 1 /* httpRequest is needed in importCertificateAction */
#import <NGObjWeb/WORequest.h>
#undef COMPILING_NGOBJWEB
#import <NGObjWeb/WOResponse.h>
#import <NGMime/NGMimeBodyPart.h>
#import <NGMime/NGMimeHeaderFields.h>
#import <NGMime/NGMimeMultipartBody.h>
#import <NGMime/NGMimeType.h>
#import <NGImap4/NSString+Imap4.h>
#import <NGExtensions/NSString+misc.h>
@@ -91,7 +103,7 @@
drafts = [[self clientObject] draftsFolderInContext: context];
newDraftMessage = [drafts newDraft];
headers = [NSMutableDictionary dictionary];
save = NO;
value = [[self request] formValueForKey: @"mailto"];
@@ -177,4 +189,64 @@
return [self _performDelegationAction: @selector (removeDelegates:)];
}
- (WOResponse *) importCertificateAction
{
NSArray *parts;
NGMimeBodyPart *part;
NGMimeContentDispositionHeaderField *header;
NSData *pkcs12;
NSString *mimeType, *name, *password;
WOResponse *response;
id data;
unsigned int count, max;
password = nil;
pkcs12 = nil;
response = [self responseWithStatus: 507];
data = [[[context request] httpRequest] body];
if (![data isKindOfClass: [NSException class]])
{
parts = [data parts];
max = [parts count];
for (count = 0; count < max; count++)
{
part = [parts objectAtIndex: count];
header = (NGMimeContentDispositionHeaderField *)[part headerForKey: @"content-disposition"];
name = [header name];
if ([name isEqualToString: @"password"])
{
password = name;
}
else if ([name isEqualToString: @"file"])
{
mimeType = [(NGMimeType *)[part headerForKey: @"content-type"] stringValue];
if ([mimeType hasSuffix: @"pkcs12"])
{
pkcs12 = [part body];
}
else
{
response = [self responseWithStatus: 507];
}
}
}
}
if (password && pkcs12)
{
// TODO: append certificate to user defaults
response = [self responseWith204];
}
return response;
}
- (WOResponse *) removeCertificateAction
{
// TODO: remove certificate from user defaults
return [self responseWith204];
}
@end
+10
View File
@@ -433,6 +433,16 @@
actionClass = "UIxMailAccountActions";
actionName = "removeDelegate";
};
importCertificate = {
protectedBy = "Change Images And Files";
actionClass = "UIxMailAccountActions";
actionName = "importCertificate";
};
removeCertificate = {
protectedBy = "Change Images And Files";
actionClass = "UIxMailAccountActions";
actionName = "removeCertificate";
};
};
};
@@ -187,6 +187,8 @@
"Collected Address Book" = "Collected Address Book";
/* IMAP Accounts */
"Settings" = "Settings";
"Security" = "Security";
"Mail Account" = "Mail Account";
"New Mail Account" = "New Mail Account";
"Server Name" = "Server Name";
@@ -204,6 +206,17 @@
"Please specify a valid reply-to address." = "Please specify a valid reply-to address.";
"Specify a hostname other than the local host" = "Specify a hostname other than the local host";
"S/MIME Certificate" = "S/MIME Certificate";
"No certificate installed" = "No certificate installed";
"The SSL certificate must use the PKCS#12 (PFX) format." = "The SSL certificate must use the PKCS#12 (PFX) format.";
"Uninstall" = "Uninstall";
"Choose PKCS12 Certificate .." = "Choose PKCS12 Certificate ..";
"Certificate Password" = "Certificate Password";
"Upload" = "Upload";
"When composing a message" = "When composing a message";
"Digitally sign the message by default" = "Digitally sign the message by default";
"Always try to encrypt the message" = "Always try to encrypt the message";
/* Additional Parameters */
"Additional Parameters" = "Additional Parameters";
+216 -132
View File
@@ -20,159 +20,243 @@
</md-input-container>
</div>
</md-toolbar>
<md-dialog-content class="md-dialog-content">
<md-dialog-content>
<form id="accountForm" name="accountForm" var:href="ownPath">
<div layout="row">
<md-input-container class="md-block md-flex">
<label><var:string label:value="Server Name"/></label>
<input name="serverName" type="text" required="required"
ng-pattern="$AccountDialogController.hostnameRE"
ng-disabled="$AccountDialogController.accountId == 0"
ng-model="$AccountDialogController.account.serverName"/>
<div ng-messages="accountForm.serverName.$error" role="alert">
<div ng-message="pattern"><var:string label:value="Specify a hostname other than the local host"/></div>
</div>
</md-input-container>
<md-tabs class="md-flex" md-border-bottom="md-border-bottom" md-dynamic-height="true">
<md-input-container class="md-block" flex="30">
<label><var:string label:value="Port"/></label>
<input type="number" min="1" max="65535"
ng-disabled="$AccountDialogController.accountId == 0"
ng-model="$AccountDialogController.account.port"
placeholder=""
sg-placeholder="$AccountDialogController.defaultPort"/>
</md-input-container>
</div>
<!-- general settings -->
<md-tab id="accountSettingsView" label:label="Settings">
<md-content class="md-padding">
<md-input-container class="md-block md-input-has-value">
<label><var:string label:value="Encryption"/></label>
<md-radio-group ng-model="$AccountDialogController.account.encryption">
<div layout="row" layout-align="space-around">
<div>
<md-radio-button
ng-click="$AccountDialogController.defaultPort = 143"
ng-disabled="$AccountDialogController.accountId == 0"
value="none" class="md-primary"><var:string label:value="None"/></md-radio-button>
<div layout="row">
<md-input-container class="md-block md-flex">
<label><var:string label:value="Server Name"/></label>
<input name="serverName" type="text" required="required"
ng-pattern="$AccountDialogController.hostnameRE"
ng-disabled="$AccountDialogController.accountId == 0"
ng-model="$AccountDialogController.account.serverName"/>
<div ng-messages="accountForm.serverName.$error" role="alert">
<div ng-message="pattern"><var:string label:value="Specify a hostname other than the local host"/></div>
</div>
</md-input-container>
<md-input-container class="md-block" flex="30">
<label><var:string label:value="Port"/></label>
<input type="number" min="1" max="65535"
ng-disabled="$AccountDialogController.accountId == 0"
ng-model="$AccountDialogController.account.port"
placeholder=""
sg-placeholder="$AccountDialogController.defaultPort"/>
</md-input-container>
</div>
<div>
<md-radio-button
ng-click="$AccountDialogController.defaultPort = 993"
ng-disabled="$AccountDialogController.accountId == 0"
value="ssl"><var:string label:value="SSL"/></md-radio-button>
<md-input-container class="md-block md-input-has-value">
<label><var:string label:value="Encryption"/></label>
<md-radio-group ng-model="$AccountDialogController.account.encryption">
<div layout="row" layout-align="space-around">
<div>
<md-radio-button
ng-click="$AccountDialogController.defaultPort = 143"
ng-disabled="$AccountDialogController.accountId == 0"
value="none" class="md-primary"><var:string label:value="None"/></md-radio-button>
</div>
<div>
<md-radio-button
ng-click="$AccountDialogController.defaultPort = 993"
ng-disabled="$AccountDialogController.accountId == 0"
value="ssl"><var:string label:value="SSL"/></md-radio-button>
</div>
<div>
<md-radio-button
ng-click="$AccountDialogController.defaultPort = 143"
ng-disabled="$AccountDialogController.accountId == 0"
value="tls"><var:string label:value="TLS"/></md-radio-button>
</div>
</div>
</md-radio-group>
</md-input-container>
<div layout="row">
<md-input-container class="md-block" flex="50">
<label><var:string label:value="User Name"/></label>
<input type="text" required="required"
autocomplete="off"
ng-disabled="$AccountDialogController.accountId == 0"
ng-model="$AccountDialogController.account.userName"/>
</md-input-container>
<md-input-container class="md-block" flex="50"
ng-hide="$AccountDialogController.accountId == 0">
<label><var:string label:value="Password"/></label>
<input type="password"
autocomplete="off"
ng-model="$AccountDialogController.account.password"/>
</md-input-container>
</div>
<div>
<md-radio-button
ng-click="$AccountDialogController.defaultPort = 143"
ng-disabled="$AccountDialogController.accountId == 0"
value="tls"><var:string label:value="TLS"/></md-radio-button>
<md-input-container class="md-block md-flex">
<label><var:string label:value="Full Name"/></label>
<input type="text" required="required"
ng-disabled="$AccountDialogController.customFromIsReadonly()"
ng-model="$AccountDialogController.account.identities[0].fullName"/>
</md-input-container>
<div layout="row">
<md-input-container class="md-block" flex="50">
<label><var:string label:value="Email"/></label>
<input type="email" required="required"
ng-disabled="$AccountDialogController.customFromIsReadonly()"
ng-model="$AccountDialogController.account.identities[0].email"/>
</md-input-container>
<md-input-container class="md-block" flex="50"
ng-hide="$AccountDialogController.customFromIsReadonly()">
<label><var:string label:value="Reply To Email"/></label>
<input type="email"
autocomplete="off"
ng-model="$AccountDialogController.account.identities[0].replyTo"/>
</md-input-container>
</div>
</div>
</md-radio-group>
</md-input-container>
<div layout="row">
<md-input-container class="md-block" flex="50">
<label><var:string label:value="User Name"/></label>
<input type="text" required="required"
ng-disabled="$AccountDialogController.accountId == 0"
ng-model="$AccountDialogController.account.userName"/>
</md-input-container>
<!-- To switch between a simple text editor and the CK/HTML editor, we use a ng-if and not
a ng-class as it doesn't get initialized by the ckEditor class directive -->
<md-input-container class="md-block md-flex"
ng-if="$AccountDialogController.defaults.SOGoMailComposeMessageType == 'text'">
<label><var:string label:value="Signature"/></label>
<textarea ng-model="$AccountDialogController.account.identities[0].signature"><!-- signature --></textarea>
</md-input-container>
<div class="pseudo-input-container"
ng-if="$AccountDialogController.defaults.SOGoMailComposeMessageType == 'html'">
<label class="pseudo-input-label"><var:string label:value="Signature"/></label>
<textarea class="ck-editor"
ck-locale="$AccountDialogController.defaults.LocaleCode"
ck-options="{ 'autoGrow_minHeight': 70,
'toolbar': [['Bold', 'Italic', '-', 'Link',
'Font','FontSize','-','TextColor',
'BGColor']] }"
ck-margin="8px"
ng-model="$AccountDialogController.account.identities[0].signature"><!-- signature --></textarea>
</div>
<md-input-container class="md-block" flex="50"
ng-hide="$AccountDialogController.accountId == 0">
<label><var:string label:value="Password"/></label>
<input type="password"
ng-model="$AccountDialogController.account.password"/>
</md-input-container>
</div>
<md-input-container class="md-block md-input-has-value">
<label><var:string label:value="When I receive a request for a return receipt"/></label>
<md-radio-group ng-model="$AccountDialogController.account.receipts.receiptAction">
<md-radio-button value="ignore"><var:string label:value="Never send a return receipt"/></md-radio-button>
<md-radio-button value="allow"><var:string label:value="Allow return receipts for some messages"/></md-radio-button>
</md-radio-group>
</md-input-container>
<md-input-container class="md-block md-flex">
<label><var:string label:value="Full Name"/></label>
<input type="text" required="required"
ng-disabled="$AccountDialogController.customFromIsReadonly()"
ng-model="$AccountDialogController.account.identities[0].fullName"/>
</md-input-container>
<div layout="column" flex-offset="5"
ng-show="$AccountDialogController.account.receipts.receiptAction == 'allow'">
<md-input-container class="md-block md-flex">
<label><var:string label:value="If I'm not in the To or Cc of the message"/></label>
<md-select ng-model="$AccountDialogController.account.receipts.receiptNonRecipientAction">
<md-option const:value="ignore"><var:string label:value="Never send"/></md-option>
<md-option const:value="send"><var:string label:value="Always send"/></md-option>
<md-option const:value="ask"><var:string label:value="Ask me"/></md-option>
</md-select>
</md-input-container>
<div layout="row">
<md-input-container class="md-block" flex="50">
<label><var:string label:value="Email"/></label>
<input type="email" required="required"
ng-disabled="$AccountDialogController.customFromIsReadonly()"
ng-model="$AccountDialogController.account.identities[0].email"/>
</md-input-container>
<md-input-container class="md-block" flex="50"
ng-hide="$AccountDialogController.customFromIsReadonly()">
<label><var:string label:value="Reply To Email"/></label>
<input type="email"
ng-model="$AccountDialogController.account.identities[0].replyTo"/>
</md-input-container>
</div>
<md-input-container class="md-block md-flex">
<label><var:string label:value="If the sender is outside my domain"/></label>
<md-select ng-model="$AccountDialogController.account.receipts.receiptOutsideDomainAction">
<md-option const:value="ignore"><var:string label:value="Never send"/></md-option>
<md-option const:value="send"><var:string label:value="Always send"/></md-option>
<md-option const:value="ask"><var:string label:value="Ask me"/></md-option>
</md-select>
</md-input-container>
<!-- To switch between a simple text editor and the CK/HTML editor, we use a ng-if and not
a ng-class as it doesn't get initialized by the ckEditor class directive -->
<md-input-container class="md-block md-flex"
ng-if="$AccountDialogController.defaults.SOGoMailComposeMessageType == 'text'">
<label><var:string label:value="Signature"/></label>
<textarea ng-model="$AccountDialogController.account.identities[0].signature"><!-- signature --></textarea>
</md-input-container>
<div class="pseudo-input-container"
ng-if="$AccountDialogController.defaults.SOGoMailComposeMessageType == 'html'">
<label class="pseudo-input-label"><var:string label:value="Signature"/></label>
<textarea class="ck-editor"
ck-locale="$AccountDialogController.defaults.LocaleCode"
ck-options="{ 'autoGrow_minHeight': 70,
'toolbar': [['Bold', 'Italic', '-', 'Link',
'Font','FontSize','-','TextColor',
'BGColor']] }"
ck-margin="8px"
ng-model="$AccountDialogController.account.identities[0].signature"><!-- signature --></textarea>
</div>
<md-input-container class="md-block md-flex">
<label><var:string label:value="In all other cases"/></label>
<md-select ng-model="$AccountDialogController.account.receipts.receiptAnyAction">
<md-option const:value="ignore"><var:string label:value="Never send"/></md-option>
<md-option const:value="send"><var:string label:value="Always send"/></md-option>
<md-option const:value="ask"><var:string label:value="Ask me"/></md-option>
</md-select>
</md-input-container>
</div>
<md-input-container class="md-block md-input-has-value">
<label><var:string label:value="When I receive a request for a return receipt"/></label>
<md-radio-group ng-model="$AccountDialogController.account.receipts.receiptAction">
<md-radio-button value="ignore"><var:string label:value="Never send a return receipt"/></md-radio-button>
<md-radio-button value="allow"><var:string label:value="Allow return receipts for some messages"/></md-radio-button>
</md-radio-group>
</md-input-container>
</md-content>
</md-tab>
<div layout="column" flex-offset="5"
ng-show="$AccountDialogController.account.receipts.receiptAction == 'allow'">
<md-input-container class="md-block md-flex">
<label><var:string label:value="If I'm not in the To or Cc of the message"/></label>
<md-select ng-model="$AccountDialogController.account.receipts.receiptNonRecipientAction">
<md-option const:value="ignore"><var:string label:value="Never send"/></md-option>
<md-option const:value="send"><var:string label:value="Always send"/></md-option>
<md-option const:value="ask"><var:string label:value="Ask me"/></md-option>
</md-select>
</md-input-container>
<!-- security tab -->
<md-tab id="accountSecurityView" label:label="Security">
<md-content id="accountSecurityContent" class="md-padding" layout="column">
<md-input-container class="md-block md-flex">
<label><var:string label:value="If the sender is outside my domain"/></label>
<md-select ng-model="$AccountDialogController.account.receipts.receiptOutsideDomainAction">
<md-option const:value="ignore"><var:string label:value="Never send"/></md-option>
<md-option const:value="send"><var:string label:value="Always send"/></md-option>
<md-option const:value="ask"><var:string label:value="Ask me"/></md-option>
</md-select>
</md-input-container>
<div layout="row" layout-align="start center">
<md-input-container
class="md-flex"
ng-class="{'md-input-invalid': $AccountDialogController.form.certificateFilename.$error.fileformat}">
<label><var:string label:value="S/MIME Certificate"/></label>
<input type="text" name="certificateFilename"
ng-disabled="true"
label:placeholder="No certificate installed"
ng-model="$AccountDialogController.certificateFilename" />
<div ng-messages="accountForm.certificateFilename.$error" role="alert">
<div ng-message="fileformat"><var:string label:value="The SSL certificate must use the PKCS#12 (PFX) format."/></div>
</div>
</md-input-container>
<md-button ng-show="$AccountDialogController.certificateIsInstalled()"
ng-click="$AccountDialogController.removeCertificate()">
<var:string label:value="Uninstall"/>
</md-button>
<div ng-hide="$AccountDialogController.certificateIsInstalled()">
<input id="smime-certificate-import" type="file" class="ng-hide"
nv-file-select="nv-file-select"
uploader="$AccountDialogController.uploader"/>
<label class="md-button" for="smime-certificate-import"
ng-click="$AccountDialogController.onBeforeUploadCertificate(accountForm)"
ng-hide="$AccountDialogController.uploader.isUploading">
<span><var:string label:value="Choose PKCS12 Certificate .."/></span>
</label>
</div>
</div>
<md-input-container class="md-flex">
<label><var:string label:value="Certificate Password"/></label>
<input type="password" autocomplete="new-password"
ng-required="$AccountDialogController.uploader.queue.length"
ng-model="$AccountDialogController.certificatePassword" />
</md-input-container>
<md-input-container class="md-block md-flex">
<label><var:string label:value="In all other cases"/></label>
<md-select ng-model="$AccountDialogController.account.receipts.receiptAnyAction">
<md-option const:value="ignore"><var:string label:value="Never send"/></md-option>
<md-option const:value="send"><var:string label:value="Always send"/></md-option>
<md-option const:value="ask"><var:string label:value="Ask me"/></md-option>
</md-select>
</md-input-container>
</div>
<div layout="row" layout-align="end end">
<md-button
class="md-warn md-raised"
ng-disabled="!$AccountDialogController.certificatePassword || !$AccountDialogController.certificateFilename"
ng-click="$AccountDialogController.importCertificate()">
<var:string label:value="Upload"/>
</md-button>
</div>
<md-divider><!-- divider --></md-divider>
<div class="pseudo-input-container">
<label class="pseudo-input-label"><var:string label:value="When composing a message"/></label>
<div layout="column">
<md-checkbox
class="pseudo-input-field"
ng-model="$AccountDialogController.account.alwaysSign"
ng-disabled="!$AccountDialogController.certificateIsInstalled()"
ng-true-value="1"
ng-false-value="0"><var:string label:value="Digitally sign the message by default"/></md-checkbox>
<md-checkbox
class="pseudo-input-field"
ng-model="$AccountDialogController.account.alwaysEncrypt"
ng-disabled="!$AccountDialogController.certificateIsInstalled()"
ng-true-value="1"
ng-false-value="0"><var:string label:value="Always try to encrypt the message"/></md-checkbox>
</div>
</div>
</md-content>
</md-tab>
</md-tabs>
</form>
</md-dialog-content>
<md-dialog-actions>
<md-button type="button" ng-click="$AccountDialogController.cancel()"><var:string label:value="Cancel"/></md-button>
<md-button aria-label="{{::'OK' | loc}}"
<md-button class="md-primary" aria-label="{{::'OK' | loc}}"
ng-disabled="accountForm.$invalid"
ng-click="$AccountDialogController.save()"
ng-bind="::'OK' | loc"><!-- OK --></md-button>
@@ -8,7 +8,7 @@
xmlns:label="OGo:label"
className="UIxPageFrame"
title="title"
const:jsFiles="vendor/ckeditor/ckeditor.js, vendor/ckeditor/ck.js, vendor/ng-sortable.min.js, Common.js, Preferences.js, Preferences.services.js, Mailer.services.js, Contacts.services.js">
const:jsFiles="vendor/ckeditor/ckeditor.js, vendor/ckeditor/ck.js, vendor/angular-file-upload.min.js, vendor/ng-sortable.min.js, Common.js, Preferences.js, Preferences.services.js, Mailer.services.js, Contacts.services.js">
<main layout-fill="layout-fill" ui-view="preferences"
ng-controller="navController"><!-- preferences --> </main>
@@ -20,7 +20,8 @@
var alert = this.$modal.alert()
.title(title)
.htmlContent(content)
.ok(l('OK'));
.ok(l('OK'))
.multiple(true);
this.$modal.show(alert);
};
@@ -287,6 +287,18 @@
});
};
/**
* @function $removeCertificate
* @memberof Account.prototype
* @desc Remove any S/MIME certificate associated with the account.
* @returns a promise of the HTTP operation
*/
Account.prototype.$removeCertificate = function() {
var _this = this;
return Account.$$resource.fetch(this.id.toString(), 'removeCertificate');
};
/**
* @function updateQuota
* @memberof Account.prototype
@@ -7,8 +7,8 @@
/**
* @ngInject
*/
AccountDialogController.$inject = ['$mdDialog', 'defaults', 'account', 'accountId', 'mailCustomFromEnabled'];
function AccountDialogController($mdDialog, defaults, account, accountId, mailCustomFromEnabled) {
AccountDialogController.$inject = ['$mdDialog', '$mdToast', 'FileUploader', 'Dialog', 'sgSettings', 'Account', 'defaults', 'account', 'accountId', 'mailCustomFromEnabled'];
function AccountDialogController($mdDialog, $mdToast, FileUploader, Dialog, Settings, Account, defaults, account, accountId, mailCustomFromEnabled) {
var vm = this;
vm.defaultPort = 143;
@@ -16,6 +16,10 @@
vm.account = account;
vm.accountId = accountId;
vm.customFromIsReadonly = customFromIsReadonly;
vm.onBeforeUploadCertificate = onBeforeUploadCertificate;
vm.certificateIsInstalled = certificateIsInstalled;
vm.removeCertificate = removeCertificate;
vm.importCertificate = importCertificate;
vm.cancel = cancel;
vm.save = save;
vm.hostnameRE = accountId > 0 ? /^(?!(127\.0\.0\.1|localhost(?:\.localdomain)?)$)/ : /./;
@@ -25,13 +29,65 @@
else if (vm.account.encryption == "ssl")
vm.defaultPort = 993;
if (vm.account.certificateFilename)
vm.certificateFilename = vm.account.certificateFilename;
vm.uploader = new FileUploader({
url: [Settings.activeUser('folderURL') + 'Mail', accountId, 'importCertificate'].join('/'),
autoUpload: false,
queueLimit: 1,
filters: [{ name: filterByExtension, fn: filterByExtension }],
onAfterAddingFile: function(item) {
vm.certificateFilename = item.file.name;
},
onSuccessItem: function(item, response, status, headers) {
var el = angular.element(document.getElementById('accountSecurityContent'));
$mdToast.show(
$mdToast.simple()
.content(l('Success'))
.parent(el)
.position('top right')
.hideDelay(3000));
this.clearQueue();
},
onErrorItem: function(item, response, status, headers) {
Dialog.alert(l('Error'), l('An error occurred while importing the certificate. Verify your password.'));
}
});
function filterByExtension(item) {
var isP12File = item.type.indexOf('pkcs12') > 0 || /\.(p12|pfx)$/.test(item.name);
vm.form.certificateFilename.$setValidity('fileformat', isP12File);
return isP12File;
}
function customFromIsReadonly() {
if (accountId > 0)
return false;
return !mailCustomFromEnabled;
}
function importCertificate() {
vm.uploader.queue[0].formData = [{ password: vm.certificatePassword }];
vm.uploader.uploadAll();
}
function onBeforeUploadCertificate(form) {
vm.form = form;
vm.uploader.clearQueue();
}
function certificateIsInstalled() {
return vm.certificateFilename && vm.uploader.queue.length === 0;
}
function removeCertificate() {
var accountObject = new Account({ id: accountId });
accountObject.$removeCertificate().then(function() {
delete vm.certificateFilename;
});
}
function cancel() {
$mdDialog.cancel();
}
@@ -4,7 +4,7 @@
(function() {
'use strict';
angular.module('SOGo.PreferencesUI', ['ui.router', 'ck', 'SOGo.Common', 'SOGo.MailerUI', 'SOGo.ContactsUI', 'SOGo.Authentication', 'as.sortable'])
angular.module('SOGo.PreferencesUI', ['ui.router', 'ck', 'angularFileUpload', 'SOGo.Common', 'SOGo.MailerUI', 'SOGo.ContactsUI', 'SOGo.Authentication', 'as.sortable'])
.config(configure)
.run(runBlock);