diff options
-rw-r--r-- | agl-identity-service/CMakeLists.txt | 1 | ||||
-rw-r--r-- | agl-identity-service/conf.d/cmake/config.cmake | 2 | ||||
-rw-r--r-- | agl-identity-service/conf.d/wgt/config.xml.in | 1 | ||||
-rw-r--r-- | agl-identity-service/etc/config.json | 2 | ||||
-rw-r--r-- | agl-identity-service/htdocs/identity/AFB-websock.js | 174 | ||||
-rw-r--r-- | agl-identity-service/htdocs/identity/binding-debug.css | 61 | ||||
-rw-r--r-- | agl-identity-service/htdocs/identity/identity-binding.js | 142 | ||||
-rw-r--r-- | agl-identity-service/htdocs/identity/index.html | 26 | ||||
-rw-r--r-- | agl-identity-service/src/CMakeLists.txt | 4 | ||||
-rw-r--r-- | agl-identity-service/src/agl-forgerock.c | 51 | ||||
-rw-r--r-- | agl-identity-service/src/agl-identity-binding.c | 50 | ||||
-rw-r--r-- | agl-identity-service/src/aia-get.c | 7 |
12 files changed, 508 insertions, 13 deletions
diff --git a/agl-identity-service/CMakeLists.txt b/agl-identity-service/CMakeLists.txt index b70c1d1..7ef4f7a 100644 --- a/agl-identity-service/CMakeLists.txt +++ b/agl-identity-service/CMakeLists.txt @@ -22,3 +22,4 @@ include(${CMAKE_CURRENT_SOURCE_DIR}/conf.d/cmake/config.cmake) install(DIRECTORY etc DESTINATION ./) +install(DIRECTORY htdocs/identity DESTINATION htdocs) diff --git a/agl-identity-service/conf.d/cmake/config.cmake b/agl-identity-service/conf.d/cmake/config.cmake index 04a9d45..c5fdae6 100644 --- a/agl-identity-service/conf.d/cmake/config.cmake +++ b/agl-identity-service/conf.d/cmake/config.cmake @@ -45,7 +45,7 @@ set(PROJECT_APP_TEMPLATES_DIR "../conf.d/app-templates") # Compilation Mode (DEBUG, RELEASE) # ---------------------------------- -set(USE_EFENCE 1) +set(USE_EFENCE 0) # Kernel selection if needed. You can choose between a # mandatory version to impose a minimal version. diff --git a/agl-identity-service/conf.d/wgt/config.xml.in b/agl-identity-service/conf.d/wgt/config.xml.in index 8d9f7f6..625b87f 100644 --- a/agl-identity-service/conf.d/wgt/config.xml.in +++ b/agl-identity-service/conf.d/wgt/config.xml.in @@ -16,5 +16,6 @@ <feature name="urn:AGL:widget:required-api"> <param name="lib/afb-identity-binding.so" value="local" /> <param name="nfc" value="auto" /> + <param name="ll-database" value="auto" /> </feature> </widget> diff --git a/agl-identity-service/etc/config.json b/agl-identity-service/etc/config.json index 5208b38..c69e5be 100644 --- a/agl-identity-service/etc/config.json +++ b/agl-identity-service/etc/config.json @@ -1,6 +1,6 @@ { "endpoint": "https://agl-graphapi.forgerocklabs.org:443/getuserprofilefromtoken", - "vin": "4T1BF1FK5GU260429", + "vin": "WVGGF7BP7HD005986", "autoadvise": true, "delay": 5, "idp": { diff --git a/agl-identity-service/htdocs/identity/AFB-websock.js b/agl-identity-service/htdocs/identity/AFB-websock.js new file mode 100644 index 0000000..08a7ffe --- /dev/null +++ b/agl-identity-service/htdocs/identity/AFB-websock.js @@ -0,0 +1,174 @@ +AFB = function(base, initialtoken){ + +var urlws = "ws://"+window.location.host+"/"+base; +var urlhttp = "http://"+window.location.host+"/"+base; + +/*********************************************/ +/**** ****/ +/**** AFB_context ****/ +/**** ****/ +/*********************************************/ +var AFB_context; +{ + var UUID = undefined; + var TOKEN = initialtoken; + + var context = function(token, uuid) { + this.token = token; + this.uuid = uuid; + } + + context.prototype = { + get token() {return TOKEN;}, + set token(tok) {if(tok) TOKEN=tok;}, + get uuid() {return UUID;}, + set uuid(id) {if(id) UUID=id;} + }; + + AFB_context = new context(); +} +/*********************************************/ +/**** ****/ +/**** AFB_websocket ****/ +/**** ****/ +/*********************************************/ +var AFB_websocket; +{ + var CALL = 2; + var RETOK = 3; + var RETERR = 4; + var EVENT = 5; + + var PROTO1 = "x-afb-ws-json1"; + + AFB_websocket = function(onopen, onabort) { + var u = urlws; + if (AFB_context.token) { + u = u + '?x-afb-token=' + AFB_context.token; + if (AFB_context.uuid) + u = u + '&x-afb-uuid=' + AFB_context.uuid; + } + this.ws = new WebSocket(u, [ PROTO1 ]); + this.pendings = {}; + this.awaitens = {}; + this.counter = 0; + this.ws.onopen = onopen.bind(this); + this.ws.onerror = onerror.bind(this); + this.ws.onclose = onclose.bind(this); + this.ws.onmessage = onmessage.bind(this); + this.onopen = onopen; + this.onabort = onabort; + this.onclose = onabort; + } + + function onerror(event) { + var f = this.onabort; + if (f) { + delete this.onopen; + delete this.onabort; + f && f(this); + } + this.onerror && this.onerror(this); + } + + function onopen(event) { + var f = this.onopen; + delete this.onopen; + delete this.onabort; + f && f(this); + } + + function onclose(event) { + for (var id in this.pendings) { + var ferr = this.pendings[id].onerror; + ferr && ferr(null, this); + } + this.pendings = {}; + this.onclose && this.onclose(); + } + + function fire(awaitens, name, data) { + var a = awaitens[name]; + if (a) + a.forEach(function(handler){handler(data);}); + var i = name.indexOf("/"); + if (i >= 0) { + a = awaitens[name.substring(0,i)]; + if (a) + a.forEach(function(handler){handler(data);}); + } + a = awaitens["*"]; + if (a) + a.forEach(function(handler){handler(data);}); + } + + function reply(pendings, id, ans, offset) { + if (id in pendings) { + var p = pendings[id]; + delete pendings[id]; + var f = p[offset]; + f(ans); + } + } + + function onmessage(event) { + var obj = JSON.parse(event.data); + var code = obj[0]; + var id = obj[1]; + var ans = obj[2]; + AFB_context.token = obj[3]; + switch (code) { + case RETOK: + reply(this.pendings, id, ans, 0); + break; + case RETERR: + reply(this.pendings, id, ans, 1); + break; + case EVENT: + default: + fire(this.awaitens, id, ans); + break; + } + } + + function close() { + this.ws.close(); + this.onabort(); + } + + function call(method, request) { + return new Promise((function(resolve, reject){ + var id, arr; + do { + id = String(this.counter = 4095 & (this.counter + 1)); + } while (id in this.pendings); + this.pendings[id] = [ resolve, reject ]; + arr = [CALL, id, method, request ]; + if (AFB_context.token) arr.push(AFB_context.token); + this.ws.send(JSON.stringify(arr)); + }).bind(this)); + } + + function onevent(name, handler) { + var id = name; + var list = this.awaitens[id] || (this.awaitens[id] = []); + list.push(handler); + } + + AFB_websocket.prototype = { + close: close, + call: call, + onevent: onevent + }; +} +/*********************************************/ +/**** ****/ +/**** ****/ +/**** ****/ +/*********************************************/ +return { + context: AFB_context, + ws: AFB_websocket +}; +}; + diff --git a/agl-identity-service/htdocs/identity/binding-debug.css b/agl-identity-service/htdocs/identity/binding-debug.css new file mode 100644 index 0000000..f41c940 --- /dev/null +++ b/agl-identity-service/htdocs/identity/binding-debug.css @@ -0,0 +1,61 @@ +#debug-panel { + float: right; +} + +#debug-panel.collapsed { + background-color: transparent; + overflow-x: hidden; +} + +#debug-panel.expanded { + background-color: lightyellow; + max-width: 25%; + overflow-x: scroll; +} + +#debug-panel.collapsed > #debug-panel-collapse { + display: none; +} + +#debug-panel.expanded > #debug-panel-collapse { + display: block; +} + +#debug-panel.collapsed > #debug-panel-expand { + display: block; +} + +#debug-panel.expanded > #debug-panel-expand { + display: none; +} + +#debug-panel.expanded > #debug-panel-content { + display: block; +} + +#debug-panel.collapsed > #debug-panel-content { + display: none; +} + +.json-key { + color: cornflowerblue; + font-weight: bold; +} + +.json-string { + color: crimson; +} + +.json-number { + color: sandybrown; +} + +.json-boolean { + color: fuchsia; + font-weight: bold; +} + +.json-null { + color: black; + font-weight: bold; +} diff --git a/agl-identity-service/htdocs/identity/identity-binding.js b/agl-identity-service/htdocs/identity/identity-binding.js new file mode 100644 index 0000000..bd45c17 --- /dev/null +++ b/agl-identity-service/htdocs/identity/identity-binding.js @@ -0,0 +1,142 @@ +var afb = new AFB("api", "HELLO"); +var ws; + +function add_debbug_panel() { + + if (document.getElementById("debug-panel")) + return; + + var itm = document.getElementById("debug-panel-container"); + if (itm) + { + var pnl = + "<div id=\"debug-panel\" class=\"expanded\">\n" + + " <button id=\"debug-panel-collapse\" onclick=\"debug_panel_collapse();\">></button>\n" + + " <button id=\"debug-panel-expand\" onclick=\"debug_panel_expand();\"><</button>\n" + + " <div id=\"debug-panel-content\">\n" + + " <h1>Debug</h1>\n" + + " <h2>Call</h2><div id=\"debug-panel-call\">\n" + + " <ul>\n" + + " <li><strong>api : </strong><span id=\"debug-panel-call-id\"></span></li>\n" + + " <li><strong>verb : </strong><span id=\"debug-panel-call-verb\"></span></li>\n" + + " <li><strong>query : </strong></li>\n" + + " </ul>\n" + + " <pre id=\"debug-panel-call-query\"></pre>\n" + + " </div>\n" + + " <h2>Response</h2><pre id=\"debug-panel-response\"></pre>\n" + + " <h2>Event</h2><pre id=\"debug-panel-event\"></pre>\n" + + " </div>\n" + + "</div>\n"; + itm.insertAdjacentHTML("afterbegin", pnl); + } +} + +function createClass(name,rules) { + var style = document.createElement('style'); + style.type = 'text/css'; + document.getElementsByTagName('head')[0].appendChild(style); + if(!(style.sheet||{}).insertRule) + (style.styleSheet || style.sheet).addRule(name, rules); + else + style.sheet.insertRule(name+"{"+rules+"}",0); +} + +function syntaxHighlight(json) { + if (typeof json != 'string') + json = JSON.stringify(json, undefined, 2); + + json = json.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'); + return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) { + var cls = 'json-number'; + if (/^"/.test(match)) { + if (/:$/.test(match)) { + cls = 'json-key'; + } else { + cls = 'json-string'; + } + } else if (/true|false/.test(match)) { + cls = 'json-boolean'; + } else if (/null/.test(match)) { + cls = 'json-null'; + } + return '<span class="' + cls + '">' + match + '</span>'; + }); +} + +function set_item_html(id, text) +{ + var itm = document.getElementById(id); + if (itm) itm.innerHTML = text; +} + +function set_item_text(id, text) +{ + var itm = document.getElementById(id); + if (itm) itm.innerText = text; +} + +function debug_panel_collapse() { + var pnl = document.getElementById('debug-panel'); + if (pnl) + { + pnl.classList.remove('expanded'); + pnl.classList.add('collapsed'); + } +} + +function debug_panel_expand() { + var pnl = document.getElementById('debug-panel'); + if (pnl) + { + pnl.classList.remove('collapsed'); + pnl.classList.add('expanded'); + } +} + +function init() { + add_debbug_panel(); + ws = new afb.ws(onopen, onabort); +} + +function onopen() { + //callbinder("ll-auth", "getuser", ""); + ws.onevent("*", gotevent); +} + +function onabort() { +} + +function replyok(obj) { + console.log("replyok:" + JSON.stringify(obj)); + set_item_html("debug-panel-response", syntaxHighlight(JSON.stringify(obj, null, 4))); +} + +function replyerr(obj) { + console.log("replyerr:" + JSON.stringify(obj)); + set_item_html("debug-panel-response", syntaxHighlight(JSON.stringify(obj, null, 4))); +} + +function gotevent(obj) { + console.log("gotevent:" + JSON.stringify(obj)); + set_item_html("debug-panel-event", syntaxHighlight(JSON.stringify(obj, null, 4))); +} + +function callbinder(api, verb, query) { + console.log ("subscribe api="+api+" verb="+verb+" query=" +query); + + set_item_text("debug-panel-call-api", api); + set_item_text("debug-panel-call-verb", verb); + set_item_html("debug-panel-call-query", syntaxHighlight(JSON.stringify(query, null, 4))); + + ws.call(api+"/"+verb, query).then(replyok, replyerr); +} + +function fake_auth() { + + var e = document.getElementById("fake-auth-kind"); + var arg = { + "kind": e.options[e.selectedIndex].value, + "key": document.getElementById("fake-auth-key").value + } + callbinder("identity", "fake-auth", arg); +} diff --git a/agl-identity-service/htdocs/identity/index.html b/agl-identity-service/htdocs/identity/index.html new file mode 100644 index 0000000..d5432fb --- /dev/null +++ b/agl-identity-service/htdocs/identity/index.html @@ -0,0 +1,26 @@ +<!doctype html> +<html> + <head> + <title>agl-service-identity</title> + <meta charset="UTF-8"> + <script type="text/javascript" src="AFB-websock.js"></script> + <script type="text/javascript" src="identity-binding.js"></script> + <link rel="stylesheet" type="text/css" href="binding-debug.css" /> + </head> + + <body onload="init();" id="app-body"> + <div id="debug-panel-container"></div> + <h1>agl-service-identity</h1> + <p> + <ul> + <li> + <select name="fake-auth-kind" id="fake-auth-kind"> + <option value="nfc" selected="selected">nfc</option> + </select> + <input type="text" name="fake-auth-key" id="fake-auth-key" value="a71d4d00"/> + <button onclick="fake_auth();">fake auth</button> + </li> + </ul> + </p> + </body> +</html> diff --git a/agl-identity-service/src/CMakeLists.txt b/agl-identity-service/src/CMakeLists.txt index 1ceb851..2e86ecc 100644 --- a/agl-identity-service/src/CMakeLists.txt +++ b/agl-identity-service/src/CMakeLists.txt @@ -41,3 +41,7 @@ set_target_properties(afb-identity-binding PROPERTIES OUTPUT_NAME "${TARGET_NAME}" ) +add_custom_command(TARGET ${TARGET_NAME} +PRE_BUILD +COMMAND mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/../package/htdocs +COMMAND cp -rv ${CMAKE_CURRENT_SOURCE_DIR}/../htdocs ${CMAKE_CURRENT_BINARY_DIR}/../package/) diff --git a/agl-identity-service/src/agl-forgerock.c b/agl-identity-service/src/agl-forgerock.c index 2a0d3da..c0c49ab 100644 --- a/agl-identity-service/src/agl-forgerock.c +++ b/agl-identity-service/src/agl-forgerock.c @@ -27,6 +27,10 @@ #include "oidc-agent.h" #include "aia-get.h" +#ifndef NULL +#define NULL 0 +#endif + static int expiration_delay = 5; static const char default_endpoint[] = "https://agl-graphapi.forgerocklabs.org/getuserprofilefromtoken"; @@ -83,7 +87,11 @@ static void loaded(struct json_object *data, const char *error) static void downloaded(void *closure, int status, const void *buffer, size_t size) { struct json_object *object, *subobj; - char *url = closure; + struct json_object *objkey = closure; + struct json_object *tmp; + + json_object_object_get_ex(objkey, "url", &tmp); + const char *url = json_object_get_string(tmp); /* checks whether discarded */ if (status == 0 && !buffer) { @@ -127,10 +135,17 @@ static void downloaded(void *closure, int status, const void *buffer, size_t siz goto end; } + // Save the profile to the database + struct json_object* dbr; + struct json_object* record = json_object_new_object(); + json_object_object_add(record, "key", objkey); + json_object_object_add(record, "value", json_object_get(subobj)); + afb_service_call_sync("persistence", "update", record, &dbr); + loaded(subobj, NULL); json_object_put(object); end: - free(url); + json_object_put(objkey); } /** public **************************************************************/ @@ -148,6 +163,21 @@ void agl_forgerock_setcb(void (*callback)(struct json_object *data, const char * onloaded = callback; } +void reply_from_db(void* closure, int status, struct json_object* result) +{ + if (status) + { + AFB_ERROR("Failed to retrieve profile from persistence!"); + return; + } + + struct json_object* tmp; + json_object_object_get_ex(result, "response", &tmp); + json_object_object_get_ex(tmp, "value", &tmp); + AFB_NOTICE("User profile retrieved from persistence: %s", json_object_to_json_string(tmp)); + loaded(json_object_get(tmp), NULL); +} + void agl_forgerock_download_request(const char *vin, const char *kind, const char *key) { int rc; @@ -155,7 +185,22 @@ void agl_forgerock_download_request(const char *vin, const char *kind, const cha rc = asprintf(&url, "%s?vin=%s&kind=%s&keytoken=%s", endpoint, vin, kind, key); if (rc >= 0) - aia_get(url, expiration_delay, oidc_name, oidc_name, downloaded, url); + { + struct json_object* obj = json_object_new_object(); + json_object_object_add(obj, "url", json_object_new_string(url)); + json_object_object_add(obj, "vin", json_object_new_string(vin)); + json_object_object_add(obj, "kind", json_object_new_string(kind)); + json_object_object_add(obj, "key", json_object_new_string(key)); + + // Async get from database and from forgerock + struct json_object* key = json_object_new_object(); + json_object_object_add(key, "key", json_object_get(obj)); + afb_service_call("persistence", "read", key, reply_from_db, NULL); + + // Async get from forgerock + aia_get(url, expiration_delay, oidc_name, oidc_name, downloaded, obj); + free(url); + } else AFB_ERROR("out of memory"); } diff --git a/agl-identity-service/src/agl-identity-binding.c b/agl-identity-service/src/agl-identity-binding.c index b10703e..12f43bd 100644 --- a/agl-identity-service/src/agl-identity-binding.c +++ b/agl-identity-service/src/agl-identity-binding.c @@ -29,6 +29,10 @@ #include "agl-forgerock.h" +#ifndef NULL +#define NULL 0 +#endif + static struct afb_event event; static struct json_object *current_identity; @@ -126,6 +130,18 @@ static int send_event_object(const char *name, const char *id, const char *nick) static void do_login(struct json_object *desc) { + if (current_identity == NULL && desc == NULL) return; // Switching from NULL to NULL -> 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_NOTICE("Reloging of the same user."); + return; // Switching from one user to the same user -> do nothing + } + } + struct json_object *object; /* switching the user */ @@ -226,6 +242,9 @@ static int service_init() if (afb_daemon_require_api("nfc", 1)) return -1; + if (afb_daemon_require_api("persistence", 1)) + return -1; + afb_service_call("nfc", "start", NULL, on_nfc_started, NULL); return 0; @@ -257,6 +276,35 @@ static void onevent(const char *event, struct json_object *object) AFB_WARNING("Unhandled event: %s", event); } +static void fake_auth(struct afb_req 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)) + { + afb_req_fail(req, "Missing arg: kind", NULL); + return; + } + + if (!json_object_object_get_ex(req_object, "key", &key_object)) + { + afb_req_fail(req, "Missing arg: key", NULL); + return; + } + + const char* kind = json_object_get_string(kind_object); + const char* key = json_object_get_string(key_object); + + send_event_object("incoming", key, key); + agl_forgerock_download_request(vin ? vin : default_vin, kind, key); + + afb_req_success(req, NULL, "fake auth success!"); +} + // NOTE: this sample does not use session to keep test a basic as possible // in real application most APIs should be protected with AFB_SESSION_CHECK static const struct afb_verb_v2 verbs[]= @@ -266,6 +314,7 @@ static const struct afb_verb_v2 verbs[]= {"fake-login" , fake_login , NULL, "fake a login" , AFB_SESSION_NONE }, {"logout" , logout , NULL, "log the current user out", AFB_SESSION_NONE }, {"get" , get , NULL, "get data" , AFB_SESSION_NONE }, + {"fake-auth" , fake_auth , NULL, "fake an authentication" , AFB_SESSION_NONE }, {NULL} }; @@ -282,4 +331,3 @@ const struct afb_binding_v2 afbBindingV2 = }; /* vim: set colorcolumn=80: */ - diff --git a/agl-identity-service/src/aia-get.c b/agl-identity-service/src/aia-get.c index 93d9470..56c82b0 100644 --- a/agl-identity-service/src/aia-get.c +++ b/agl-identity-service/src/aia-get.c @@ -66,13 +66,6 @@ static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; static int curl_initialized = 0; - - - - - - - static void perform_query_callback(void *closure, int status, CURL *curl, const char *result, size_t size) { struct keyrequest *kr = closure; |