Skip to content

Commit

Permalink
Add --web-admin-allowed-origin option to server
Browse files Browse the repository at this point in the history
To set the Access-Control-Allow-Origin header in responses from the web
admin. This is particularly useful for development, where you don't want
to start up an nginx or something to mess with the admin frontend.
  • Loading branch information
askmeaboutlo0m committed Sep 18, 2023
1 parent 6560aca commit 46589e3
Show file tree
Hide file tree
Showing 6 changed files with 61 additions and 2 deletions.
1 change: 1 addition & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Unreleased Version 2.2.0-pre
* Fix: Synchronize rendering during recording playback properly.
* Feature: Bring back drawpile-cmd, the command-line tool that renders Drawpile recordings to images. Should also mostly work like it did in Drawpile 2.1.
* Feature: Implement drawpile-timelapse, a new command-line tool that turns Drawpile recordings into timelapse videos.
* Server Feature: Add --web-admin-allowed-origin option, to set the Access-Control-Allow-Origin header to the given value. Particularly useful for development, where you don't particularly want to set up an nginx to make CORS happy.

2023-08-26 Version 2.2.0-beta.7
* Fix: Make classic brushes not go brighter when smudging into transparency. Thanks to cada for reporting.
Expand Down
13 changes: 13 additions & 0 deletions src/thinsrv/headless/headless.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,13 @@ bool start() {
// --web-admin-access <address/subnet>
QCommandLineOption webadminAccessOption("web-admin-access", "Set web admin access mask", "address/subnet|all");
parser.addOption(webadminAccessOption);

// --web-admin-allow-origin <origin>
QCommandLineOption webadminAllowedOriginOption(
"web-admin-allowed-origin",
"Allowed origin for Access-Control-Allow-Origin headers in responses",
"origin");
parser.addOption(webadminAllowedOriginOption);
#endif

// --database, -d <filename>
Expand Down Expand Up @@ -268,6 +275,12 @@ bool start() {
return false;
}
}

QString allowedOrigin = parser.value(webadminAllowedOriginOption);
if(!allowedOrigin.isEmpty()) {
webadmin->setAllowedOrigin(allowedOrigin);
}

#ifdef Q_OS_UNIX
server->connect(UnixSignals::instance(), SIGNAL(sigUsr1()), webadmin, SLOT(restart()));
#endif
Expand Down
28 changes: 27 additions & 1 deletion src/thinsrv/webadmin/qmhttp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ struct MicroHttpd::Private {
QByteArray baUser;
QByteArray baPass;

QByteArray allowedOrigin;

// Access controls
AcceptPolicy acceptPolicy;

Expand Down Expand Up @@ -116,6 +118,14 @@ void request_completed(void *cls, struct MHD_Connection *connection, void **con_
*con_cls = nullptr;
}

static void addCorsHeader(MicroHttpd::Private *d, MHD_Response *res)
{
if(!d->allowedOrigin.isEmpty()) {
MHD_add_response_header(
res, "Access-Control-Allow-Origin", d->allowedOrigin.constData());
}
}

MHD_Result request_handler(void *cls, MHD_Connection *connection, const char *url, const char *methodstr, const char *version, const char *upload_data, size_t *upload_data_size, void **con_cls)
{
Q_UNUSED(version);
Expand All @@ -135,6 +145,8 @@ MHD_Result request_handler(void *cls, MHD_Connection *connection, const char *ur
method = HttpRequest::PUT;
else if(qstrcmp(methodstr, "DELETE")==0)
method = HttpRequest::DELETE;
else if(qstrcmp(methodstr, "OPTIONS")==0)
method = HttpRequest::OPTIONS;
else {
logMessage(connection, 405, methodstr, url);
return MHD_NO;
Expand All @@ -159,6 +171,7 @@ MHD_Result request_handler(void *cls, MHD_Connection *connection, const char *ur
strlen(MSG_404),
const_cast<char*>(MSG_404),
MHD_RESPMEM_PERSISTENT);
addCorsHeader(d, response);
const auto ret = MHD_queue_response(connection, MHD_HTTP_NOT_FOUND, response);
MHD_destroy_response(response);
return ret;
Expand All @@ -173,7 +186,7 @@ MHD_Result request_handler(void *cls, MHD_Connection *connection, const char *ur
ctx->request.setHeaders(headers);

// Demand authentication if basic auth is enabled
if(!d->baPass.isEmpty()) {
if(!d->baPass.isEmpty() && request.method() != HttpRequest::OPTIONS) {
char *user, *pass = nullptr;
bool fail = false;

Expand All @@ -193,6 +206,7 @@ MHD_Result request_handler(void *cls, MHD_Connection *connection, const char *ur
const_cast<char*>(MSG_401),
MHD_RESPMEM_PERSISTENT
);
addCorsHeader(d, response);

int ret;
// If a header indicating this was an AJAX request is set,
Expand Down Expand Up @@ -270,6 +284,7 @@ MHD_Result request_handler(void *cls, MHD_Connection *connection, const char *ur
MHD_add_response_header(mhdresponse, i.key().toUtf8().constData(), i.value().toUtf8().constData());
}

addCorsHeader(d, mhdresponse);
const auto ret = MHD_queue_response (connection, response.code(), mhdresponse);
MHD_destroy_response(mhdresponse);

Expand Down Expand Up @@ -366,6 +381,11 @@ void MicroHttpd::setBasicAuth(const QString &realm, const QString &username, con
_d->baPass = password.toUtf8();
}

void MicroHttpd::setAllowedOrigin(const QString &allowedOrigin)
{
_d->allowedOrigin = allowedOrigin.toUtf8();
}

QString MicroHttpd::version()
{
return QString::fromUtf8(MHD_get_version());
Expand Down Expand Up @@ -406,6 +426,12 @@ HttpResponse HttpResponse::JsonErrorResponse(const QString &message, int statusc
return JsonResponse(QJsonDocument(o), statuscode);
}

//! Return a No Content response
HttpResponse HttpResponse::NoContent(int statuscode)
{
return HttpResponse(statuscode);
}

HttpResponse HttpResponse::MethodNotAllowed(const QStringList &allow)
{
QString methods = allow.join(",");
Expand Down
7 changes: 6 additions & 1 deletion src/thinsrv/webadmin/qmhttp.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ Q_OBJECT
//! Set server-wide basic authentication
void setBasicAuth(const QString &realm, const QString &username, const QString &password);

void setAllowedOrigin(const QString &allowedOrigin);

//! Get libmicrohttpd version
static QString version();

Expand All @@ -74,7 +76,7 @@ Q_OBJECT

class HttpRequest {
public:
enum Method {HEAD, GET, POST, PUT, DELETE};
enum Method {HEAD, GET, POST, PUT, DELETE, OPTIONS};

HttpRequest() {}
HttpRequest(Method method, const QString &path)
Expand Down Expand Up @@ -136,6 +138,9 @@ class HttpResponse {
//! Return an error message
static HttpResponse JsonErrorResponse(const QString &message, int statuscode);

//! Return a No Content response
static HttpResponse NoContent(int statuscode=204);

//! Return a Method Not Allowed error
static HttpResponse MethodNotAllowed(const QStringList &allow);

Expand Down
12 changes: 12 additions & 0 deletions src/thinsrv/webadmin/webadmin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ void Webadmin::setBasicAuth(const QString &userpass)
}
}

void Webadmin::setAllowedOrigin(const QString &allowedOrigin)
{
m_server->setAllowedOrigin(allowedOrigin);
}

void Webadmin::setSessions(MultiServer *server)
{
m_server->addRequestHandler("^/api/(.*)", [server](const HttpRequest &req) {
Expand All @@ -49,6 +54,13 @@ void Webadmin::setSessions(MultiServer *server)
case HttpRequest::DELETE:
m = JsonApiMethod::Delete;
break;
case HttpRequest::OPTIONS: {
HttpResponse r = HttpResponse::NoContent();
r.setHeader("Access-Control-Allow-Methods", "HEAD, GET, POST, PUT, DELETE");
r.setHeader("Access-Control-Allow-Headers", "Authorization, Content-Type");
r.setHeader("Access-Control-Max-Age", "86400");
return r;
}
default:
return HttpResponse::JsonErrorResponse("Unknown request method", 400);
}
Expand Down
2 changes: 2 additions & 0 deletions src/thinsrv/webadmin/webadmin.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ class Webadmin final : public QObject
*/
void setBasicAuth(const QString &userpass);

void setAllowedOrigin(const QString &allowedOrigin);

//! Set the session server to administrate
void setSessions(MultiServer *server);

Expand Down

0 comments on commit 46589e3

Please sign in to comment.