Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[carddav] Add CardDAV sync plugin code #1

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions buteo-sync-plugin-carddav.pro
@@ -0,0 +1,3 @@
TEMPLATE=subdirs
SUBDIRS=src
OTHER_FILES+=rpm/buteo-sync-plugin-carddav.spec
52 changes: 52 additions & 0 deletions rpm/buteo-sync-plugin-carddav.spec
@@ -0,0 +1,52 @@
Name: buteo-sync-plugin-carddav
Summary: Syncs calendar data from CardDAV services
Version: 0.0.1
Release: 1
Group: System/Libraries
License: LGPLv2.1
URL: https://github.com/nemomobile/buteo-sync-plugin-carddav
Source0: %{name}-%{version}.tar.bz2
BuildRequires: pkgconfig(Qt5Core)
BuildRequires: pkgconfig(Qt5Gui)
BuildRequires: pkgconfig(Qt5DBus)
BuildRequires: pkgconfig(Qt5Sql)
BuildRequires: pkgconfig(Qt5Network)
BuildRequires: pkgconfig(Qt5Contacts)
BuildRequires: pkgconfig(Qt5Versit)
BuildRequires: pkgconfig(mlite5)
BuildRequires: pkgconfig(buteosyncfw5)
BuildRequires: pkgconfig(accounts-qt5)
BuildRequires: pkgconfig(libsignon-qt5)
BuildRequires: pkgconfig(libsailfishkeyprovider)
BuildRequires: pkgconfig(qtcontacts-sqlite-qt5-extensions)
Requires: buteo-syncfw-qt5-msyncd

%description
A Buteo plugin which syncs contact data from CardDAV services

%files
%defattr(-,root,root,-)
#out-of-process-plugin
/usr/lib/buteo-plugins-qt5/oopp/carddav-client
#in-process-plugin
#/usr/lib/buteo-plugins-qt5/libcarddav-client.so
%config %{_sysconfdir}/buteo/profiles/client/carddav.xml
%config %{_sysconfdir}/buteo/profiles/sync/carddav.Contacts.xml

%prep
%setup -q -n %{name}-%{version}

%build
%qmake5 "DEFINES+=BUTEO_OUT_OF_PROCESS_SUPPORT"
make %{?jobs:-j%jobs}

%pre
rm -f /home/nemo/.cache/msyncd/sync/client/carddav.xml
rm -f /home/nemo/.cache/msyncd/sync/carddav.Contacts.xml

%install
rm -rf %{buildroot}
%qmake5_install

%post
su nemo -c "systemctl --user restart msyncd.service" || :
168 changes: 168 additions & 0 deletions src/auth.cpp
@@ -0,0 +1,168 @@
/*
* This file is part of buteo-sync-plugin-carddav package
*
* Copyright (C) 2014 Jolla Ltd. and/or its subsidiary(-ies).
*
* Contributors: Chris Adams <chris.adams@jolla.com>
*
* This program/library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*
* This program/library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program/library; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*/

#include "auth_p.h"
#include <sailfishkeyprovider.h>
#include <QtDebug>

namespace {
QString skp_storedKey(const QString &provider, const QString &service, const QString &key)
{
QString retn;
char *value = NULL;
int success = SailfishKeyProvider_storedKey(provider.toLatin1(), service.toLatin1(), key.toLatin1(), &value);
if (value) {
if (success == 0) {
retn = QString::fromLatin1(value);
}
free(value);
}
return retn;
}
}

Auth::Auth(QObject *parent)
: QObject(parent)
, m_account(0)
, m_ident(0)
, m_session(0)
{
}

Auth::~Auth()
{
delete m_account;
if (m_ident && m_session) {
m_ident->destroySession(m_session);
}
delete m_ident;
}

void Auth::signIn(int accountId)
{
m_account = m_manager.account(accountId);
if (!m_account) {
qWarning() << Q_FUNC_INFO << "unable to load account" << accountId;
emit signInError();
return;
}

// determine which service to sign in with.
Accounts::Service srv;
Accounts::ServiceList services = m_account->services();
Q_FOREACH (const Accounts::Service &s, services) {
if (s.serviceType().toLower() == QStringLiteral("carddav")) {
srv = s;
break;
}
}

if (!srv.isValid()) {
qWarning() << Q_FUNC_INFO << "unable to find carddav service for account" << accountId;
emit signInError();
return;
}

// determine the remote server URL from the account settings, and then sign in.
m_account->selectService(srv);
m_serverUrl = m_account->value("CardDAVServerUrl").toString(); // TODO: use "Remote database" for consistency?
if (m_serverUrl.isEmpty()) {
qWarning() << Q_FUNC_INFO << "no valid server url setting in account" << accountId;
emit signInError();
return;
}

m_ident = m_account->credentialsId() > 0 ? SignOn::Identity::existingIdentity(m_account->credentialsId()) : 0;
if (!m_ident) {
qWarning() << Q_FUNC_INFO << "no valid credentials for account" << accountId;
emit signInError();
return;
}

Accounts::AccountService accSrv(m_account, srv);
QString method = accSrv.authData().method();
QString mechanism = accSrv.authData().mechanism();
SignOn::AuthSession *session = m_ident->createSession(method);
if (!session) {
qWarning() << Q_FUNC_INFO << "unable to create authentication session with account" << accountId;
emit signInError();
return;
}

QString providerName = m_account->providerName();
QString clientId = skp_storedKey(providerName, QString(), QStringLiteral("client_id"));
QString clientSecret = skp_storedKey(providerName, QString(), QStringLiteral("client_secret"));
QString consumerKey = skp_storedKey(providerName, QString(), QStringLiteral("consumer_key"));
QString consumerSecret = skp_storedKey(providerName, QString(), QStringLiteral("consumer_secret"));

QVariantMap signonSessionData = accSrv.authData().parameters();
signonSessionData.insert("UiPolicy", SignOn::NoUserInteractionPolicy);
if (!clientId.isEmpty()) signonSessionData.insert("ClientId", clientId);
if (!clientSecret.isEmpty()) signonSessionData.insert("ClientSecret", clientSecret);
if (!consumerKey.isEmpty()) signonSessionData.insert("ConsumerKey", consumerKey);
if (!consumerSecret.isEmpty()) signonSessionData.insert("ConsumerSecret", consumerSecret);

connect(session, SIGNAL(response(SignOn::SessionData)),
this, SLOT(signOnResponse(SignOn::SessionData)),
Qt::UniqueConnection);
connect(session, SIGNAL(error(SignOn::Error)),
this, SLOT(signOnError(SignOn::Error)),
Qt::UniqueConnection);

session->setProperty("accountId", accountId);
session->setProperty("mechanism", mechanism);
session->setProperty("signonSessionData", signonSessionData);
session->process(SignOn::SessionData(signonSessionData), mechanism);
}

void Auth::signOnResponse(const SignOn::SessionData &response)
{
QString username, password, accessToken;
Q_FOREACH (const QString &key, response.propertyNames()) {
if (key.toLower() == QStringLiteral("username")) {
username = response.getProperty(key).toString();
} else if (key.toLower() == QStringLiteral("secret")) {
password = response.getProperty(key).toString();
} else if (key.toLower() == QStringLiteral("password")) {
password = response.getProperty(key).toString();
} else if (key.toLower() == QStringLiteral("accesstoken")) {
accessToken = response.getProperty(key).toString();
}
}

// we need both username+password, OR accessToken.
if (!accessToken.isEmpty()) {
emit signInCompleted(m_serverUrl, QString(), QString(), accessToken);
} else if (!username.isEmpty() && !password.isEmpty()) {
emit signInCompleted(m_serverUrl, username, password, QString());
} else {
qWarning() << Q_FUNC_INFO << "authentication succeeded, but couldn't find valid credentials";
emit signInError();
}
}

void Auth::signOnError(const SignOn::Error &error)
{
qWarning() << Q_FUNC_INFO << "authentication error:" << error.type() << ":" << error.message();
emit signInError();
return;
}
60 changes: 60 additions & 0 deletions src/auth_p.h
@@ -0,0 +1,60 @@
/*
* This file is part of buteo-sync-plugin-carddav package
*
* Copyright (C) 2014 Jolla Ltd. and/or its subsidiary(-ies).
*
* Contributors: Chris Adams <chris.adams@jolla.com>
*
* This program/library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*
* This program/library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program/library; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*/

#include <QObject>

#include <Accounts/Account>
#include <Accounts/Manager>
#include <Accounts/Service>
#include <Accounts/AccountService>
#include <SignOn/Identity>
#include <SignOn/Error>
#include <SignOn/SessionData>
#include <SignOn/AuthSession>

class Auth : public QObject
{
Q_OBJECT

public:
Auth(QObject *parent);
~Auth();

void signIn(int accountId);

Q_SIGNALS:
void signInCompleted(const QString &serverUrl, const QString &username, const QString &password, const QString &accessToken);
void signInError();

private Q_SLOTS:
void signOnResponse(const SignOn::SessionData &response);
void signOnError(const SignOn::Error &error);

private:
Accounts::Manager m_manager;
Accounts::Account *m_account;
SignOn::Identity *m_ident;
SignOn::AuthSession *m_session;
QString m_serverUrl;
};


16 changes: 16 additions & 0 deletions src/carddav.Contacts.xml
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<profile name="carddav.Contacts" type="sync" >
<key name="destinationtype" value="online"/>
<key name="enabled" value="false"/>
<key name="hidden" value="true"/>
<key name="use_accounts" value="true"/>

<profile type="client" name="carddav">
<key value="two-way" name="Sync Direction"/>
<key value="carddav" name="Sync Protocol"/>
<key value="HTTP" name="Sync Transport"/>
<key value="prefer remote" name="conflictpolicy" />
</profile>

<schedule enabled="false" interval="" days="1,2,3,4,5,6,7" syncconfiguredtime="" time="05:00:00" />
</profile>