diff --git a/include/QHttpEngine/QLocalAuth b/include/QHttpEngine/QLocalAuth new file mode 100644 index 0000000..772a54f --- /dev/null +++ b/include/QHttpEngine/QLocalAuth @@ -0,0 +1 @@ +#include "qlocalauth.h" diff --git a/include/QHttpEngine/qlocalauth.h b/include/QHttpEngine/qlocalauth.h new file mode 100644 index 0000000..91e000a --- /dev/null +++ b/include/QHttpEngine/qlocalauth.h @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2015 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef QHTTPENGINE_QLOCALAUTH_H +#define QHTTPENGINE_QLOCALAUTH_H + +#include + +#include + +#include "qhttpengine_global.h" + +class QHTTPENGINE_EXPORT QLocalAuthPrivate; + +/** + * @brief Middleware for local file-based authentication + * + * This class is intended for authenticating applications running under the + * same user account as the server. QLocalFile is used to expose a token to + * connecting applications. The client passes the token in a special header + * and the request is permitted. + * + * The file consists of a JSON object in the following format: + * + * @code + * { + * "token": "{8a34d0f0-29d0-4e54-b3aa-ce8f8ad65527}" + * } + * @endcode + * + * Additional data can be added to the object using the setData() method. + */ +class QHTTPENGINE_EXPORT QLocalAuth : public QHttpMiddleware +{ + Q_OBJECT + +public: + + /** + * @brief Initialize local authentication + * + * To determine whether the local file was created successfully, call the + * exists() method. + */ + explicit QLocalAuth(QObject *parent = Q_NULLPTR); + + /** + * @brief Determine whether the file exists + */ + bool exists() const; + + /** + * @brief Retrieve the name of the file used for storing the token + */ + QString filename() const; + + /** + * @brief Set additional data to include with the token + */ + void setData(const QVariantMap &data); + + /** + * @brief Set the name of the custom header used for confirming the token + * + * The default value is "X-Auth-Token". + */ + void setHeaderName(const QByteArray &name); + + /** + * @brief Process the request + * + * If the token supplied by the client matches, the request is allowed. + * Otherwise, an HTTP 403 error is returned. + */ + virtual bool process(QHttpSocket *socket); + +private: + + QLocalAuthPrivate *const d; +}; + +#endif // QHTTPENGINE_QLOCALAUTH_H diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4fa9970..a204bce 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -12,6 +12,7 @@ set(SRC qhttpserver.cpp qhttpsocket.cpp qiodevicecopier.cpp + qlocalauth.cpp qlocalfile.cpp qobjecthandler.cpp ) diff --git a/src/qlocalauth.cpp b/src/qlocalauth.cpp new file mode 100644 index 0000000..9b97219 --- /dev/null +++ b/src/qlocalauth.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2015 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include +#include +#include + +#include +#include + +#include "qlocalauth_p.h" + +QLocalAuthPrivate::QLocalAuthPrivate(QObject *parent) + : QObject(parent), + tokenHeader("X-Auth-Token"), + token(QUuid::createUuid().toString()) +{ + updateFile(); +} + +void QLocalAuthPrivate::updateFile() +{ + if (file.open()) { + file.write(QJsonDocument(QJsonObject::fromVariantMap(data)).toJson()); + file.close(); + } +} + +QLocalAuth::QLocalAuth(QObject *parent) + : QHttpMiddleware(parent), + d(new QLocalAuthPrivate(this)) +{ +} + +bool QLocalAuth::exists() const +{ + return d->file.exists(); +} + +QString QLocalAuth::filename() const +{ + return d->file.fileName(); +} + +void QLocalAuth::setData(const QVariantMap &data) +{ + d->data = data; + d->data.insert("token", d->token); + d->updateFile(); +} + +void QLocalAuth::setHeaderName(const QByteArray &name) +{ + d->tokenHeader = name; +} + +bool QLocalAuth::process(QHttpSocket *socket) +{ + if (socket->headers().value(d->tokenHeader) != d->token) { + socket->writeError(QHttpSocket::Forbidden); + return false; + } + + return true; +} diff --git a/src/qlocalauth_p.h b/src/qlocalauth_p.h new file mode 100644 index 0000000..0d2d5b8 --- /dev/null +++ b/src/qlocalauth_p.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2015 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef QHTTPENGINE_QLOCALAUTHPRIVATE_H +#define QHTTPENGINE_QLOCALAUTHPRIVATE_H + +#include +#include + +#include + +class QLocalAuthPrivate : public QObject +{ + Q_OBJECT + +public: + + explicit QLocalAuthPrivate(QObject *parent); + + void updateFile(); + + QLocalFile file; + QVariantMap data; + QByteArray tokenHeader; + QString token; +}; + +#endif // QHTTPENGINE_QLOCALAUTHPRIVATE_H diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 19d6591..648de7c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -13,6 +13,7 @@ set(TESTS TestQHttpSocket TestQIByteArray TestQIODeviceCopier + TestQLocalAuth TestQLocalFile TestQObjectHandler ) diff --git a/tests/TestQLocalAuth.cpp b/tests/TestQLocalAuth.cpp new file mode 100644 index 0000000..33cd514 --- /dev/null +++ b/tests/TestQLocalAuth.cpp @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2015 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include "common/qsimplehttpclient.h" +#include "common/qsocketpair.h" + +const QByteArray HeaderName = "X-Test"; +const QByteArray CustomName = "Name"; +const QByteArray CustomData = "Data"; + +class TestQLocalAuth : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + + void testAuth(); +}; + +void TestQLocalAuth::testAuth() +{ + QSocketPair pair; + QTRY_VERIFY(pair.isConnected()); + + QSimpleHttpClient client(pair.client()); + QHttpSocket socket(pair.server(), &pair); + + QLocalAuth localAuth; + localAuth.setData(QVariantMap{ + {CustomName, CustomData} + }); + localAuth.setHeaderName(HeaderName); + QVERIFY(localAuth.exists()); + + QFile file(localAuth.filename()); + QVERIFY(file.open(QIODevice::ReadOnly)); + + QVariantMap data = QJsonDocument::fromJson(file.readAll()).object().toVariantMap(); + QVERIFY(data.contains("token")); + QCOMPARE(data.value(CustomName).toByteArray(), CustomData); + + client.sendHeaders("GET", "/", QHttpSocket::HeaderMap{ + {HeaderName, data.value("token").toByteArray()} + }); + QTRY_VERIFY(socket.isHeadersParsed()); + + QVERIFY(localAuth.process(&socket)); +} + +QTEST_MAIN(TestQLocalAuth) +#include "TestQLocalAuth.moc"