support FRIENDS in http mode

This commit is contained in:
Jan-Piet Mens 2016-02-23 12:05:46 +01:00
parent 115302feb6
commit cb7cdbaa30
4 changed files with 153 additions and 3 deletions

View File

@ -857,6 +857,38 @@ The content of the request is used by the Recorder as though it had arrived as a
If the Recorder is compiled without specifying `WITH_MQTT` at build time, support for MQTT is disabled completely.
### Friends in HTTP mode
When a device posts a location request in HTTP mode, the endpoint may return a JSON array of OwnTracks objects of which `_type`s `cmd`, `location` and `card` may be supported by the device. This allows the device to see, say, friends. The Recorder has built-in support for this with the named "friends" lmdb database.
Assuming the following content of the friends database
```
$ ocat -S JP --dump=friends
jane-phone ["john/android"]
```
when user `jane` and device `phone` POST a new location via HTTP, the Recorder will present the following payload to the device:
```json
[
{
"_type": "card",
"tid": "JA",
"face": "/9j/4AAQSkZJR...",
"name": "John Doe"
},
{
"_type": "location",
"tid": "JA",
"lat": 48.95833,
"lon": 2.39523,
"tst": 1456212791
}
]
```
### Authentication
In HTTP mode, the Recorder provides no form of authentication; anybody who "stumbles" over the correct endpoint will be able to post location data to your Recorder! You do not want this to happen.
@ -902,6 +934,17 @@ echo "jjolie-iphone s3cr1t" | ocat --load=keys
Beware: these secret keys are stored in plain text so the database must be protected!
#### `friends`
For http mode, the `friends` named LMDB database contains lists of "friends" on a per user-device key. The key's value must be a valid JSON array of strings, each in the form `"user:device"` or `"user/device"` which indicate which locations a particular user may see. For example, when a user called `jane` on device `phone` publishes in http mode and she should be permitted to see where `john` / `android` is, we add the following key/value to the friends named database:
```bash
ocat --load=friends <<EOF
jane-phone [ "john/android" ]
```
The user/device separator in the array's strings may be a slash (`/`), a dash (`-`), or a colon (`:`).
## Encryption (*experimental!*)
If compiled with `WITH_ENCRYPT` support (this is the default in our packages), the recorder will handle messages from OwnTracks [devices which support payload encryption](http://owntracks.org/booklet/features/encrypt/). Each user / device requires a secret key which is configured on the device and which must be configured on the Recorder host in order for the Recorder to be able to decrypt the payloads.

106
http.c
View File

@ -373,6 +373,103 @@ static int send_status(struct mg_connection *conn, int status, char *text)
return (MG_TRUE);
}
/*
* Create an array of OwnTracks objects of locations and cards of
* friends of user `u` and device `d`. Each of the objects in this
* array *must* contain a TID as the apps will use that to construct
* a ficticious topic name (owntracks/_http/<tid>) internally.
* If this user/device combo has no friends, return an empty array.
*/
JsonNode *populate_friends(struct mg_connection *conn, char *u, char *d)
{
struct udata *ud = (struct udata *)conn->server_param;
JsonNode *results = json_mkarray(), *lastuserlist;
JsonNode *friends, *obj, *jud, *newob, *jtid;
int np;
char *pairs[3];
static UT_string *userdevice = NULL;
utstring_renew(userdevice);
utstring_printf(userdevice, "%s-%s", u, d);
friends = gcache_json_get(ud->httpfriends, UB(userdevice));
if (ud->debug) {
char *js = NULL;
if (friends)
js = json_stringify(friends, NULL);
debug(ud, "Friends of %s: %s", UB(userdevice), js ? js : "<nil>");
if (js)
free(js);
}
/* assume the following are friends of jane/3s */
// json_append_element(friends, json_mkstring("foo/bar"));
// json_append_element(friends, json_mkstring("e:1"));
// json_append_element(friends, json_mkstring("jog/fok"));
// json_append_element(friends, json_mkstring("iss-iss"));
// json_append_element(friends, json_mkstring("db/station"));
/*
* Run through the array of friends of this user. Get LAST object,
* which contains CARD and LOCATION data. Create an array of
* separate location and card objects to return in HTTP mode.
*/
json_foreach(jud, friends) {
if ((np = splitter(jud->string_, "/:-", pairs)) != 2) {
continue;
}
if ((lastuserlist = last_users(pairs[0], pairs[1], NULL)) == NULL) {
splitterfree(pairs);
continue;
}
splitterfree(pairs);
if ((obj = json_find_element(lastuserlist, 0)) == NULL) {
json_delete(lastuserlist);
continue;
}
// printf("OBJ --->%s<---\n", json_stringify(obj, " "));
/* TID is mandatory; if we don't have that, skip */
if ((jtid = json_find_member(obj, "tid")) == NULL) {
json_delete(lastuserlist);
continue;
}
/* CARD */
if (json_find_member(obj, "face") && json_find_member(obj, "name")) {
newob = json_mkobject();
json_append_member(newob, "_type", json_mkstring("card"));
json_copy_element_to_object(newob, "tid", jtid);
json_copy_element_to_object(newob, "face", json_find_member(obj, "face"));
json_copy_element_to_object(newob, "name", json_find_member(obj, "name"));
json_append_element(results, newob);
}
/* LOCATION */
newob = json_mkobject();
json_append_member(newob, "_type", json_mkstring("location"));
json_copy_element_to_object(newob, "tid", jtid);
json_copy_element_to_object(newob, "lat", json_find_member(obj, "lat"));
json_copy_element_to_object(newob, "lon", json_find_member(obj, "lon"));
json_copy_element_to_object(newob, "tst", json_find_member(obj, "tst"));
json_append_element(results, newob);
json_delete(lastuserlist);
}
json_delete(friends);
return (results);
}
/*
* Invoked from an HTTP POST to /pub?u=username&d=devicename
* We need u and d in order to contruct a topic name. Obtain
@ -385,6 +482,7 @@ static int dopublish(struct mg_connection *conn, const char *uri)
struct udata *ud = (struct udata *)conn->server_param;
char *payload, *u, *d;
static UT_string *topic = NULL;
JsonNode *jarray;
if ((u = field(conn, "u")) == NULL) {
u = strdup("owntracks");
@ -396,8 +494,6 @@ static int dopublish(struct mg_connection *conn, const char *uri)
utstring_renew(topic);
utstring_printf(topic, "owntracks/%s/%s", u, d);
free(u);
free(d);
/* We need a nul-terminated payload in handle_message() */
payload = calloc(sizeof(char), conn->content_len + 1);
@ -409,7 +505,11 @@ static int dopublish(struct mg_connection *conn, const char *uri)
free(payload);
return json_response(conn, NULL);
jarray = populate_friends(conn, u, d);
free(u);
free(d);
return json_response(conn, jarray);
}
/*

View File

@ -1490,6 +1490,11 @@ int main(int argc, char **argv)
}
gcache_close(gt);
#endif /* !ENCRYPT */
if ((gt = gcache_open(path, "friends", FALSE)) == NULL) {
fprintf(stderr, "Cannot lmdb-open `friends'\n");
exit(2);
}
gcache_close(gt);
exit(0);
}
@ -1547,6 +1552,7 @@ int main(int argc, char **argv)
# ifdef WITH_ENCRYPT
ud->keydb = gcache_open(err, "keys", TRUE);
# endif
ud->httpfriends = gcache_open(err, "friends", TRUE);
#if WITH_LUA
/*

View File

@ -38,6 +38,7 @@ struct udata {
char *label; /* Server label */
char *geokey; /* Google reverse-geo API key */
int debug; /* enable for debugging */
struct gcache *httpfriends; /* lmdb named database 'friends' */
};
#endif