HttpServerConnection: Implement CORS support

fixes #4326
This commit is contained in:
Noah Hilverling 2017-07-27 14:57:34 +02:00 committed by Michael Friedrich
parent 835f926b41
commit 94fe1b2292
5 changed files with 81 additions and 20 deletions

View File

@ -44,7 +44,7 @@ object ApiListener "api" {
Configuration Attributes: Configuration Attributes:
Name |Description Name |Description
--------------------------|-------------------------- --------------------------------------|--------------------------------------
cert\_path |**Required.** Path to the public key. cert\_path |**Required.** Path to the public key.
key\_path |**Required.** Path to the private key. key\_path |**Required.** Path to the private key.
ca\_path |**Required.** Path to the CA certificate file. ca\_path |**Required.** Path to the CA certificate file.
@ -56,6 +56,10 @@ Configuration Attributes:
accept\_commands |**Optional.** Accept remote commands. Defaults to `false`. accept\_commands |**Optional.** Accept remote commands. Defaults to `false`.
cipher\_list |**Optional.** Cipher list that is allowed. cipher\_list |**Optional.** Cipher list that is allowed.
tls\_protocolmin |**Optional.** Minimum TLS protocol version. Must be one of `TLSv1`, `TLSv1.1` or `TLSv1.2`. Defaults to `TLSv1`. tls\_protocolmin |**Optional.** Minimum TLS protocol version. Must be one of `TLSv1`, `TLSv1.1` or `TLSv1.2`. Defaults to `TLSv1`.
access\_control\_allow\_origin |**Optional.** Specifies an array of origin URLs that may access the API. [(MDN docs)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Allow-Origin)
access\_control\_allow\_credentials |**Optional.** Indicates whether or not the actual request can be made using credentials. Defaults to `true`. [(MDN docs)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Allow-Credentials)
access\_control\_allow\_headers |**Optional.** Used in response to a preflight request to indicate which HTTP headers can be used when making the actual request. Defaults to `Authorization`. [(MDN docs)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Allow-Headers)
access\_control\_allow\_methods |**Optional.** Used in response to a preflight request to indicate which HTTP methods can be used when making the actual request. Defaults to `GET, POST, PUT, DELETE`. [(MDN docs)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Allow-Methods)
## ApiUser <a id="objecttype-apiuser"></a> ## ApiUser <a id="objecttype-apiuser"></a>

View File

@ -49,6 +49,23 @@ class ApiListener : ConfigObject
[config] String ticket_salt; [config] String ticket_salt;
[config] array(String) access_control_allow_origin {
default {{{ return new Array(); }}}
};
[config] bool access_control_allow_credentials
{
default {{{ return true; }}}
};
[config] String access_control_allow_headers
{
default {{{ return "Authorization"; }}}
};
[config] String access_control_allow_methods
{
default {{{ return "GET, POST, PUT, DELETE"; }}}
};
[state, no_user_modify] Timestamp log_message_timestamp; [state, no_user_modify] Timestamp log_message_timestamp;
[no_user_modify] String identity; [no_user_modify] String identity;

View File

@ -58,13 +58,7 @@ void HttpResponse::SetStatus(int code, const String& message)
void HttpResponse::AddHeader(const String& key, const String& value) void HttpResponse::AddHeader(const String& key, const String& value)
{ {
if (m_State != HttpResponseHeaders) { m_Headers.push_back(key + ": " + value + "\r\n");
Log(LogWarning, "HttpResponse", "Tried to add header after headers had already been sent.");
return;
}
String header = key + ": " + value + "\r\n";
m_Stream->Write(header.CStr(), header.GetLength());
} }
void HttpResponse::FinishHeaders(void) void HttpResponse::FinishHeaders(void)
@ -74,6 +68,10 @@ void HttpResponse::FinishHeaders(void)
AddHeader("Transfer-Encoding", "chunked"); AddHeader("Transfer-Encoding", "chunked");
AddHeader("Server", "Icinga/" + Application::GetAppVersion()); AddHeader("Server", "Icinga/" + Application::GetAppVersion());
for (const String& header : m_Headers)
m_Stream->Write(header.CStr(), header.GetLength());
m_Stream->Write("\r\n", 2); m_Stream->Write("\r\n", 2);
m_State = HttpResponseBody; m_State = HttpResponseBody;
} }

View File

@ -23,6 +23,7 @@
#include "remote/httprequest.hpp" #include "remote/httprequest.hpp"
#include "base/stream.hpp" #include "base/stream.hpp"
#include "base/fifo.hpp" #include "base/fifo.hpp"
#include <vector>
namespace icinga namespace icinga
{ {
@ -70,6 +71,7 @@ private:
const HttpRequest& m_Request; const HttpRequest& m_Request;
Stream::Ptr m_Stream; Stream::Ptr m_Stream;
FIFO::Ptr m_Body; FIFO::Ptr m_Body;
std::vector<String> m_Headers;
void FinishHeaders(void); void FinishHeaders(void);
}; };

View File

@ -171,6 +171,46 @@ void HttpServerConnection::ProcessMessageAsync(HttpRequest& request)
HttpResponse response(m_Stream, request); HttpResponse response(m_Stream, request);
ApiListener::Ptr listener = ApiListener::GetInstance();
if (!listener)
return;
Array::Ptr headerAllowOrigin = listener->GetAccessControlAllowOrigin();
if (headerAllowOrigin->GetLength() != 0) {
String origin = request.Headers->Get("origin");
{
ObjectLock olock(headerAllowOrigin);
for (const String& allowedOrigin : headerAllowOrigin) {
if (allowedOrigin == origin)
response.AddHeader("Access-Control-Allow-Origin", origin);
}
}
if (listener->GetAccessControlAllowCredentials())
response.AddHeader("Access-Control-Allow-Credentials", "true");
String accessControlRequestMethodHeader = request.Headers->Get("access-control-request-method");
if (!accessControlRequestMethodHeader.IsEmpty()) {
response.SetStatus(200, "OK");
response.AddHeader("Access-Control-Allow-Methods", listener->GetAccessControlAllowMethods());
response.AddHeader("Access-Control-Allow-Headers", listener->GetAccessControlAllowHeaders());
String msg = "Preflight OK";
response.WriteBody(msg.CStr(), msg.GetLength());
response.Finish();
m_PendingRequests--;
return;
}
}
String accept_header = request.Headers->Get("accept"); String accept_header = request.Headers->Get("accept");
if (request.RequestMethod != "GET" && accept_header != "application/json") { if (request.RequestMethod != "GET" && accept_header != "application/json") {