471 lines
17 KiB
C++
471 lines
17 KiB
C++
|
|
/*
|
||
|
|
* File: K7Main.cpp
|
||
|
|
* Author: SET - nmset@netcourrier.com
|
||
|
|
* License : GPL v2
|
||
|
|
* Copyright SET - © 2019
|
||
|
|
*
|
||
|
|
* Created on 7 octobre 2019, 21:29
|
||
|
|
*/
|
||
|
|
|
||
|
|
#include "K7Main.h"
|
||
|
|
#include "global.h"
|
||
|
|
#include <iostream>
|
||
|
|
#include <Wt/WBreak.h>
|
||
|
|
#include <Wt/WPushButton.h>
|
||
|
|
#include <Wt/WGridLayout.h>
|
||
|
|
#include <Wt/WHBoxLayout.h>
|
||
|
|
#include <Wt/WVBoxLayout.h>
|
||
|
|
#include <Wt/WLink.h>
|
||
|
|
#include "GpgMEWorker.h"
|
||
|
|
#include "GpgMECWorker.h"
|
||
|
|
|
||
|
|
using namespace std;
|
||
|
|
|
||
|
|
K7Main::K7Main(const WEnvironment& env)
|
||
|
|
: WApplication(env)
|
||
|
|
{
|
||
|
|
m_config = NULL;
|
||
|
|
WApplication::setTitle(_APPNAME_);
|
||
|
|
const WString bundle = WApplication::appRoot() + _APPNAME_;
|
||
|
|
WApplication::instance()->messageResourceBundle().use(bundle.toUTF8());
|
||
|
|
if (!env.javaScript() || !env.ajax())
|
||
|
|
{
|
||
|
|
root()->addWidget(cpp14::make_unique<WText>(TR("NoJS")));
|
||
|
|
WApplication::quit(WString::Empty);
|
||
|
|
}
|
||
|
|
// We want users to authenticate with an X509 certificate
|
||
|
|
if (WApplication::instance()->environment().sslInfo() == NULL)
|
||
|
|
{
|
||
|
|
root()->addWidget(cpp14::make_unique<WText>(TR("NoAuth")));
|
||
|
|
WApplication::quit(WString::Empty);
|
||
|
|
}
|
||
|
|
// Translate UID trusts to string
|
||
|
|
UidValidities[UserID::Validity::Full] = TR("UidFull");
|
||
|
|
UidValidities[UserID::Validity::Marginal] = TR("UidMarginal");
|
||
|
|
UidValidities[UserID::Validity::Never] = TR("UidNever");
|
||
|
|
UidValidities[UserID::Validity::Ultimate] = TR("UidUltimate");
|
||
|
|
UidValidities[UserID::Validity::Undefined] = TR("UidUndefined");
|
||
|
|
UidValidities[UserID::Validity::Unknown] = TR("UidUnknown");
|
||
|
|
m_uploader = NULL; m_deleter = NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
K7Main::~K7Main()
|
||
|
|
{
|
||
|
|
delete m_config; delete m_uploader; delete m_deleter;
|
||
|
|
}
|
||
|
|
|
||
|
|
void
|
||
|
|
K7Main::Create()
|
||
|
|
{
|
||
|
|
WContainerWidget * cwHeader = new WContainerWidget();
|
||
|
|
WHBoxLayout * hblHeader = new WHBoxLayout();
|
||
|
|
cwHeader->setLayout(unique_ptr<WHBoxLayout> (hblHeader));
|
||
|
|
hblHeader->addWidget(cpp14::make_unique<WText>(_APPNAME_));
|
||
|
|
// Error messages will go here
|
||
|
|
m_tmwMessage = new TransientMessageWidget();
|
||
|
|
m_tmwMessage->setTextAlignment(AlignmentFlag::Right);
|
||
|
|
hblHeader->addWidget(unique_ptr<TransientMessageWidget>(m_tmwMessage));
|
||
|
|
root()->addWidget(unique_ptr<WContainerWidget> (cwHeader));
|
||
|
|
hblHeader->setStretchFactor(m_tmwMessage, 1);
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Load config JSON file.
|
||
|
|
* On error, just abort, AppConfig will print an error message in m_tmwMessage
|
||
|
|
*/
|
||
|
|
m_config = new AppConfig(m_tmwMessage);
|
||
|
|
if (!m_config->LoadConfig())
|
||
|
|
return;
|
||
|
|
|
||
|
|
m_cwMain = new WContainerWidget();
|
||
|
|
WGridLayout * grlMain = new WGridLayout();
|
||
|
|
grlMain->setColumnStretch(0, 1);
|
||
|
|
grlMain->setColumnStretch(1, 0);
|
||
|
|
m_cwMain->setLayout(unique_ptr<WGridLayout> (grlMain));
|
||
|
|
// Add a search zone : line edit and button
|
||
|
|
m_leSearch = new WLineEdit();
|
||
|
|
grlMain->addWidget(unique_ptr<WLineEdit> (m_leSearch), 0, 0);
|
||
|
|
m_leSearch->enterPressed().connect(this, &K7Main::Search);
|
||
|
|
WPushButton * btnSearch = new WPushButton(TR("Search"));
|
||
|
|
btnSearch->setToolTip(TR("TTTSearch"));
|
||
|
|
grlMain->addWidget(unique_ptr<WPushButton> (btnSearch), 0, 1);
|
||
|
|
btnSearch->clicked().connect(this, &K7Main::Search);
|
||
|
|
// Add a result zone as a tree table
|
||
|
|
m_ttbKeys = new WTreeTable();
|
||
|
|
grlMain->addWidget(unique_ptr<WTreeTable> (m_ttbKeys), 1, 0);
|
||
|
|
|
||
|
|
WContainerWidget * cwButtons = new WContainerWidget();
|
||
|
|
WVBoxLayout * vblButtons = new WVBoxLayout();
|
||
|
|
cwButtons->setLayout(unique_ptr<WVBoxLayout> (vblButtons));
|
||
|
|
// Add an import button if current user is allowed
|
||
|
|
if (m_config->CanImport())
|
||
|
|
{
|
||
|
|
m_btnUpload = new WPushButton(TR("Upload"));
|
||
|
|
m_btnUpload->setToolTip(TR("TTTUpload"));
|
||
|
|
vblButtons->addWidget(unique_ptr<WPushButton> (m_btnUpload));
|
||
|
|
m_btnUpload->clicked().connect(this, &K7Main::PopupUploader);
|
||
|
|
m_btnImport = new WPushButton(TR("Import"));
|
||
|
|
m_btnImport->setToolTip(TR("TTTImport"));
|
||
|
|
vblButtons->addWidget(unique_ptr<WPushButton> (m_btnImport));
|
||
|
|
m_btnImport->clicked().connect(this, &K7Main::DoImportKey);
|
||
|
|
m_btnImport->hide();
|
||
|
|
}
|
||
|
|
// Add a delete button if current user is allowed
|
||
|
|
if (m_config->CanDelete())
|
||
|
|
{
|
||
|
|
m_btnDelete = new WPushButton(TR("Delete"));
|
||
|
|
m_btnDelete->setToolTip(TR("TTTDelete"));
|
||
|
|
vblButtons->addWidget(unique_ptr<WPushButton> (m_btnDelete));
|
||
|
|
m_btnDelete->clicked().connect(this, &K7Main::PopupDeleter);
|
||
|
|
m_btnDelete->hide();
|
||
|
|
}
|
||
|
|
vblButtons->addSpacing(300);
|
||
|
|
grlMain->addWidget(unique_ptr<WContainerWidget> (cwButtons), 1, 1);
|
||
|
|
|
||
|
|
// Add and hide detail tables
|
||
|
|
m_ttbUids = new WTreeTable();
|
||
|
|
grlMain->addWidget(unique_ptr<WTreeTable> (m_ttbUids), 2, 0);
|
||
|
|
m_ttbUids->hide();
|
||
|
|
m_ttbSubKeys = new WTreeTable();
|
||
|
|
grlMain->addWidget(unique_ptr<WTreeTable> (m_ttbSubKeys), 3, 0);
|
||
|
|
m_ttbSubKeys->hide();
|
||
|
|
|
||
|
|
root()->addWidget(cpp14::make_unique<WBreak>());
|
||
|
|
root()->addWidget(unique_ptr<WContainerWidget> (m_cwMain));
|
||
|
|
#ifdef DEVTIME
|
||
|
|
// Save my fingertips.
|
||
|
|
m_leSearch->setText("s");
|
||
|
|
#endif
|
||
|
|
}
|
||
|
|
|
||
|
|
void K7Main::Search()
|
||
|
|
{
|
||
|
|
#ifndef DEVTIME
|
||
|
|
// A reasonable minimal search criteria
|
||
|
|
if (!m_leSearch->text().empty() && m_leSearch->text().toUTF8().length() < 3)
|
||
|
|
{
|
||
|
|
m_tmwMessage->SetText(TR("CriteriaTooShort"));
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
m_ttbSubKeys->hide();
|
||
|
|
m_ttbUids->hide();
|
||
|
|
Error e;
|
||
|
|
vector<WString> configPrivKeys = m_config->PrivateKeyIds();
|
||
|
|
GpgMEWorker gpgw;
|
||
|
|
vector<GpgME::Key> privkList, pubkList;
|
||
|
|
|
||
|
|
// Find private keys if any is declared in config file for current user.
|
||
|
|
for (uint i = 0; i < configPrivKeys.size(); i++)
|
||
|
|
{
|
||
|
|
/*
|
||
|
|
* User may manage more than one private key.
|
||
|
|
* configPrivKeys.at(i) : we want this to be a full key id(short, normal or fpr)
|
||
|
|
* But it can be any string in the config file (name, email...).
|
||
|
|
* lst can hence contain many keys.
|
||
|
|
*/
|
||
|
|
vector<GpgME::Key> lst = gpgw.FindKeys(configPrivKeys.at(i).toUTF8().c_str(), true, e);
|
||
|
|
if (e.code() != 0)
|
||
|
|
{
|
||
|
|
privkList.clear();
|
||
|
|
m_tmwMessage->SetText(e.asString());
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
/*
|
||
|
|
* If any key is not a full key id, abort.
|
||
|
|
*/
|
||
|
|
for (uint j = 0; j < lst.size(); j++)
|
||
|
|
{
|
||
|
|
const GpgME::Key k = lst.at(j);
|
||
|
|
if (!ConfigKeyIdMatchesKey(k, configPrivKeys.at(i)))
|
||
|
|
{
|
||
|
|
m_tmwMessage->SetText(configPrivKeys.at(i) + TR("BadConfigKeyId"));
|
||
|
|
privkList.clear();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
privkList.push_back(k);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
// Find public keys as per criteria
|
||
|
|
if (!m_leSearch->text().empty())
|
||
|
|
{
|
||
|
|
pubkList = gpgw.FindKeys(m_leSearch->text().toUTF8().c_str(), false, e);
|
||
|
|
if (e.code() != 0)
|
||
|
|
{
|
||
|
|
pubkList.clear();
|
||
|
|
m_tmwMessage->SetText(e.asString());
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (m_ttbKeys->columnCount() == 1) {
|
||
|
|
m_ttbKeys->addColumn(TR("ID"), 120);
|
||
|
|
m_ttbKeys->addColumn(TR("Fpr"), 300);
|
||
|
|
}
|
||
|
|
// The previous tree root is auto deleted by the use of smart pointers !!
|
||
|
|
WTreeTableNode * rootNode = new WTreeTableNode(TR("Keys"));
|
||
|
|
rootNode->setChildCountPolicy(ChildCountPolicy::Enabled);
|
||
|
|
m_ttbKeys->setTreeRoot(unique_ptr<WTreeTableNode> (rootNode), TR("KeyRing"));
|
||
|
|
rootNode->expand();
|
||
|
|
// Show available keys
|
||
|
|
if (pubkList.size())
|
||
|
|
DisplayKeys(pubkList, TR("Publics"), true);
|
||
|
|
if (privkList.size() && m_config->PrivateKeyIds().size() > 0)
|
||
|
|
DisplayKeys(privkList, TR("Secrets"), false);
|
||
|
|
}
|
||
|
|
|
||
|
|
bool K7Main::ConfigKeyIdMatchesKey(const GpgME::Key& k, const WString& configKeyId) const
|
||
|
|
{
|
||
|
|
// We want key identifier in config file to be real and complete.
|
||
|
|
return (configKeyId == WString(k.shortKeyID())
|
||
|
|
|| configKeyId == WString(k.keyID())
|
||
|
|
|| configKeyId == WString(k.primaryFingerprint()));
|
||
|
|
}
|
||
|
|
|
||
|
|
WString K7Main::MakeDateTimeLabel(time_t ticks)
|
||
|
|
{
|
||
|
|
std::chrono::minutes offset = WApplication::instance()->environment().timeZoneOffset();
|
||
|
|
const WDateTime dt = WDateTime::fromTime_t(ticks + (time_t) offset.count() * 60);
|
||
|
|
return dt.toString(WString("yyyy-MM-dd - hh:mm:ss"));
|
||
|
|
}
|
||
|
|
|
||
|
|
void K7Main::DisplayKeys(const vector<GpgME::Key>& kList, const WString& grpLabel, bool expand)
|
||
|
|
{
|
||
|
|
WTreeTableNode * grpNode = new WTreeTableNode(grpLabel);
|
||
|
|
m_ttbKeys->treeRoot()->addChildNode(unique_ptr<WTreeTableNode> (grpNode));
|
||
|
|
for (uint i = 0; i < kList.size(); i++)
|
||
|
|
{
|
||
|
|
const GpgME::Key k = kList.at(i);
|
||
|
|
WTreeTableNode * keyNode = new WTreeTableNode(k.userID(0).name());
|
||
|
|
WLink ln;
|
||
|
|
ln.setUrl(WString(L"javascript:void(0)").toUTF8());
|
||
|
|
WAnchor * anc = new WAnchor(ln, k.shortKeyID());
|
||
|
|
anc->setId(k.shortKeyID());
|
||
|
|
// We use widget attribute values to buffer properties.
|
||
|
|
anc->setAttributeValue("hasSecret", k.hasSecret() ? "1" : "0");
|
||
|
|
anc->clicked().connect(std::bind(&K7Main::OnKeyAnchorClicked, this, anc));
|
||
|
|
keyNode->setColumnWidget(1, unique_ptr<WAnchor> (anc));
|
||
|
|
keyNode->setColumnWidget(2, cpp14::make_unique<WText> (k.primaryFingerprint()));
|
||
|
|
grpNode->addChildNode(unique_ptr<WTreeTableNode> (keyNode));
|
||
|
|
}
|
||
|
|
if (expand)
|
||
|
|
grpNode->expand();
|
||
|
|
}
|
||
|
|
|
||
|
|
void K7Main::OnKeyAnchorClicked(WAnchor * source)
|
||
|
|
{
|
||
|
|
// Show key details
|
||
|
|
const WString hasSecret = source->attributeValue("hasSecret");
|
||
|
|
bool secret = true;
|
||
|
|
if (hasSecret == WString("0"))
|
||
|
|
secret = false;
|
||
|
|
const string id = source->id();
|
||
|
|
DisplayUids(id, secret);
|
||
|
|
DisplaySubKeys(id, secret);
|
||
|
|
if (m_config->CanDelete()) // m_btnDelete is NULL otherwise
|
||
|
|
m_btnDelete->setHidden(!CanKeyBeDeleted(id));
|
||
|
|
}
|
||
|
|
|
||
|
|
void K7Main::DisplayUids(const WString& fullKeyID, bool secret)
|
||
|
|
{
|
||
|
|
// Show UID details for a key
|
||
|
|
Error e;
|
||
|
|
GpgMEWorker gpgw;
|
||
|
|
m_ttbUids->hide();
|
||
|
|
const GpgME::Key k = gpgw.FindKey(fullKeyID.toUTF8().c_str(), e, secret);
|
||
|
|
if (e.code() != 0)
|
||
|
|
{
|
||
|
|
m_tmwMessage->SetText(e.asString());
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (m_ttbUids->columnCount() == 1)
|
||
|
|
{
|
||
|
|
m_ttbUids->addColumn(TR("Email"), 200);
|
||
|
|
m_ttbUids->addColumn(TR("Trust"), 100);
|
||
|
|
m_ttbUids->addColumn(TR("Comment"), 300);
|
||
|
|
}
|
||
|
|
WTreeTableNode * rootNode = new WTreeTableNode(fullKeyID);
|
||
|
|
rootNode->setChildCountPolicy(ChildCountPolicy::Enabled);
|
||
|
|
m_ttbUids->setTreeRoot(unique_ptr<WTreeTableNode> (rootNode), TR("UIDs"));
|
||
|
|
rootNode->expand();
|
||
|
|
for (uint i = 0; i < k.numUserIDs(); i++)
|
||
|
|
{
|
||
|
|
UserID uid = k.userID(i);
|
||
|
|
WTreeTableNode * uidNode = new WTreeTableNode(uid.name());
|
||
|
|
uidNode->setColumnWidget(1, cpp14::make_unique<WText> (uid.email()));
|
||
|
|
uidNode->setColumnWidget(2, cpp14::make_unique<WText> (UidValidities[uid.validity()]));
|
||
|
|
uidNode->setColumnWidget(3, cpp14::make_unique<WText> (uid.comment()));
|
||
|
|
rootNode->addChildNode(unique_ptr<WTreeTableNode> (uidNode));
|
||
|
|
// uid.numSignatures() is always 0, even for signed keys !
|
||
|
|
for (uint s = 0; s < uid.numSignatures(); s++)
|
||
|
|
{
|
||
|
|
UserID::Signature sig = uid.signature(s);
|
||
|
|
const WString signer = WString(sig.signerName()) + _SPACE_
|
||
|
|
+ WString(sig.signerKeyID());
|
||
|
|
WTreeTableNode * sigNode = new WTreeTableNode(signer);
|
||
|
|
sigNode->setColumnWidget(1, cpp14::make_unique<WText> (sig.signerEmail()));
|
||
|
|
WString exp = TR("Expiration") + _SPACE_ + _COLON_ + _SPACE_;
|
||
|
|
exp += sig.neverExpires() ? TR("Never") : MakeDateTimeLabel(sig.expirationTime());
|
||
|
|
sigNode->setColumnWidget(2, cpp14::make_unique<WText> (exp));
|
||
|
|
sigNode->setColumnWidget(3, cpp14::make_unique<WText> (sig.signerComment()));
|
||
|
|
uidNode->addChild(unique_ptr<WTreeTableNode> (sigNode));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
m_ttbUids->show();
|
||
|
|
}
|
||
|
|
|
||
|
|
void K7Main::DisplaySubKeys(const WString& fullKeyID, bool secret)
|
||
|
|
{
|
||
|
|
// Show subkey details for a key
|
||
|
|
Error e;
|
||
|
|
GpgMEWorker gpgw;
|
||
|
|
m_ttbSubKeys->hide();
|
||
|
|
const GpgME::Key k = gpgw.FindKey(fullKeyID.toUTF8().c_str(), e, secret);
|
||
|
|
if (e.code() != 0)
|
||
|
|
{
|
||
|
|
m_tmwMessage->SetText(e.asString());
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (m_ttbSubKeys->columnCount() == 1)
|
||
|
|
{
|
||
|
|
m_ttbSubKeys->addColumn(TR("Fpr"), 300);
|
||
|
|
m_ttbSubKeys->addColumn(TR("Expiration"), 150);
|
||
|
|
m_ttbSubKeys->addColumn(TR("Usage"), 70);
|
||
|
|
m_ttbSubKeys->addColumn(TR("Secret"), 50);
|
||
|
|
}
|
||
|
|
WTreeTableNode * rootNode = new WTreeTableNode(fullKeyID);
|
||
|
|
rootNode->setChildCountPolicy(ChildCountPolicy::Enabled);
|
||
|
|
m_ttbSubKeys->setTreeRoot(unique_ptr<WTreeTableNode> (rootNode), TR("SubKeys"));
|
||
|
|
rootNode->expand();
|
||
|
|
for (uint i = 0; i < k.numSubkeys(); i++)
|
||
|
|
{
|
||
|
|
Subkey sk = k.subkey(i);
|
||
|
|
WTreeTableNode * skNode = new WTreeTableNode(sk.keyID());
|
||
|
|
skNode->setColumnWidget(1, cpp14::make_unique<WText> (sk.fingerprint()));
|
||
|
|
WString exp = sk.neverExpires() ? TR("Never") : MakeDateTimeLabel(sk.expirationTime());
|
||
|
|
skNode->setColumnWidget(2, cpp14::make_unique<WText> (exp));
|
||
|
|
WString usage = sk.canAuthenticate() ? WString("A") : WString::Empty;
|
||
|
|
usage += sk.canCertify() ? WString("C") : WString::Empty;
|
||
|
|
usage += sk.canEncrypt() ? WString("E") : WString::Empty;
|
||
|
|
usage += sk.canSign() ? WString("S") : WString::Empty;
|
||
|
|
skNode->setColumnWidget(3, cpp14::make_unique<WText> (usage));
|
||
|
|
const WString isSecret = sk.isSecret() ? TR("Yes") : TR("No");
|
||
|
|
skNode->setColumnWidget(4, cpp14::make_unique<WText> (isSecret));
|
||
|
|
rootNode->addChildNode(unique_ptr<WTreeTableNode> (skNode));
|
||
|
|
}
|
||
|
|
m_ttbSubKeys->show();
|
||
|
|
}
|
||
|
|
|
||
|
|
void K7Main::PopupUploader() {
|
||
|
|
if (m_uploader == NULL) {
|
||
|
|
m_uploader = new Uploader(m_btnUpload, m_tmwMessage);
|
||
|
|
m_uploader->Create();
|
||
|
|
m_uploader->UploadDone().connect(this, &K7Main::OnUploadCompleted);
|
||
|
|
}
|
||
|
|
m_uploader->show();
|
||
|
|
}
|
||
|
|
|
||
|
|
void K7Main::OnUploadCompleted(const WString& spool) {
|
||
|
|
// Buffer the spool file name in the import button
|
||
|
|
m_btnImport->setAttributeValue("spool", spool);
|
||
|
|
m_btnImport->show();
|
||
|
|
m_uploader->hide();
|
||
|
|
}
|
||
|
|
|
||
|
|
void K7Main::DoImportKey() {
|
||
|
|
const WString spool = m_btnImport->attributeValue("spool");
|
||
|
|
Error e;
|
||
|
|
GpgMEWorker gpgw;
|
||
|
|
const WString keyid = gpgw.ImportKey(spool.toUTF8().c_str(), e);
|
||
|
|
m_btnImport->hide();
|
||
|
|
m_btnImport->setAttributeValue("spool", "");
|
||
|
|
if (e.code() != 0) {
|
||
|
|
m_tmwMessage->SetText(e.asString());
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (keyid.empty()) {
|
||
|
|
m_tmwMessage->SetText(TR("ImportError") + keyid);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
// Show the imported key
|
||
|
|
GpgME::Key k = gpgw.FindKey(keyid.toUTF8().c_str(), e, false); // A public is present anyway
|
||
|
|
if (e.code() != 0) {
|
||
|
|
m_tmwMessage->SetText(e.asString());
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
m_tmwMessage->SetText(TR("ImportSuccess") + keyid + WString(" - ") + WString(k.userID(0).name()));
|
||
|
|
m_leSearch->setText(keyid);
|
||
|
|
Search();
|
||
|
|
}
|
||
|
|
|
||
|
|
bool K7Main::CanKeyBeDeleted(const WString& fullKeyID) {
|
||
|
|
// Caller should check m_config->canDelete first. m_btnDelete is null if can't delete.
|
||
|
|
Error e;
|
||
|
|
GpgMEWorker gpgw;
|
||
|
|
GpgME::Key k = gpgw.FindKey(fullKeyID.toUTF8().c_str(), e, true); // Look for a private key
|
||
|
|
if (e.code() != 0 && e.code() != 16383) { // 16383 : end of file, when key is not private
|
||
|
|
m_tmwMessage->SetText(e.asString());
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
// k can now be secret or public
|
||
|
|
if (k.isNull()) {// Is a public key
|
||
|
|
k = gpgw.FindKey(fullKeyID.toUTF8().c_str(), e, false);
|
||
|
|
// Prepare actual delete
|
||
|
|
m_btnDelete->setAttributeValue("keyid", k.keyID());
|
||
|
|
m_btnDelete->setAttributeValue("hasSecret", "0");
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
/*
|
||
|
|
* k is now secret
|
||
|
|
* Is selected private key one of those that the current user manages ?
|
||
|
|
*/
|
||
|
|
vector<WString> curUserPrivKeys = m_config->PrivateKeyIds();
|
||
|
|
vector<WString>::iterator it;
|
||
|
|
for (it = curUserPrivKeys.begin(); it != curUserPrivKeys.end(); it++) {
|
||
|
|
if (ConfigKeyIdMatchesKey(k, *it)) {
|
||
|
|
m_btnDelete->setAttributeValue("keyid", k.keyID());
|
||
|
|
m_btnDelete->setAttributeValue("hasSecret", "1");
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
void K7Main::PopupDeleter() {
|
||
|
|
if (m_deleter == NULL) {
|
||
|
|
m_deleter = new Deleter(m_btnDelete, m_tmwMessage);
|
||
|
|
m_deleter->Create();
|
||
|
|
m_deleter->GetDeleteButton()->clicked().connect(this, &K7Main::DoDeleteKey);
|
||
|
|
}
|
||
|
|
m_deleter->show();
|
||
|
|
}
|
||
|
|
|
||
|
|
void K7Main::DoDeleteKey() {
|
||
|
|
// Deleting keys requires the GPGME C API
|
||
|
|
Error c_e, e;
|
||
|
|
GpgMECWorker gpgcw;
|
||
|
|
GpgMEWorker gpgw;
|
||
|
|
const WString fullKeyID = m_btnDelete->attributeValue("keyid");
|
||
|
|
const WString hasSecret = m_btnDelete->attributeValue("hasSecret");
|
||
|
|
bool secret = true;
|
||
|
|
if (hasSecret == WString("0"))
|
||
|
|
secret = false;
|
||
|
|
// Get the key before deletion, to show its ID on success
|
||
|
|
GpgME::Key k = gpgw.FindKey(fullKeyID.toUTF8().c_str(), e, secret);
|
||
|
|
if (e.code() != 0) {
|
||
|
|
m_tmwMessage->SetText(e.asString());
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
// Delete the key using the C API
|
||
|
|
bool res = gpgcw.DeleteKey(fullKeyID.toUTF8().c_str(), secret, c_e);
|
||
|
|
if (c_e.code() != 0) {
|
||
|
|
m_tmwMessage->SetText(c_e.asString());
|
||
|
|
} else {
|
||
|
|
m_tmwMessage->SetText(TR("DeleteSuccess") + fullKeyID + WString(" - ") + WString(k.userID(0).name()));
|
||
|
|
}
|
||
|
|
m_btnDelete->hide();
|
||
|
|
m_deleter->hide();
|
||
|
|
// Show that the key is no longer available
|
||
|
|
m_leSearch->setText(fullKeyID);
|
||
|
|
Search();
|
||
|
|
}
|