/* * Copyright (C) 2015, 2016, 2017 "IoT.bzh" * Author: José Bollo * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #define AFB_BINDING_VERSION 3 #include #include "agl-forgerock.h" static afb_api_t this_api; static afb_event_t event; static json_object *current_identity; /* default vin corresponds to Toyota Camry 2016, 429 */ static const char default_vin[] = "4T1BF1FK5GU260429"; static char *vin; afb_api_t get_local_api(void) { return this_api; } /***** configuration from file **********************************/ static struct json_object *readjson(int fd) { char *buffer; struct stat s; json_object *result = NULL; int rc; rc = fstat(fd, &s); if (rc == 0 && S_ISREG(s.st_mode)) { buffer = alloca((size_t)(s.st_size)+1); if (read(fd, buffer, (size_t)s.st_size) == (ssize_t)s.st_size) { buffer[s.st_size] = 0; result = json_tokener_parse(buffer); } } close(fd); return result; } static struct json_object *get_global_config(const char *name, const char *locale) { int fd = afb_daemon_rootdir_open_locale(name, O_RDONLY, locale); if (fd < 0) AFB_API_ERROR(this_api, "Config file not found: %s", name); return fd < 0 ? NULL : readjson(fd); } static struct json_object *get_local_config(const char *name) { int fd = openat(AT_FDCWD, name, O_RDONLY, 0); return fd < 0 ? NULL : readjson(fd); } static void confsetstr(struct json_object *conf, const char *name, char **value, const char *def) { struct json_object *v; const char *s; char *p; s = conf && json_object_object_get_ex(conf, name, &v) ? json_object_get_string(v) : def; p = *value; if (s && p != s) { *value = strdup(s); free(p); } } static void setconfig(struct json_object *conf) { if (conf) { confsetstr(conf, "vin", &vin, vin ? : default_vin); agl_forgerock_setconfig(conf); } } static void readconfig() { setconfig(get_global_config("etc/config.json", NULL)); setconfig(get_local_config("/etc/agl/identity-agent-config.json")); setconfig(get_local_config("config.json")); } /****************************************************************/ static struct json_object *make_event_object(const char *name, const char *id, const char *nick) { struct json_object *object; if (!name || !id ) return NULL; object = json_object_new_object(); json_object_object_add(object, "eventName", json_object_new_string(name)); json_object_object_add(object, "accountid", json_object_new_string(id)); if (nick) json_object_object_add(object, "nickname", json_object_new_string(nick)); return object; } static int send_event_object(const char *name, const char *id, const char *nick) { return afb_event_push(event, make_event_object(name, id, nick)); } static void do_login(struct json_object *desc) { /* if switching from NULL to NULL -> do nothing */ if (current_identity == NULL && desc == NULL) return; /* if switching from one user to the same user -> do nothing */ if (current_identity && desc) { const char* a = json_object_to_json_string(current_identity); const char* b = json_object_to_json_string(desc); if (strcmp(a, b) == 0) { AFB_API_NOTICE(this_api, "re-logging the same user!"); return; } } struct json_object *object; /* switching to a different user */ AFB_API_INFO(this_api, "Switching to user %s", desc ? json_object_to_json_string(desc) : "null"); object = current_identity; current_identity = json_object_get(desc); json_object_put(object); if (!json_object_object_get_ex(desc, "name", &object)) object = NULL; send_event_object("login", !object ? "null" : json_object_get_string(object)? : "?", 0); } static void do_logout() { struct json_object *object; AFB_API_INFO(this_api, "Switching to no user"); object = current_identity; current_identity = NULL; json_object_put(object); send_event_object("logout", "null", 0); } static void on_forgerock_data(struct json_object *data, const char *error) { if (error) { AFB_API_ERROR(this_api, "Can't get data: %s", error); } else { do_login(data); } } static void on_nfc_subscribed(void *closure, struct json_object *result, const char *error, const char *info, afb_api_t api) { if (error) AFB_API_ERROR(api, "Failed to subscribe to nfc events."); else AFB_API_DEBUG(api, "Subscribed to nfc events."); } static int service_init(afb_api_t api) { struct json_object *jrequest = NULL; int ret; this_api = api; ret = afb_api_require_api(this_api, "nfc", 1); if (ret < 0) { AFB_API_ERROR(this_api, "nfc api not available"); goto out; } ret = afb_api_require_api(this_api, "persistence", 1); if (ret < 0) { AFB_API_ERROR(this_api, "persistence api not available"); goto out; } event = afb_api_make_event(this_api, "event"); if (!afb_event_is_valid(event)) { AFB_API_ERROR(this_api, "Failed to create event"); ret = -EINVAL; goto out; } readconfig(); agl_forgerock_setcb(on_forgerock_data); jrequest = json_object_new_object(); json_object_object_add(jrequest, "value", json_object_new_string("presence")); afb_api_call(api, "nfc", "subscribe", jrequest, on_nfc_subscribed, NULL); out: return ret; } static void parse_nfc_tag_record(struct json_object *object) { const char *tag_uid; const char *tag_vin; const char *value; char *tmp; value = json_object_get_string(object); if (!value) { AFB_API_ERROR(this_api, "ignore tag - empty content value"); goto out; } tag_vin = strtok_r(value,":", &tmp); if (!tag_vin) { AFB_API_ERROR(this_api, "ignore tag - missing vin value"); goto out; } tag_uid = strtok_r(NULL, ":", &tmp); if (!tag_uid) { AFB_API_ERROR(this_api, "ignore tag - missing uid value"); goto out; } if (!strcmp(tag_vin, vin)) { AFB_API_NOTICE(this_api, "tag record: vin=%s, key=%s", tag_vin, tag_uid); agl_forgerock_download_request(vin, "nfc", tag_uid); } out: return; } static void on_nfc_target_add(struct json_object *object) { struct json_object *json_status; struct json_object *json_uid; const char *uid; if (!object) { AFB_API_ERROR(this_api, "nfc/presence empty args"); goto out; } AFB_API_DEBUG(this_api, "nfc/presence debug: %s", json_object_to_json_string(object)); if (!json_object_object_get_ex(object, "status", &json_status)) { AFB_API_ERROR(this_api, "nfc/presence missing status"); goto out; } if (strncmp(json_object_get_string(json_status), "detected", 8)) { AFB_API_WARNING(this_api, "Not a tag detected event!"); goto out; } if (!json_object_object_get_ex(object, "uid", &json_uid)) { AFB_API_ERROR(this_api, "nfc/presence missing uid"); goto out; } parse_nfc_tag_record(json_uid); out: return; } static void onevent(afb_api_t api, const char *event, struct json_object *object) { AFB_API_NOTICE(api, "Received event: %s", event); if (!strcmp("nfc/presence", event)) { on_nfc_target_add(object); return; } AFB_API_WARNING(api, "Unhandled event: %s", event); } /****************************************************************/ static void subscribe (afb_req_t request) { if (afb_req_subscribe(request, event)) { AFB_REQ_ERROR(request, "subscribe to identity event failed"); afb_req_reply(request, NULL, "failed", "subscribe error"); return; } afb_req_reply(request, NULL, NULL, NULL); } static void unsubscribe (afb_req_t request) { if (afb_req_unsubscribe(request, event)) { AFB_REQ_ERROR(request, "unsubscribe to identity event failed"); afb_req_reply(request, NULL, "failed", "unsubscribe error"); return; } afb_req_reply(request, NULL, NULL, NULL); } static void logout (afb_req_t request) { do_logout(); afb_req_reply(request, NULL, NULL, NULL); } /* * verb "fake_login" intended for testing: force sending a login event */ static void fake_login (afb_req_t request) { struct json_object *desc = afb_req_json(request); do_logout(); if (desc) do_login(desc); afb_req_reply(request, NULL, NULL, NULL); } static void get (afb_req_t request) { afb_req_reply(request, json_object_get(current_identity), NULL, NULL); } /* * verb "fake_auth" intended for testing: trigger authentication on the * provided data (e.g same args as per the "nfc/presence" event) */ static void fake_auth(afb_req_t req) { struct json_object* req_object; struct json_object* kind_object; struct json_object* key_object; req_object = afb_req_json(req); if ((!json_object_object_get_ex(req_object, "kind", &kind_object)) || (!json_object_object_get_ex(req_object, "key", &key_object))) { AFB_REQ_ERROR(req, "bad request: %s", json_object_get_string(req_object)); afb_req_reply(req, NULL, "Missing arg", NULL); return; } const char* kind = json_object_get_string(kind_object); const char* key = json_object_get_string(key_object); AFB_REQ_NOTICE(req, "kind: %s, key: %s", kind, key); agl_forgerock_download_request(vin ? vin : default_vin, kind, key); afb_req_reply(req, NULL, NULL, "fake auth success!"); } /* * NOTE: this sample does not use session to keep the test as basic as possible * in real application most APIs should be protected with AFB_SESSION_CHECK */ const afb_verb_t verbs[]= { {.verb = "subscribe" , .callback = subscribe , .info = "subscribe to events" , .session = AFB_SESSION_NONE }, {.verb = "unsubscribe", .callback = unsubscribe , .info = "unsubscribe to events" , .session = AFB_SESSION_NONE }, {.verb = "fake-login" , .callback = fake_login , .info = "fake a login" , .session = AFB_SESSION_NONE }, {.verb = "logout" , .callback = logout , .info = "log the current user out", .session = AFB_SESSION_NONE }, {.verb = "get" , .callback = get , .info = "get data" , .session = AFB_SESSION_NONE }, {.verb = "fake-auth" , .callback = fake_auth , .info = "fake an authentication" , .session = AFB_SESSION_NONE }, { } }; const afb_binding_t afbBindingExport = { .api = "identity", .specification = "Identity-agent API", .info = "AGL identity service", .verbs = verbs, .init = service_init, .onevent = onevent, }; /* vim: set colorcolumn=80: */