under /usr/share/nginx/access (create folder if it doesn't exist). To generate
the htpasswd file, install the apache2-utils package and use the 'htpasswd'
executable.
+
+
+P.S: (I always forget these)
+Set file permissions to: 644
+Set folder permissions to: 755
\ No newline at end of file
deny all;
}
- #Login area
- location ^~ /api/login {
- auth_basic "Restricted Access";
- auth_basic_user_file /usr/share/nginx/access/.htpasswd;
-
- fastcgi_pass 127.0.0.1:9005;
- fastcgi_param DOCUMENT_URI_LOCAL login;
- include fastcgi_params;
- }
-
#MCTX API
location /api {
+ #Login area
+ location ^~ /api/control {
+ auth_basic "Restricted Access";
+ auth_basic_user_file /usr/share/nginx/access/.htpasswd;
+
+ fastcgi_pass 127.0.0.1:9005;
+ fastcgi_param DOCUMENT_URI_LOCAL control;
+ include fastcgi_params;
+ }
location ~ ^/api/?([^?]*) {
fastcgi_pass 127.0.0.1:9005;
fastcgi_param DOCUMENT_URI_LOCAL $1;
CXX = gcc
FLAGS = -std=c99 -Wall -Werror -pedantic -g
LIB = -lpthread -lfcgi -lssl -lcrypto
-OBJ = log.o sensor.o fastcgi.o thread.o main.o
+OBJ = log.o control.o sensor.o fastcgi.o thread.o main.o
RM = rm -f
BIN = server
--- /dev/null
+#include "common.h"
+#include "control.h"
+
+/**
+ * System control handler. This covers control over all aspects of the system.
+ * E.g: Actuators, system commands (start/stop experiment/recording) etc
+ * @param context The context to work in
+ * @param params The input parameters
+ */
+void Control_Handler(FCGIContext *context, char *params) {
+ const char *key, *value, *loginkey = NULL, *action = NULL;
+ bool force = false;
+
+ while ((params = FCGI_KeyPair(params, &key, &value))) {
+ if (!strcmp(key, "action"))
+ action = value;
+ else if (!strcmp(key, "key"))
+ loginkey = value;
+ else if (!strcmp(key, "force"))
+ force = !force;
+ else if (!strcmp(key, "id")) {
+
+ }
+ else if (!strcmp(key, "value")) {
+
+ }
+ }
+
+ if (!strcmp(action, "start")) {
+ FCGI_Authorize(context, force);
+ } else if (!strcmp(action, "stop")) { //Don't require control key to stop...
+ //EMERGENCY STOP!!
+ FCGI_BeginJSON(context, STATUS_OK);
+ FCGI_JSONPair("description", "stopped!"); //Not really
+ FCGI_EndJSON();
+ } else {
+ if (!FCGI_Authorized(context, loginkey)) {
+ FCGI_BeginJSON(context, STATUS_UNAUTHORIZED);
+ FCGI_JSONPair("description", "Invalid key specified.");
+ FCGI_EndJSON();
+ return;
+ } else if (!strcmp(action, "end")) {
+ FCGI_AuthorizeEnd(context);
+ } else if (!strcmp(action, "set")) {
+ FCGI_BeginJSON(context, STATUS_OK);
+ FCGI_JSONPair("description", "actuated!");
+ FCGI_EndJSON();
+ }
+ }
+}
--- /dev/null
+#ifndef _CONTROL_H
+#define _CONTROL_H
+
+extern void Control_Handler(FCGIContext *context, char *params);
+
+
+#endif
#include "common.h"
#include "sensor.h"
-#include "log.h"
+#include "control.h"
#include "options.h"
#define LOGIN_TIMEOUT 180
};
/**
- * Handles user logins.
- * @param context The context to work in
- * @param params User specified parameters
- */
-static void LoginHandler(FCGIContext *context, char *params) {
- const char *key, *value;
- bool force = 0, end = 0;
-
- while ((params = FCGI_KeyPair(params, &key, &value))) {
- if (!strcmp(key, "force"))
- force = !force;
- else if (!strcmp(key, "end"))
- end = !end;
- }
-
- if (end) {
- *(context->login_key) = 0;
- FCGI_BeginJSON(context, STATUS_OK);
- FCGI_EndJSON();
- return;
- }
+ * Identifies current version info. Useful for testing that the API is running.
+ * TODO - Consider adding info about available sensors and actuators (eg capabilities)?
+ */
+static void IdentifyHandler(FCGIContext *context, char *params) {
+ FCGI_BeginJSON(context, STATUS_OK);
+ FCGI_JSONPair("description", "MCTX3420 Server API (2013)");
+ FCGI_JSONPair("build_date", __DATE__ " " __TIME__);
+ FCGI_EndJSON();
+}
+/**
+ * Gives the user an authorization key that determines who has control over
+ * the system at any one time. The key can be forcibly generated, revoking
+ * any previous control keys. To be used in conjunction with HTTP
+ * basic authentication.
+ * This function will generate a JSON response that indicates success/failure.
+ * @param context The context to work in
+ * @param force Whether to force key generation or not.
+ */
+void FCGI_Authorize(FCGIContext *context, bool force) {
time_t now = time(NULL);
- if (force || !*(context->login_key) ||
- (now - context->login_timestamp > LOGIN_TIMEOUT))
- {
+ bool expired = now - context->login_timestamp > LOGIN_TIMEOUT;
+
+ if (force || !*(context->login_key) || expired) {
SHA_CTX sha1ctx;
unsigned char sha1[20];
int i = rand();
snprintf(context->login_ip, 16, "%s", getenv("REMOTE_ADDR"));
FCGI_BeginJSON(context, STATUS_OK);
FCGI_JSONPair("key", context->login_key);
- FCGI_EndJSON();
+ FCGI_EndJSON();
} else {
char buf[128];
strftime(buf, 128, "%H:%M:%S %d-%m-%Y",
localtime(&(context->login_timestamp)));
FCGI_BeginJSON(context, STATUS_UNAUTHORIZED);
FCGI_JSONPair("description", "Already logged in");
- FCGI_JSONPair("user", context->login_ip);
- FCGI_JSONPair("time", buf);
+ FCGI_JSONPair("current_user", context->login_ip);
+ FCGI_JSONPair("when", buf);
FCGI_EndJSON();
}
}
-/*TODO: Remove and replace with the actual actuator code*/
-static void ActuatorHandler(FCGIContext *context, char *params) {
- const char *key, *value, *loginkey = NULL;
- while ((params = FCGI_KeyPair(params, &key, &value))) {
- if (!strcmp(key, "key")) {
- loginkey = value;
- }
- }
- if (!loginkey || !FCGI_Authorized(context, loginkey)) {
- FCGI_BeginJSON(context, STATUS_UNAUTHORIZED);
- FCGI_JSONPair("description", "Invalid key specified.");
- FCGI_EndJSON();
- } else {
- FCGI_BeginJSON(context, STATUS_OK);
- FCGI_JSONPair("description", "Logged in!");
- FCGI_EndJSON();
- }
+/**
+ * Revokes the current authorization key, if present.
+ * @param context The context to work in
+ */
+void FCGI_AuthorizeEnd(FCGIContext *context) {
+ *(context->login_key) = 0;
+ FCGI_BeginJSON(context, STATUS_OK);
+ FCGI_EndJSON();
+ return;
}
/**
bool FCGI_Authorized(FCGIContext *context, const char *key) {
time_t now = time(NULL);
int result = (now - context->login_timestamp) <= LOGIN_TIMEOUT &&
- !strcmp(context->login_key, key);
+ key != NULL && !strcmp(context->login_key, key);
if (result) {
context->login_timestamp = now; //Update the login_timestamp
}
if (lastchar > 0 && module[lastchar] == '/')
module[lastchar] = 0;
-
- if (!strcmp("login", module)) {
- module_handler = LoginHandler;
+ if (!*module || !strcmp("identify", module)) {
+ module_handler = IdentifyHandler;
+ } else if (!strcmp("control", module)) {
+ module_handler = Control_Handler;
} else if (!strcmp("sensors", module)) {
module_handler = Sensor_Handler;
- } else if (!strcmp("actuators", module)) {
- module_handler = ActuatorHandler;
}
context.current_module = module;
if (module_handler) {
module_handler(&context, params);
} else {
- strncat(module, " [unknown]", BUFSIZ);
+ strncat(module, " (unhandled)", BUFSIZ);
FCGI_RejectJSON(&context);
}
context.response_number++;
#ifndef _FASTCGI_H
#define _FASTCGI_H
-/**Status codes that fcgi module handlers can return**/
+/**(HTTP) Status codes that fcgi module handlers can return**/
typedef enum StatusCodes {
- STATUS_OK = 0,
- STATUS_ERROR = -1,
- STATUS_UNAUTHORIZED = -2
+ STATUS_OK = 200,
+ STATUS_ERROR = 400,
+ STATUS_UNAUTHORIZED = 401
} StatusCodes;
typedef struct FCGIContext FCGIContext;
-typedef void (*ModuleHandler) (FCGIContext *data, char *params);
+typedef void (*ModuleHandler) (FCGIContext *context, char *params);
+extern void FCGI_Authorize(FCGIContext *context, bool force);
+extern void FCGI_AuthorizeEnd(FCGIContext *context);
extern bool FCGI_Authorized(FCGIContext *context, const char *key);
extern char *FCGI_KeyPair(char *in, const char **key, const char **value);
extern void FCGI_BeginJSON(FCGIContext *context, StatusCodes status_code);
--- /dev/null
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>MCTX3420 2013 Server API unit tests</title>
+ <link rel="stylesheet" href="http://code.jquery.com/qunit/qunit-1.12.0.css">
+ <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
+ <script src="http://code.jquery.com/qunit/qunit-1.12.0.js"></script>
+ </head>
+<body>
+ <div id="qunit"></div>
+ <div id="qunit-fixture"></div>
+ <script src="unit-tests.js"></script>
+</body>
+</html>
\ No newline at end of file
--- /dev/null
+/**
+ * MCTX3420 2013 - Remote pressurised can experiment.
+ * Unit testing for the server API.
+ * These unit tests use the QUnit unit testing framework.
+ * @requires QUnit and jQuery
+ * @date 28/8/13
+ * @author Jeremy Tan
+ */
+
+var api = location.protocol + "//" + location.host + "/api/";
+
+/**
+ * Sends an AJAX query to the API
+ * query(module, username, password, callback);
+ * query(module, callback);
+ * query(module, opts, callback);
+ * query(module, opts, username, password, callback);
+ * @param {string} module The name of the module to be queried
+ * @param {Object} opts Object containing parameters to pass to module
+ * @param {string} username Optional
+ * @param {string} password Required if username specified
+ * @param {function} callback Function that receives JSON data
+ * @returns JSON data
+ */
+function query(module, opts, username, password, callback) {
+ if (typeof opts === 'string') {
+ callback = password;
+ password = username;
+ username = opts;
+ opts = undefined;
+ } else if (typeof opts === 'function') {
+ callback = opts;
+ opts = undefined;
+ } else if (typeof username === 'function') {
+ callback = username;
+ username = undefined;
+ }
+
+ function buildQuery(opts) {
+ var result = "?";
+ var first = true;
+
+ for (key in opts) {
+ if (!first)
+ result += "&";
+ else
+ first = false;
+ result += encodeURIComponent(key) +
+ (opts.key ? "=" + encodeURIComponent(opts.key) : "");
+ }
+ return result;
+ }
+
+ var queryurl = api + module;
+ if (opts)
+ queryurl += buildQuery(opts);
+
+ var authfunc;
+ if (username) {
+ authfunc = function(xhr) {
+ xhr.setRequestHeader("Authorization",
+ "Basic " + btoa(username + ":" + password));
+ };
+ }
+
+ $.ajax({
+ url: queryurl,
+ type: 'GET',
+ dataType: 'json',
+ beforeSend: authfunc
+ }).done(callback)
+ .fail(function(jqXHR) {
+ //Note:Callback must be called so the QUnit test can run.
+ if (jqXHR.status !== 400) {
+ callback({"status" : jqXHR.status, "description" : jqXHR.statusText});
+ } else {
+ try {
+ callback($.parseJSON(jqXHR.responseText));
+ } catch (err) {
+ callback({"status" : jqXHR.status, "description" : jqXHR.statusText});
+ }
+ }
+ });
+}
+
+
+QUnit.asyncTest("API Existence", function () {
+ query("test", function(data) {
+ start();
+ //TODO:Change fastcgi error codes
+ equal(parseInt(data.status, 10), 400, "Nonexistent module"); //Magic numbers!
+ });
+});
+
+QUnit.asyncTest("Login test", function() {
+ query("login", {"force" : true}, "mctxadmin", "admin", function(data) {
+ start();
+ equal(parseInt(data.status, 10), 200, "Login ok"); //Magic numbers!
+ });
+});
+
+QUnit.test("Sensors module", function() {
+
+});
+
+/*QUnit.test("Login module", function () {
+
+});*/
+
+QUnit.test("Access control", function () {
+
+});
\ No newline at end of file