aboutsummaryrefslogtreecommitdiff
path: root/src/auth_flow.cpp
blob: 07544e8c7fcbd8c2a2e6380c50b2149349019558 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
#include <QHeaderView>
#include <QJsonDocument>
#include <QJsonObject>
#include <QTableView>
#include <QVBoxLayout>

#include "auth_flow.h"
#include "keyring.h"

#include "file_functions.h"

// This function is supposed to open the authentication dialog with various
// fields like method selection and fields to enter password or select a key,
// but for now, it'll simply submit a public key auth request. Hostname
// parameter is unused.
void start_auth_flow(AOApplication *ao_app, QString username)
{
  ao_app->ex_auth_username = username;
  AuthRequest req;
  req.username = username;
  req.method = AuthMethod::certificate;
  ao_app->send_ex_message(serializeAuthRequest(req));
}

KeySelectDialog::KeySelectDialog(KeyringModel *model, QStringView username, QWidget *parent)
    : QDialog(parent)
    , m_model(model)
{
  this->setAttribute(Qt::WA_DeleteOnClose);
  this->setWindowTitle(QString("Select key for %1").arg(username));
  auto view = new QTableView(this);
  view->setModel(m_model);
  view->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
  view->setSelectionBehavior(QAbstractItemView::SelectRows);
  view->setSelectionMode(QAbstractItemView::SingleSelection);
  QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
  buttons->button(QDialogButtonBox::Ok)->setEnabled(false);
  QVBoxLayout *layout = new QVBoxLayout(this);
  layout->addWidget(view);
  layout->addWidget(buttons);
  connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
  connect(buttons, &QDialogButtonBox::accepted, this, [this, view] {
    auto rows = view->selectionModel()->selectedRows();
    if (rows.isEmpty())
    {
      return;
    }

    QByteArray key_id = m_model->data(rows.first(), KeyringModel::KeyIDRole).toByteArray();
    QString key_name = m_model->data(rows.first()).toString();

    emit key_selected(key_id, key_name);
  });
  connect(view->selectionModel(), &QItemSelectionModel::selectionChanged, this, [view, buttons](const QItemSelection &, const QItemSelection &) {
    bool selected = !view->selectionModel()->selectedRows().isEmpty();
    buttons->button(QDialogButtonBox::Ok)->setEnabled(selected);
  });
}

KeyPassphraseDialog::KeyPassphraseDialog(QStringView key_name, QWidget *parent)
    : QDialog(parent)
{
  this->setWindowTitle(QString("Enter passphrase for key %1").arg(key_name));
  QVBoxLayout *pw_layout = new QVBoxLayout(this);
  pw_layout->addWidget(new QLabel(QStringLiteral("Passphrase:"), this));
  m_pw_line = new QLineEdit(this);
  m_pw_line->setEchoMode(QLineEdit::Password);
  pw_layout->addWidget(m_pw_line);
  QDialogButtonBox *pw_buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
  pw_layout->addWidget(pw_buttons);
  m_err_lbl = new QLabel(this);
  m_err_lbl->setVisible(false);
  pw_layout->addWidget(m_err_lbl);
  connect(pw_buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
  connect(pw_buttons, &QDialogButtonBox::accepted, this, [this] {
    emit passphrase_submitted(m_pw_line->text().toUtf8());
    m_pw_line->clear();
    // zero the buffer
  });
}

void KeyPassphraseDialog::display_error(ResponseResult error) const
{
  QString error_text;
  switch (error)
  {
  case ResponseResult::unsupported_version:
    error_text = QStringLiteral("Unsupported key version.");
    break;
  case ResponseResult::decryption_failed:
    error_text = QStringLiteral("Wrong passphrase (or corrupted key).");
    break;
  default:
    error_text = QString("Error unlocking key (code %1). Catastrophic failure.").arg((int)error);
    break;
  }
  m_err_lbl->setText(error_text);
  m_err_lbl->setVisible(true);
}

AuthFlow::AuthFlow(AOApplication *ao_app, const AuthChallenge &challenge, QWidget *parent)
    : QObject(parent)
    , m_ao_app(ao_app)
    , m_challenge(challenge)
{
  auto saved_key = ao_app->saved_auth.lookup(ao_app->m_serverdata.m_server_hostname.toUtf8(), ao_app->ex_auth_username.toUtf8());
  if (!saved_key.isEmpty())
  {
    m_mode = FlowMode::Saved;
    m_key_dlg = nullptr;
    on_key_selected(saved_key, QString("%1@%2 (saved)").arg(ao_app->ex_auth_username, ao_app->m_serverdata.m_server_hostname));
  }
  else
  {
    m_mode = FlowMode::Default;
    m_key_dlg = new KeySelectDialog(&ao_app->keyring_model, ao_app->ex_auth_username, parent);
    connect(m_key_dlg, &KeySelectDialog::key_selected, this, &AuthFlow::on_key_selected);
    m_key_dlg->open();
  }
}

void AuthFlow::on_key_selected(QByteArrayView key_id, QStringView key_name)
{
  m_pwd_dlg = new KeyPassphraseDialog(key_name, m_key_dlg);
  connect(m_pwd_dlg, &KeyPassphraseDialog::passphrase_submitted, this, [this, key_id](QByteArrayView passphrase) {
    AuthResponse response;
    ResponseResult result = unlock_and_auth(key_id, passphrase, m_challenge.challenge, m_ao_app->ex_auth_username.toUtf8(), response);
    if (result == ResponseResult::success)
    {
      m_ao_app->send_ex_message(serializeAuthResponse(response));
      if (m_mode == FlowMode::Default)
      {
        m_key_dlg->accept();
        m_ao_app->saved_auth.insert(m_ao_app->m_serverdata.m_server_hostname.toUtf8(), m_ao_app->ex_auth_username.toUtf8(), key_id);
      }
      else
      {
        m_pwd_dlg->accept();
      }
      deleteLater();
    }
    else
    {
      m_pwd_dlg->display_error(result);
    }
  });
  connect(m_pwd_dlg, &QDialog::rejected, this, [this] {
    if (m_mode == FlowMode::Saved)
    {
      m_ao_app->saved_auth.remove(m_ao_app->m_serverdata.m_server_hostname.toUtf8(), m_ao_app->ex_auth_username.toUtf8());
      m_mode = FlowMode::Default;

      m_key_dlg = new KeySelectDialog(&m_ao_app->keyring_model, m_ao_app->ex_auth_username);
      connect(m_key_dlg, &KeySelectDialog::key_selected, this, &AuthFlow::on_key_selected);
      m_key_dlg->open();
    }
  });
  m_pwd_dlg->open();
}