Commit 3bff1884 authored by Pali Rohár's avatar Pali Rohár
Browse files

Add support for X-OAuth2 authentication in Jabber protocol

Based on Psi demo code from:
https://github.com/psi-im/iris/issues/35
https://github.com/psi-plus/main/blob/master/patches/dev/xoauth2-support-demo.diff

REVIEW: 129239
BUG: 354473
FIXED-IN: 16.12
parent 9b48acb5
......@@ -95,6 +95,9 @@ macro_log_feature(OPENSSL_FOUND "OpenSSL" "OpenSSL implementation of SSL" "https
macro_optional_find_package(QCA2)
macro_log_feature(QCA2_FOUND "QCA2" "Qt Cryptographic Architecture" "http://delta.affinix.com/qca/" FALSE "2.0.0" "Required for the GroupWise and Jabber protocols")
macro_optional_find_package(QJSON)
macro_log_feature(QJSON_FOUND "QJSON" "JSON handling library for Qt" "http://qjson.sourceforge.net/" FALSE "" "Required for the Jabber protocol")
macro_optional_find_package(QGpgme)
macro_log_feature(QGPGME_FOUND "QGpgme" "QGpgME library (from kdepimlibs)" "http://www.kde.org/" FALSE "" "Required for the Cryptography plugin")
......
......@@ -81,13 +81,13 @@ if(NOT WIN32)
endif(NOT WIN32)
if(QCA2_FOUND AND ZLIB_FOUND)
if(IDN_FOUND)
if(IDN_FOUND AND QJSON_FOUND)
if(WITH_jabber)
add_subdirectory( jabber )
endif(WITH_jabber)
else(IDN_FOUND)
message(STATUS "${CMAKE_CURRENT_SOURCE_DIR}: Disabled Jabber because libidn-devel was not found")
endif(IDN_FOUND)
else(IDN_FOUND AND QJSON_FOUND)
message(STATUS "${CMAKE_CURRENT_SOURCE_DIR}: Disabled Jabber because libidn-devel or qjson was not found")
endif(IDN_FOUND AND QJSON_FOUND)
if(WITH_groupwise)
add_subdirectory( groupwise )
endif(WITH_groupwise)
......
......@@ -11,6 +11,7 @@ ${CMAKE_CURRENT_SOURCE_DIR}/tasks/
${CMAKE_CURRENT_SOURCE_DIR}/libiris/include/iris
${CMAKE_CURRENT_SOURCE_DIR}/libiris/src
${QCA2_INCLUDE_DIR}
${QJSON_INCLUDE_DIR}
)
if(BUILD_LIBJINGLE)
......@@ -49,6 +50,7 @@ set(kopete_jabber_ui_SRCS
ui/privacylistmodel.cpp
ui/privacydlg.cpp
ui/privacyruledlg.cpp
ui/dlgjabberxoauth2.cpp
)
if(BUILD_LIBJINGLE)
......@@ -82,6 +84,7 @@ set(kopete_jabber_ui_files
ui/dlgchatroomslist.ui
ui/privacyrule.ui
ui/privacy.ui
ui/dlgxoauth2.ui
)
if(BUILD_LIBJINGLE)
......@@ -133,6 +136,7 @@ set(kopete_jabber_PART_SRCS
jabberbookmarks.cpp
jabberclient.cpp
jabberbobcache.cpp
xoauth2provider.cpp
)
if(BUILD_LIBJINGLE)
......@@ -160,7 +164,7 @@ endif(BUILD_JINGLE)
kde4_add_plugin(kopete_jabber ${kopete_jabber_PART_SRCS})
target_link_libraries(kopete_jabber ${KDE4_KDEUI_LIBS} ${KDE4_KIO_LIBS} ${QCA2_LIBRARIES} ${KDE4_SOLID_LIBS} kopete iris_kopete)
target_link_libraries(kopete_jabber ${KDE4_KDEUI_LIBS} ${KDE4_KIO_LIBS} ${QCA2_LIBRARIES} ${QJSON_LIBRARIES} ${KDE4_SOLID_LIBS} kopete iris_kopete)
if(BUILD_JINGLE)
target_link_libraries(kopete_jabber ${LIBORTP_LIBRARY} ${ASOUND_LIBRARY} ${SPEEX_LIBRARY})
endif(BUILD_JINGLE)
......
......@@ -376,6 +376,9 @@ void JabberAccount::connectWithPassword ( const QString &password )
// allow plaintext password authentication or not?
m_jabberClient->setAllowPlainTextPassword ( configGroup()->readEntry ( "AllowPlainTextPassword", false ) );
// use X-OAUTH2
m_jabberClient->setUseXOAuth2 ( configGroup()->readEntry ( "UseXOAuth2", false ) );
// enable file transfer (if empty, IP will be set after connection has been established)
KConfigGroup config = KGlobal::config()->group ( "Jabber" );
m_jabberClient->setFileTransfersEnabled ( true, config.readEntry ( "LocalIP" ) );
......@@ -1865,7 +1868,7 @@ void JabberAccount::loginLibjingleResolver(const QHostAddress &address, quint16
bool JabberAccount::enabledLibjingle()
{
return configGroup()->readEntry("Libjingle", true);
return configGroup()->readEntry("Libjingle", true) && !configGroup()->readEntry("UseXOAuth2", false);
}
void JabberAccount::enableLibjingle(bool b)
......
......@@ -43,6 +43,8 @@
#include "privacymanager.h"
#include "xoauth2provider.h"
#define JABBER_PENALTY_TIME 2
class JabberClient::Private
......@@ -114,6 +116,9 @@ public:
// allow transmission of plaintext passwords
bool allowPlainTextPassword;
// use X-OAUTH2
bool useXOAuth2;
// enable file transfers
bool fileTransfersEnabled;
......@@ -185,6 +190,7 @@ void JabberClient::cleanUp ()
setOverrideHost ( false );
setAllowPlainTextPassword ( true );
setUseXOAuth2 ( false );
setFileTransfersEnabled ( false );
setS5BServerPort ( 8010 );
......@@ -412,6 +418,20 @@ bool JabberClient::allowPlainTextPassword () const
}
void JabberClient::setUseXOAuth2 ( bool flag )
{
d->useXOAuth2 = flag;
}
bool JabberClient::useXOAuth2 () const
{
return d->useXOAuth2;
}
void JabberClient::setFileTransfersEnabled ( bool flag, const QString &localAddress )
{
......@@ -679,6 +699,26 @@ JabberClient::ErrorCode JabberClient::connect ( const XMPP::Jid &jid, const QStr
this, SLOT (slotCSConnected()) );
}
if ( useXOAuth2() )
{
/*
* Load X-OAUTH2 SASL provider
*/
bool loaded = false;
foreach ( QCA::Provider *p, QCA::providers() ) {
if ( p->name() == "xoauth2sasl" ) {
loaded = true;
break;
}
}
if ( ! loaded ) {
/* install with higher priority as simplesasl to prevent loading xoauth2sasl automatically */
QCA::insertProvider(createProviderXOAuth2());
QCA::setProviderPriority("xoauth2sasl", 11);
}
d->jabberClientStream->setSaslMechanismProvider("X-OAUTH2", "xoauth2sasl");
}
/*
* Initiate anti-idle timer (will be triggered every 55 seconds).
*/
......
......@@ -214,6 +214,9 @@ public:
*/
bool allowPlainTextPassword () const;
void setUseXOAuth2 ( bool flag );
bool useXOAuth2 () const;
/**
* Enable file transfers. Default is false.
* @param flag Whether to enable file transfers.
......
......@@ -459,6 +459,40 @@
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QCheckBox" name="cbUseXOAuth2">
<property name="text">
<string>Use X-OAuth2 authentication</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="btnXOAuth2">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Manage X-OAuth2 tokens</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
......@@ -1333,5 +1367,69 @@ One media type uses 2 ports and each new Jingle session will use the next 2 avai
</hint>
</hints>
</connection>
<connection>
<sender>cbUseXOAuth2</sender>
<signal>toggled(bool)</signal>
<receiver>btnXOAuth2</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>121</x>
<y>193</y>
</hint>
<hint type="destinationlabel">
<x>441</x>
<y>193</y>
</hint>
</hints>
</connection>
<connection>
<sender>cbUseXOAuth2</sender>
<signal>toggled(bool)</signal>
<receiver>mPass</receiver>
<slot>setHidden(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>121</x>
<y>193</y>
</hint>
<hint type="destinationlabel">
<x>276</x>
<y>94</y>
</hint>
</hints>
</connection>
<connection>
<sender>cbUseXOAuth2</sender>
<signal>toggled(bool)</signal>
<receiver>Libjingle</receiver>
<slot>setDisabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>121</x>
<y>193</y>
</hint>
<hint type="destinationlabel">
<x>276</x>
<y>94</y>
</hint>
</hints>
</connection>
<connection>
<sender>cbUseXOAuth2</sender>
<signal>toggled(bool)</signal>
<receiver>cbAllowPlainTextPassword</receiver>
<slot>setDisabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>121</x>
<y>193</y>
</hint>
<hint type="destinationlabel">
<x>276</x>
<y>94</y>
</hint>
</hints>
</connection>
</connections>
</ui>
/*
dlgjabberxoauth2.cpp - X-OAuth2 dialog
Copyright (c) 2016 by Pali Rohár <pali.rohar@gmail.com>
*************************************************************************
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
*************************************************************************
*/
#include "dlgjabberxoauth2.h"
#include <klocale.h>
#include <kopetepassword.h>
#include "jabberaccount.h"
#include "ui_dlgxoauth2.h"
DlgJabberXOAuth2::DlgJabberXOAuth2(JabberAccount *account, QWidget *parent) : KDialog(parent), m_account(account) {
setCaption(i18n("Manage X-OAuth2 tokens"));
setButtons(KDialog::Ok | KDialog::Cancel);
setDefaultButton(KDialog::Ok);
showButtonSeparator(true);
m_mainWidget = new Ui::DlgXOAuth2();
m_mainWidget->setupUi(mainWidget());
const QString &token = m_account->password().cachedValue();
if (token.contains(QChar(0x7F))) {
const QStringList &tokens = token.split(QChar(0x7F));
if (tokens.size() == 5) {
m_mainWidget->cbUseAccessToken->setChecked(!tokens.at(3).isEmpty());
m_mainWidget->clientId->setText(tokens.at(0));
m_mainWidget->clientSecretKey->setText(tokens.at(1));
m_mainWidget->refreshToken->setText(tokens.at(2));
m_mainWidget->accessToken->setText(tokens.at(3));
m_mainWidget->requestUrl->setText(tokens.at(4));
}
}
connect(this, SIGNAL(okClicked()), this, SLOT(slotOk()));
connect(this, SIGNAL(cancelClicked()), this, SLOT(slotCancel()));
}
DlgJabberXOAuth2::~DlgJabberXOAuth2() {
delete m_mainWidget;
}
void DlgJabberXOAuth2::slotOk() {
QStringList tokens;
tokens << m_mainWidget->clientId->text();
tokens << m_mainWidget->clientSecretKey->text();
tokens << m_mainWidget->refreshToken->text();
tokens << m_mainWidget->accessToken->text();
tokens << m_mainWidget->requestUrl->text();
m_account->password().set(tokens.join(QChar(0x7F)));
}
void DlgJabberXOAuth2::slotCancel() {
deleteLater();
}
#include "dlgjabberxoauth2.moc"
/*
dlgjabberxoauth2.cpp - X-OAuth2 dialog
Copyright (c) 2016 by Pali Rohár <pali.rohar@gmail.com>
*************************************************************************
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
*************************************************************************
*/
#ifndef DLGJABBERXOAUTH2_H
#define DLGJABBERXOAUTH2_H
#include <kdialog.h>
class JabberAccount;
namespace Ui {
class DlgXOAuth2;
}
class DlgJabberXOAuth2 : public KDialog {
Q_OBJECT
public:
DlgJabberXOAuth2(JabberAccount *account, QWidget *parent = NULL);
virtual ~DlgJabberXOAuth2();
private slots:
void slotOk();
void slotCancel();
private:
Ui::DlgXOAuth2 *m_mainWidget;
JabberAccount *m_account;
};
#endif
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DlgXOAuth2</class>
<widget class="QWidget" name="DlgXOAuth2">
<property name="minimumSize">
<size>
<width>500</width>
<height>0</height>
</size>
</property>
<property name="windowTitle">
<string>Manage X-OAuth2 tokens</string>
</property>
<layout class="QGridLayout" name="layout">
<item row="0" column="0" colspan="2">
<widget class="QCheckBox" name="cbUseAccessToken">
<property name="text">
<string>Authenticate directly with Access Token</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="labelAccessToken">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Access Token</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="accessToken">
<property name="enabled">
<bool>false</bool>
</property>
<property name="echoMode">
<enum>QLineEdit::PasswordEchoOnEdit</enum>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="labelRequetUrl">
<property name="text">
<string>Request URL</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="requestUrl"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="labelClientId">
<property name="text">
<string>Client ID</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="clientId"/>
</item>
<item row="4" column="0">
<widget class="QLabel" name="labelClientSecretKey">
<property name="text">
<string>Client Secret Key</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="clientSecretKey">
<property name="echoMode">
<enum>QLineEdit::PasswordEchoOnEdit</enum>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="labelRefreshToken">
<property name="text">
<string>Refresh Token</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLineEdit" name="refreshToken">
<property name="echoMode">
<enum>QLineEdit::PasswordEchoOnEdit</enum>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>cbUseAccessToken</sender>
<signal>toggled(bool)</signal>
<receiver>accessToken</receiver>
<slot>clear()</slot>
</connection>
<connection>
<sender>cbUseAccessToken</sender>
<signal>toggled(bool)</signal>
<receiver>accessToken</receiver>
<slot>setEnabled(bool)</slot>
</connection>
<connection>
<sender>cbUseAccessToken</sender>
<signal>toggled(bool)</signal>
<receiver>labelAccessToken</receiver>
<slot>setEnabled(bool)</slot>
</connection>
<connection>
<sender>cbUseAccessToken</sender>
<signal>toggled(bool)</signal>
<receiver>clientId</receiver>
<slot>setDisabled(bool)</slot>
</connection>
<connection>
<sender>cbUseAccessToken</sender>
<signal>toggled(bool)</signal>
<receiver>labelClientId</receiver>
<slot>setDisabled(bool)</slot>
</connection>
<connection>
<sender>cbUseAccessToken</sender>
<signal>toggled(bool)</signal>
<receiver>clientSecretKey</receiver>
<slot>setDisabled(bool)</slot>
</connection>
<connection>
<sender>cbUseAccessToken</sender>
<signal>toggled(bool)</signal>
<receiver>labelClientSecretKey</receiver>
<slot>setDisabled(bool)</slot>
</connection>
<connection>
<sender>cbUseAccessToken</sender>
<signal>toggled(bool)</signal>
<receiver>refreshToken</receiver>
<slot>setDisabled(bool)</slot>
</connection>
<connection>
<sender>cbUseAccessToken</sender>
<signal>toggled(bool)</signal>
<receiver>labelRefreshToken</receiver>
<slot>setDisabled(bool)</slot>
</connection>
<connection>
<sender>cbUseAccessToken</sender>
<signal>toggled(bool)</signal>
<receiver>requestUrl</receiver>
<slot>setDisabled(bool)</slot>
</connection>
<connection>
<sender>cbUseAccessToken</sender>
<signal>toggled(bool)</signal>
<receiver>labelRequetUrl</receiver>
<slot>setDisabled(bool)</slot>
</connection>
</connections>
</ui>
......@@ -44,6 +44,7 @@
#include "jabberclient.h"
#include "jabberregisteraccount.h"
#include "dlgjabberchangepassword.h"
#include "dlgjabberxoauth2.h"
#include "privacydlg.h"
#include "xmpp.h"
......@@ -66,6 +67,8 @@ JabberEditAccountWidget::JabberEditAccountWidget (JabberProtocol * proto, Jabber
connect (cbUseSSL, SIGNAL (toggled(bool)), this, SLOT (sslToggled(bool)));
connect (btnChangePassword, SIGNAL (clicked()), this, SLOT (slotChangePasswordClicked()));
connect (btnXOAuth2, SIGNAL (clicked()), this, SLOT (slotManageXOAuth2Clicked()));
connect (privacyListsButton, SIGNAL (clicked()), this, SLOT (slotPrivacyListsClicked()) );
......@@ -212,6 +215,7 @@ void JabberEditAccountWidget::reopen ()
}
cbAllowPlainTextPassword->setChecked (account()->configGroup()->readEntry("AllowPlainTextPassword", true));
cbUseXOAuth2->setChecked (account()->configGroup()->readEntry("UseXOAuth2", false));
KConfigGroup config = KGlobal::config()->group("Jabber");
leLocalIP->setText (config.readEntry("LocalIP", QString()));
......@@ -290,6 +294,7 @@ void JabberEditAccountWidget::writeConfig ()
{
account()->configGroup()->writeEntry("UseSSL", cbUseSSL->isChecked());
if (!cbUseXOAuth2->isChecked())
mPass->save(&account()->password ());
account()->configGroup()->writeEntry("CustomServer", cbCustomServer->isChecked());
......@@ -299,6 +304,7 @@ void JabberEditAccountWidget::writeConfig ()
//account()->setAccountId(mID->text());
account()->configGroup()->writeEntry("AllowPlainTextPassword", cbAllowPlainTextPassword->isChecked());
account()->configGroup()->writeEntry("UseXOAuth2", cbUseXOAuth2->isChecked());
account()->configGroup()->writeEntry("Server", mServer->text().trimmed ());
account()->configGroup()->writeEntry("Resource", mResource->text ());
account()->configGroup()->writeEntry("Priority", QString::number (mPriority->value ()));
......@@ -344,7 +350,7 @@ void JabberEditAccountWidget::writeConfig ()
account()->setOldEncrypted(oldEncrypted->isChecked());
#ifdef LIBJINGLE_SUPPORT
account()->enableLibjingle(Libjingle->isChecked());
account()->enableLibjingle(Libjingle->isChecked() && !cbUseXOAuth2->isChecked());
#endif
}
......@@ -429,6 +435,17 @@ void JabberEditAccountWidget::slotChangePasswordFinished ()
}
void JabberEditAccountWidget::slotManageXOAuth2Clicked ()