Merge pull request #2214 from owncloud/2211

Implement (most of) #2211
This commit is contained in:
Daniel Molkentin 2014-09-18 00:09:26 +02:00
commit db3318886e
10 changed files with 374 additions and 59 deletions

View file

@ -427,7 +427,7 @@ if(KRAZY2_EXECUTABLE)
endif()
set(owncloudcmd_NAME ${APPLICATION_EXECUTABLE}cmd)
set(OWNCLOUDCMD_SRC owncloudcmd/simplesslerrorhandler.cpp owncloudcmd/owncloudcmd.cpp)
set(OWNCLOUDCMD_SRC owncloudcmd/simplesslerrorhandler.cpp owncloudcmd/owncloudcmd.cpp owncloudcmd/netrcparser.cpp)
if(NOT BUILD_LIBRARIES_ONLY)
add_executable(${owncloudcmd_NAME} ${OWNCLOUDCMD_SRC})

View file

@ -36,7 +36,7 @@ AbstractCredentials* create(const QString& type)
// empty string might happen for old version of configuration
if (type == "http" || type == "") {
return new HttpCredentials;
return new HttpCredentialsGui;
} else if (type == "dummy") {
return new DummyCredentials;
} else if (type == "shibboleth") {

View file

@ -37,9 +37,6 @@ using namespace QKeychain;
namespace Mirall
{
namespace
{
int getauth(const char *prompt,
char *buf,
size_t len,
@ -74,10 +71,10 @@ int getauth(const char *prompt,
return re;
}
namespace
{
const char userC[] = "user";
const char authenticationFailedC[] = "owncloud-authentication-failed";
} // ns
class HttpCredentialsAccessManager : public MirallAccessManager {
@ -299,19 +296,6 @@ void HttpCredentials::slotReadJobDone(QKeychain::Job *job)
}
}
QString HttpCredentials::queryPassword(bool *ok)
{
if (ok) {
QString str = QInputDialog::getText(0, tr("Enter Password"),
tr("Please enter %1 password for user '%2':")
.arg(Theme::instance()->appNameGUI(), _user),
QLineEdit::Password, QString(), ok);
return str;
} else {
return QString();
}
}
void HttpCredentials::invalidateToken(Account *account)
{
_password = QString();
@ -380,4 +364,17 @@ void HttpCredentials::slotAuthentication(QNetworkReply* reply, QAuthenticator* a
reply->close();
}
QString HttpCredentialsGui::queryPassword(bool *ok)
{
if (ok) {
QString str = QInputDialog::getText(0, tr("Enter Password"),
tr("Please enter %1 password for user '%2':")
.arg(Theme::instance()->appNameGUI(), _user),
QLineEdit::Password, QString(), ok);
return str;
} else {
return QString();
}
}
} // ns Mirall

View file

@ -49,7 +49,7 @@ public:
void persist(Account *account) Q_DECL_OVERRIDE;
QString user() const Q_DECL_OVERRIDE;
QString password() const;
QString queryPassword(bool *ok);
virtual QString queryPassword(bool *ok) = 0;
void invalidateToken(Account *account) Q_DECL_OVERRIDE;
QString fetchUser(Account *account);
@ -58,14 +58,23 @@ private Q_SLOTS:
void slotReadJobDone(QKeychain::Job*);
void slotWriteJobDone(QKeychain::Job*);
private:
protected:
QString _user;
QString _password;
private:
bool _ready;
bool _fetchJobInProgress; //True if the keychain job is in progress or the input dialog visible
bool _readPwdFromDeprecatedPlace;
};
class OWNCLOUDSYNC_EXPORT HttpCredentialsGui : public HttpCredentials {
public:
HttpCredentialsGui() : HttpCredentials() {}
HttpCredentialsGui(const QString& user, const QString& password) : HttpCredentials(user, password) {}
QString queryPassword(bool *ok) Q_DECL_OVERRIDE;
};
} // ns Mirall
#endif

View file

@ -0,0 +1,97 @@
/*
* Copyright (C) by Daniel Molkentin <danimo@owncloud.com>
*
* This program 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.
*
* This program 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 General Public License
* for more details.
*/
#include <QDir>
#include <QFile>
#include <QTextStream>
#include "netrcparser.h"
namespace Mirall {
namespace {
QString defaultKeyword = QLatin1String("default");
QString machineKeyword = QLatin1String("machine");
QString loginKeyword = QLatin1String("login");
QString passwordKeyword = QLatin1String("password");
}
NetrcParser::NetrcParser(const QString &fileName)
: _fileName(fileName)
{
if (_fileName.isEmpty()) {
_fileName = QDir::homePath()+QLatin1String("/.netrc");
}
}
void NetrcParser::tryAddEntryAndClear(QString& machine, LoginPair& pair, bool& isDefault) {
if (isDefault) {
_default = pair;
} else if (!machine.isEmpty() && !pair.first.isEmpty()){
_entries.insert(machine, pair);
}
pair = qMakePair(QString(), QString());
machine.clear();
isDefault = false;
}
bool NetrcParser::parse()
{
QFile netrc(_fileName);
if (!netrc.open(QIODevice::ReadOnly)) {
return false;
}
QTextStream ts(&netrc);
LoginPair pair;
QString machine;
bool isDefault = false;
while (!ts.atEnd()) {
QString next;
ts >> next;
if (next == defaultKeyword) {
tryAddEntryAndClear(machine, pair, isDefault);
isDefault = true;
}
if (next == machineKeyword) {
tryAddEntryAndClear(machine, pair, isDefault);
ts >> machine;
} else if (next == loginKeyword) {
ts >> pair.first;
} else if (next == passwordKeyword) {
ts >> pair.second;
} // ignore unsupported tokens
}
tryAddEntryAndClear(machine, pair, isDefault);
if (!_entries.isEmpty() || _default != qMakePair(QString(), QString())) {
return true;
} else {
return false;
}
}
NetrcParser::LoginPair NetrcParser::find(const QString &machine)
{
QHash<QString, LoginPair>::const_iterator it = _entries.find(machine);
if (it != _entries.end()) {
return *it;
} else {
return _default;
}
}
} // namespace Mirall

View file

@ -0,0 +1,41 @@
/*
* Copyright (C) by Daniel Molkentin <danimo@owncloud.com>
*
* This program 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.
*
* This program 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 General Public License
* for more details.
*/
#ifndef NETRCPARSER_H
#define NETRCPARSER_H
#include <QHash>
#include <QPair>
namespace Mirall {
class NetrcParser
{
public:
typedef QPair<QString, QString> LoginPair;
NetrcParser(const QString &fileName = QString::null);
bool parse();
LoginPair find(const QString &machine);
private:
void tryAddEntryAndClear(QString &machine, LoginPair &pair, bool &isDefault);
QHash<QString, LoginPair> _entries;
LoginPair _default;
QString _fileName;
};
} // namespace Mirall
#endif // NETRCPARSER_H

View file

@ -31,15 +31,27 @@
#include "owncloudcmd.h"
#include "simplesslerrorhandler.h"
#include "netrcparser.h"
#ifdef Q_OS_WIN32
#include <windows.h>
#else
#include <termios.h>
#endif
using namespace Mirall;
struct CmdOptions {
QString source_dir;
QString target_url;
QString config_directory;
QString user;
QString password;
QString proxy;
bool silent;
bool trustSSL;
bool useNetrc;
bool interactive;
QString exclude;
};
@ -47,21 +59,59 @@ struct CmdOptions {
// So we have to use a global variable
CmdOptions *opts = 0;
int getauth(const char* prompt, char* buf, size_t len, int a, int b, void *userdata)
class EchoDisabler
{
Q_UNUSED(a) Q_UNUSED(b) Q_UNUSED(userdata)
std::cout << "** Authentication required: \n" << prompt << std::endl;
std::string s;
if(opts && opts->trustSSL) {
s = "yes";
} else {
std::getline(std::cin, s);
public:
EchoDisabler()
{
#ifdef Q_OS_WIN
HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
GetConsoleMode(hStdin, &mode);
SetConsoleMode(hStdin, mode & (~ENABLE_ECHO_INPUT));
#else
tcgetattr(STDIN_FILENO, &tios);
termios tios_new = tios;
tios_new.c_lflag &= ~ECHO;
tcsetattr(STDIN_FILENO, TCSANOW, &tios_new);
#endif
}
strncpy( buf, s.c_str(), len );
return 0;
~EchoDisabler()
{
#ifdef Q_OS_WIN
SetConsoleMode(hStdin, mode);
#else
tcsetattr(STDIN_FILENO, TCSANOW, &tios);
#endif
}
private:
#ifdef Q_OS_WIN
DWORD mode = 0;
#else
termios tios;
#endif
};
QString queryPassword(const QString &user)
{
EchoDisabler disabler;
std::cout << "Password for user " << qPrintable(user) << ": ";
std::string s;
std::getline(std::cin, s);
return QString::fromStdString(s);
}
class HttpCredentialsText : public HttpCredentials {
public:
HttpCredentialsText(const QString& user, const QString& password) : HttpCredentials(user, password) {}
QString queryPassword(bool *ok) {
if (ok) {
*ok = true;
}
return ::queryPassword(user());
}
};
void help()
{
std::cout << "owncloudcmd - command line ownCloud client tool." << std::endl;
@ -72,12 +122,15 @@ void help()
std::cout << "uses the setting from a configured sync client." << std::endl;
std::cout << std::endl;
std::cout << "Options:" << std::endl;
std::cout << " --silent Don't be so verbose" << std::endl;
std::cout << " --confdir = configdir: Read config from there." << std::endl;
std::cout << " --httpproxy = proxy: Specify a http proxy to use." << std::endl;
std::cout << " --silent, -s Don't be so verbose" << std::endl;
std::cout << " --httpproxy [proxy] Specify a http proxy to use." << std::endl;
std::cout << " Proxy is http://server:port" << std::endl;
std::cout << " --trust Trust the SSL certification." << std::endl;
std::cout << " --exclude [file] exclude list file" << std::endl;
std::cout << " --user, -u [name] Use [name] as the login name" << std::endl;
std::cout << " --password, -p [pass] Use [pass] as password" << std::endl;
std::cout << " -n Use netrc (5) for login" << std::endl;
std::cout << " --non-interactive Do not block execution with interaction" << std::endl;
std::cout << "" << std::endl;
exit(1);
@ -97,7 +150,7 @@ void parseOptions( const QStringList& app_args, CmdOptions *options )
if(!options->target_url.endsWith("/")) {
options->target_url.append("/");
}
options->target_url.append("remote.php/webdav/");
options->target_url.append("remote.php/webdav");
}
if (options->target_url.startsWith("http"))
options->target_url.replace(0, 4, "owncloud");
@ -114,14 +167,20 @@ void parseOptions( const QStringList& app_args, CmdOptions *options )
while(it.hasNext()) {
const QString option = it.next();
if( option == "--confdir" && !it.peekNext().startsWith("-") ) {
options->config_directory = it.next();
} else if( option == "--httpproxy" && !it.peekNext().startsWith("-")) {
if( option == "--httpproxy" && !it.peekNext().startsWith("-")) {
options->proxy = it.next();
} else if( option == "--silent") {
} else if( option == "-s" || option == "--silent") {
options->silent = true;
} else if( option == "--trust") {
options->trustSSL = true;
} else if( option == "-n") {
options->useNetrc = true;
} else if( option == "--non-interactive") {
options->interactive = false;
} else if( (option == "-u" || option == "--user") && !it.peekNext().startsWith("-") ) {
options->user = it.next();
} else if( (option == "-p" || option == "--password") && !it.peekNext().startsWith("-") ) {
options->user = it.next();
} else if( option == "--exclude" && !it.peekNext().startsWith("-") ) {
options->exclude = it.next();
} else {
@ -140,26 +199,57 @@ int main(int argc, char **argv) {
CmdOptions options;
options.silent = false;
options.trustSSL = false;
options.useNetrc = false;
options.interactive = true;
ClientProxy clientProxy;
parseOptions( app.arguments(), &options );
QUrl url(options.target_url.toUtf8());
if (url.userName().isEmpty()) {
std::cout << "** Please enter the username:" << std::endl;
std::string s;
std::getline(std::cin, s);
url.setUserName(QString::fromStdString(s));
}
if (url.password().isEmpty()) {
std::cout << "** Please enter the password:" << std::endl;
std::string s;
std::getline(std::cin, s);
url.setPassword(QString::fromStdString(s));
QUrl url = QUrl::fromUserInput(options.target_url);
// Fetch username and password. If empty, try to retrieve
// from URL and strip URL
QString user;
QString password;
if (options.useNetrc) {
NetrcParser parser;
if (parser.parse()) {
NetrcParser::LoginPair pair = parser.find(url.host());
user = pair.first;
password = pair.second;
}
} else {
user = options.user;
if (user.isEmpty()) {
user = url.userName();
}
password = options.password;
if (password.isEmpty()) {
password = url.password();
}
if (options.interactive) {
if (user.isEmpty()) {
std::cout << "Please enter user name: ";
std::string s;
std::getline(std::cin, s);
user = QString::fromStdString(s);
}
if (password.isEmpty()) {
password = queryPassword(user);
}
}
}
QUrl originalUrl = url;
// ### ensure URL is free of credentials
if (url.userName().isEmpty()) {
url.setUserName(user);
}
if (url.password().isEmpty()) {
url.setPassword(password);
}
Account account;
@ -171,16 +261,19 @@ int main(int argc, char **argv) {
SimpleSslErrorHandler *sslErrorHandler = new SimpleSslErrorHandler;
HttpCredentials *cred = new HttpCredentialsText(user, password);
account.setUrl(url);
account.setCredentials(new HttpCredentials(url.userName(), url.password()));
account.setCredentials(cred);
account.setSslErrorHandler(sslErrorHandler);
AccountManager::instance()->setAccount(&account);
restart_sync:
CSYNC *_csync_ctx;
if( csync_create( &_csync_ctx, options.source_dir.toUtf8(),
originalUrl.toEncoded().constData()) < 0 ) {
url.toEncoded().constData()) < 0 ) {
qFatal("Unable to create csync-context!");
return EXIT_FAILURE;
}
@ -192,7 +285,7 @@ restart_sync:
csync_set_log_level(options.silent ? 1 : 11);
opts = &options;
csync_set_auth_callback( _csync_ctx, getauth );
cred->syncContextPreInit(_csync_ctx);
if( csync_init( _csync_ctx ) < 0 ) {
qFatal("Could not initialize csync!");
@ -239,8 +332,9 @@ restart_sync:
csync_add_exclude_list(_csync_ctx, options.exclude.toLocal8Bit());
}
OwncloudCmd owncloudCmd;
cred->syncContextPreStart(_csync_ctx);
OwncloudCmd owncloudCmd;
SyncJournalDb db(options.source_dir);
SyncEngine engine(_csync_ctx, options.source_dir, QUrl(options.target_url).path(), folder, &db);
QObject::connect(&engine, SIGNAL(finished()), &app, SLOT(quit()));

View file

@ -148,7 +148,7 @@ void OwncloudHttpCredsPage::setErrorString(const QString& err)
AbstractCredentials* OwncloudHttpCredsPage::getCredentials() const
{
return new HttpCredentials(_ui.leUsername->text(), _ui.lePassword->text());
return new HttpCredentialsGui(_ui.leUsername->text(), _ui.lePassword->text());
}
void OwncloudHttpCredsPage::setConfigExists(bool config)

View file

@ -26,5 +26,6 @@ if( UNIX AND NOT APPLE )
endif(UNIX AND NOT APPLE)
owncloud_add_test(CSyncSqlite "")
owncloud_add_test(NetrcParser ../src/owncloudcmd/netrcparser.cpp)

76
test/testnetrcparser.h Normal file
View file

@ -0,0 +1,76 @@
/*
* This software is in the public domain, furnished "as is", without technical
* support, and with no warranty, express or implied, as to its usefulness for
* any purpose.
* */
#ifndef MIRALL_INOTIFYWATCHER_H
#define MIRALL_INOTIFYWATCHER_H
#include <QtTest>
#include "owncloudcmd/netrcparser.h"
using namespace Mirall;
namespace {
const char testfileC[] = "netrctest";
const char testfileWithDefaultC[] = "netrctestDefault";
const char testfileEmptyC[] = "netrctestEmpty";
}
class TestNetrcParser : public QObject
{
Q_OBJECT
private slots:
void initTestCase() {
QFile netrc(testfileC);
QVERIFY(netrc.open(QIODevice::WriteOnly));
netrc.write("machine foo login bar password baz\n");
netrc.write("machine broken login bar2 dontbelonghere password baz2 extratokens dontcare andanother\n");
netrc.write("machine\nfunnysplit\tlogin bar3 password baz3\n");
QFile netrcWithDefault(testfileWithDefaultC);
QVERIFY(netrcWithDefault.open(QIODevice::WriteOnly));
netrcWithDefault.write("machine foo login bar password baz\n");
netrcWithDefault.write("default login user password pass\n");
QFile netrcEmpty(testfileEmptyC);
QVERIFY(netrcEmpty.open(QIODevice::WriteOnly));
}
void cleanupTestCase() {
QVERIFY(QFile::remove(testfileC));
QVERIFY(QFile::remove(testfileWithDefaultC));
QVERIFY(QFile::remove(testfileEmptyC));
}
void testValidNetrc() {
NetrcParser parser(testfileC);
QVERIFY(parser.parse());
QCOMPARE(parser.find("foo"), qMakePair(QString("bar"), QString("baz")));
QCOMPARE(parser.find("broken"), qMakePair(QString("bar2"), QString("baz2")));
QCOMPARE(parser.find("funnysplit"), qMakePair(QString("bar3"), QString("baz3")));
}
void testEmptyNetrc() {
NetrcParser parser(testfileEmptyC);
QVERIFY(!parser.parse());
QCOMPARE(parser.find("foo"), qMakePair(QString(), QString()));
}
void testValidNetrcWithDefault() {
NetrcParser parser(testfileWithDefaultC);
QVERIFY(parser.parse());
QCOMPARE(parser.find("foo"), qMakePair(QString("bar"), QString("baz")));
QCOMPARE(parser.find("dontknow"), qMakePair(QString("user"), QString("pass")));
}
void testInvalidNetrc() {
NetrcParser parser("/invalid");
QVERIFY(!parser.parse());
}
};
#endif