Browse Source

add auth support (hate the copypasta, but not sure of a more elegant way)

Chris Mullins 8 years ago
parent
commit
0d39e30938
4 changed files with 148 additions and 31 deletions
  1. 4 0
      lib/Settings/Settings.h
  2. 81 0
      lib/WebServer/WebServer.cpp
  3. 23 1
      lib/WebServer/WebServer.h
  4. 40 30
      src/main.cpp

+ 4 - 0
lib/Settings/Settings.h

@@ -27,6 +27,10 @@ public:
       delete deviceIds;
       delete deviceIds;
     }
     }
   }
   }
+  
+  bool hasAuthSettings() {
+    return adminUsername.length() > 0 && adminPassword.length() > 0;
+  }
 
 
   static void deserialize(Settings& settings, String json);
   static void deserialize(Settings& settings, String json);
   static void deserialize(Settings& settings, JsonObject& json);
   static void deserialize(Settings& settings, JsonObject& json);

+ 81 - 0
lib/WebServer/WebServer.cpp

@@ -3,4 +3,85 @@
 
 
 void WebServer::onPattern(const String& pattern, const HTTPMethod method, PatternHandler::TPatternHandlerFn fn) {
 void WebServer::onPattern(const String& pattern, const HTTPMethod method, PatternHandler::TPatternHandlerFn fn) {
   addHandler(new PatternHandler(pattern, method, fn));
   addHandler(new PatternHandler(pattern, method, fn));
+}
+
+void WebServer::requireAuthentication(const String& username, const String& password) {
+  this->username = String(username);
+  this->password = String(password);
+  this->authEnabled = true;
+}
+
+void WebServer::disableAuthentication() {
+  this->authEnabled = false;
+}
+
+void WebServer::_handleRequest() {
+  if (this->authEnabled 
+    && !this->authenticate(this->username.c_str(), this->password.c_str())) {
+      Serial.println(this->username);
+      Serial.println(this->password);
+    this->requestAuthentication();
+  } else {
+    ESP8266WebServer::_handleRequest();
+  }
+}
+
+void WebServer::handleClient() {
+  if (_currentStatus == HC_NONE) {
+    WiFiClient client = _server.available();
+    if (!client) {
+      return;
+    }
+
+    _currentClient = client;
+    _currentStatus = HC_WAIT_READ;
+    _statusChange = millis();
+  }
+
+  if (!_currentClient.connected()) {
+    _currentClient = WiFiClient();
+    _currentStatus = HC_NONE;
+    return;
+  }
+
+  // Wait for data from client to become available
+  if (_currentStatus == HC_WAIT_READ) {
+    if (!_currentClient.available()) {
+      if (millis() - _statusChange > HTTP_MAX_DATA_WAIT) {
+        _currentClient = WiFiClient();
+        _currentStatus = HC_NONE;
+      }
+      yield();
+      return;
+    }
+
+    if (!_parseRequest(_currentClient)) {
+      _currentClient = WiFiClient();
+      _currentStatus = HC_NONE;
+      return;
+    }
+    _currentClient.setTimeout(HTTP_MAX_SEND_WAIT);
+    _contentLength = CONTENT_LENGTH_NOT_SET;
+    _handleRequest();
+
+    if (!_currentClient.connected()) {
+      _currentClient = WiFiClient();
+      _currentStatus = HC_NONE;
+      return;
+    } else {
+      _currentStatus = HC_WAIT_CLOSE;
+      _statusChange = millis();
+      return;
+    }
+  }
+
+  if (_currentStatus == HC_WAIT_CLOSE) {
+    if (millis() - _statusChange > HTTP_MAX_CLOSE_WAIT) {
+      _currentClient = WiFiClient();
+      _currentStatus = HC_NONE;
+    } else {
+      yield();
+      return;
+    }
+  }
 }
 }

+ 23 - 1
lib/WebServer/WebServer.h

@@ -6,18 +6,40 @@
 #include <ESP8266WebServer.h>
 #include <ESP8266WebServer.h>
 #include <PatternHandler.h>
 #include <PatternHandler.h>
 
 
+#define HTTP_DOWNLOAD_UNIT_SIZE 1460
+#define HTTP_UPLOAD_BUFLEN 2048
+#define HTTP_MAX_DATA_WAIT 1000 //ms to wait for the client to send the request
+#define HTTP_MAX_POST_WAIT 1000 //ms to wait for POST data to arrive
+#define HTTP_MAX_SEND_WAIT 5000 //ms to wait for data chunk to be ACKed
+#define HTTP_MAX_CLOSE_WAIT 2000 //ms to wait for the client to close the connection
+
 class WebServer : public ESP8266WebServer {
 class WebServer : public ESP8266WebServer {
 public:
 public:
   WebServer(int port) : ESP8266WebServer(port) { }
   WebServer(int port) : ESP8266WebServer(port) { }
   
   
   bool matchesPattern(const String& pattern, const String& url);
   bool matchesPattern(const String& pattern, const String& url);
   void onPattern(const String& pattern, const HTTPMethod method, PatternHandler::TPatternHandlerFn fn);
   void onPattern(const String& pattern, const HTTPMethod method, PatternHandler::TPatternHandlerFn fn);
+  void requireAuthentication(const String& username, const String& password);
+  void disableAuthentication();
   
   
   inline bool clientConnected() { 
   inline bool clientConnected() { 
     return _currentClient && _currentClient.connected();
     return _currentClient && _currentClient.connected();
   }
   }
   
   
-private:
+  // These are copied / patched from ESP8266WebServer because they aren't 
+  // virtual. (*barf*)
+  void handleClient();
+  void _handleRequest();
+  
+  bool authenticationRequired() {
+    return authEnabled;
+  }
+  
+protected:
+  
+  bool authEnabled;
+  String username;
+  String password;
   
   
   void resetPathMatches();
   void resetPathMatches();
   void checkPatterns();
   void checkPatterns();

+ 40 - 30
src/main.cpp

@@ -11,13 +11,13 @@
 MiLightClient* milightClient;
 MiLightClient* milightClient;
 
 
 WiFiManager wifiManager;
 WiFiManager wifiManager;
-WebServer server(80);
+WebServer *server = new WebServer(80);
 File updateFile;
 File updateFile;
 Settings settings;
 Settings settings;
 
 
 void handleUpdateGateway(const UrlTokenBindings* urlBindings) {
 void handleUpdateGateway(const UrlTokenBindings* urlBindings) {
   DynamicJsonBuffer buffer;
   DynamicJsonBuffer buffer;
-  JsonObject& request = buffer.parse(server.arg("plain"));
+  JsonObject& request = buffer.parse(server->arg("plain"));
   
   
   const uint16_t deviceId = parseInt<uint16_t>(urlBindings->get("device_id"));
   const uint16_t deviceId = parseInt<uint16_t>(urlBindings->get("device_id"));
   
   
@@ -29,15 +29,15 @@ void handleUpdateGateway(const UrlTokenBindings* urlBindings) {
     }
     }
   }
   }
   
   
-  server.send(200, "application/json", "true");
+  server->send(200, "application/json", "true");
 }
 }
 
 
 void handleUpdateGroup(const UrlTokenBindings* urlBindings) {
 void handleUpdateGroup(const UrlTokenBindings* urlBindings) {
   DynamicJsonBuffer buffer;
   DynamicJsonBuffer buffer;
-  JsonObject& request = buffer.parse(server.arg("plain"));
+  JsonObject& request = buffer.parse(server->arg("plain"));
   
   
   if (!request.success()) {
   if (!request.success()) {
-    server.send(400, "text/plain", "Invalid JSON");
+    server->send(400, "text/plain", "Invalid JSON");
     return;
     return;
   }
   }
   
   
@@ -80,12 +80,12 @@ void handleUpdateGroup(const UrlTokenBindings* urlBindings) {
     }
     }
   }
   }
   
   
-  server.send(200, "application/json", "true");
+  server->send(200, "application/json", "true");
 }
 }
 
 
 void handleListenGateway() {
 void handleListenGateway() {
   while (!milightClient->available()) {
   while (!milightClient->available()) {
-    if (!server.clientConnected()) {
+    if (!server->clientConnected()) {
       return;
       return;
     }
     }
     
     
@@ -107,13 +107,13 @@ void handleListenGateway() {
   response += "Sequence Num : " + String(packet.sequenceNum, HEX) + "\n";
   response += "Sequence Num : " + String(packet.sequenceNum, HEX) + "\n";
   response += "\n\n";
   response += "\n\n";
   
   
-  server.send(200, "text/plain", response);
+  server->send(200, "text/plain", response);
 }
 }
 
 
 bool serveFile(const char* file, const char* contentType = "text/html") {
 bool serveFile(const char* file, const char* contentType = "text/html") {
   if (SPIFFS.exists(file)) {
   if (SPIFFS.exists(file)) {
     File f = SPIFFS.open(file, "r");
     File f = SPIFFS.open(file, "r");
-    server.send(200, contentType, f.readString());
+    server->send(200, contentType, f.readString());
     f.close();
     f.close();
     return true;
     return true;
   }
   }
@@ -128,9 +128,9 @@ ESP8266WebServer::THandlerFunction handleServeFile(const char* filename,
   return [filename, contentType, defaultText]() {
   return [filename, contentType, defaultText]() {
     if (!serveFile(filename)) {
     if (!serveFile(filename)) {
       if (defaultText) {
       if (defaultText) {
-        server.send(200, contentType, defaultText);
+        server->send(200, contentType, defaultText);
       } else {
       } else {
-        server.send(404);
+        server->send(404);
       }
       }
     }
     }
   };
   };
@@ -138,7 +138,7 @@ ESP8266WebServer::THandlerFunction handleServeFile(const char* filename,
 
 
 ESP8266WebServer::THandlerFunction handleUpdateFile(const char* filename) {
 ESP8266WebServer::THandlerFunction handleUpdateFile(const char* filename) {
   return [filename]() {
   return [filename]() {
-    HTTPUpload& upload = server.upload();
+    HTTPUpload& upload = server->upload();
     
     
     if (upload.status == UPLOAD_FILE_START) {
     if (upload.status == UPLOAD_FILE_START) {
       updateFile = SPIFFS.open(filename, "w");
       updateFile = SPIFFS.open(filename, "w");
@@ -154,7 +154,7 @@ ESP8266WebServer::THandlerFunction handleUpdateFile(const char* filename) {
 }
 }
 
 
 void onWebUpdated() {
 void onWebUpdated() {
-  server.send(200, "text/plain", "success");
+  server->send(200, "text/plain", "success");
 }
 }
 
 
 void initMilightClient() {
 void initMilightClient() {
@@ -168,7 +168,7 @@ void initMilightClient() {
 
 
 void handleUpdateSettings() {
 void handleUpdateSettings() {
   DynamicJsonBuffer buffer;
   DynamicJsonBuffer buffer;
-  const String& rawSettings = server.arg("plain");
+  const String& rawSettings = server->arg("plain");
   JsonObject& parsedSettings = buffer.parse(rawSettings);
   JsonObject& parsedSettings = buffer.parse(rawSettings);
   
   
   if (parsedSettings.success()) {
   if (parsedSettings.success()) {
@@ -176,9 +176,15 @@ void handleUpdateSettings() {
     settings.save();
     settings.save();
     initMilightClient();
     initMilightClient();
     
     
-    server.send(200, "application/json", "true");
+    if (server->authenticationRequired() && !settings.hasAuthSettings()) {
+      server->disableAuthentication();
+    } else {
+      server->requireAuthentication(settings.adminUsername, settings.adminPassword);
+    }
+    
+    server->send(200, "application/json", "true");
   } else {
   } else {
-    server.send(400, "application/json", "\"Invalid JSON\"");
+    server->send(400, "application/json", "\"Invalid JSON\"");
   }
   }
 }
 }
 
 
@@ -189,22 +195,22 @@ void setup() {
   Settings::load(settings);
   Settings::load(settings);
   initMilightClient();
   initMilightClient();
   
   
-  server.on("/", HTTP_GET, handleServeFile(WEB_INDEX_FILENAME, "text/html"));
-  server.on("/settings", HTTP_GET, handleServeFile(SETTINGS_FILE, "application/json"));
-  server.on("/settings", HTTP_PUT, handleUpdateSettings);
-  server.on("/gateway_traffic", HTTP_GET, handleListenGateway);
-  server.onPattern("/gateways/:device_id/:group_id", HTTP_PUT, handleUpdateGroup);
-  server.onPattern("/gateways/:device_id", HTTP_PUT, handleUpdateGateway);
-  server.on("/web", HTTP_POST, onWebUpdated, handleUpdateFile(WEB_INDEX_FILENAME));
-  server.on("/firmware", HTTP_POST, 
+  server->on("/", HTTP_GET, handleServeFile(WEB_INDEX_FILENAME, "text/html"));
+  server->on("/settings", HTTP_GET, handleServeFile(SETTINGS_FILE, "application/json"));
+  server->on("/settings", HTTP_PUT, handleUpdateSettings);
+  server->on("/gateway_traffic", HTTP_GET, handleListenGateway);
+  server->onPattern("/gateways/:device_id/:group_id", HTTP_PUT, handleUpdateGroup);
+  server->onPattern("/gateways/:device_id", HTTP_PUT, handleUpdateGateway);
+  server->on("/web", HTTP_POST, onWebUpdated, handleUpdateFile(WEB_INDEX_FILENAME));
+  server->on("/firmware", HTTP_POST, 
     [](){
     [](){
-      server.sendHeader("Connection", "close");
-      server.sendHeader("Access-Control-Allow-Origin", "*");
-      server.send(200, "text/plain", (Update.hasError())?"FAIL":"OK");
+      server->sendHeader("Connection", "close");
+      server->sendHeader("Access-Control-Allow-Origin", "*");
+      server->send(200, "text/plain", (Update.hasError())?"FAIL":"OK");
       ESP.restart();
       ESP.restart();
     },
     },
     [](){
     [](){
-      HTTPUpload& upload = server.upload();
+      HTTPUpload& upload = server->upload();
       if(upload.status == UPLOAD_FILE_START){
       if(upload.status == UPLOAD_FILE_START){
         WiFiUDP::stopAll();
         WiFiUDP::stopAll();
         uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;
         uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;
@@ -225,9 +231,13 @@ void setup() {
     }
     }
   );
   );
   
   
-  server.begin();
+  if (settings.adminUsername.length() > 0 && settings.adminPassword.length() > 0) {
+    server->requireAuthentication(settings.adminUsername, settings.adminPassword);
+  }
+  
+  server->begin();
 }
 }
 
 
 void loop() {
 void loop() {
-  server.handleClient();
+  server->handleClient();
 }
 }