diff --git a/boot/d4s-boot-old.js b/boot/d4s-boot-old.js new file mode 100644 index 0000000..602fafe --- /dev/null +++ b/boot/d4s-boot-old.js @@ -0,0 +1,248 @@ +/** + * D4Science boot component that handles keykloak authz + * { + this.initKeycloak() + }) + document.head.appendChild(script) + } else { + this.initKeycloak() + } + } + + initKeycloak() { + this.#keycloak = new Keycloak({ + url: this.#url, + realm: this.#realm, + clientId: this.#clientId + }) + + const keycloak = this.#keycloak + const url = this.#url + + keycloak.init({ + onLoad: 'login-required' + , checkLoginIframe: false + + }).then((authenticated) => { + console.log(authenticated ? 'Authenticated in ' + this.#realm : 'Not authenticated') + if (typeof KeycloakAuthorization === 'undefined') { + const authz = document.createElement('script') + authz.src = url + '/js/keycloak-authz.js' + authz.type = 'text/javascript' + authz.addEventListener('load', () => { + this.#authorization = new KeycloakAuthorization(keycloak) + }) + document.head.appendChild(authz) + } else { + this.#authorization = new KeycloakAuthorization(keycloak) + } + //console.log("expires: " + this.expirationDate(keycloak.tokenParsed.exp)) + + }).catch(function(err) { + console.error("Failed to initialize d4science boot component") + console.error(err) + }) + } + + parseJwt(token) { + var base64Url = token.split('.')[1] + var base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/') + var jsonPayload = decodeURIComponent(atob(base64).split('').map(function(c) { + return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2) + }).join('')) + + return JSON.parse(jsonPayload); + } + + expirationDate(utc) { + let d = new Date(0) + d.setUTCSeconds(utc) + return d + } + + checkContext() { + const parseJwt = this.parseJwt + const expDt = this.expirationDate + const audience = encodeURIComponent(this.#audience) + this.#authorization.entitlement(audience).then(function (rpt) { + // onGrant callback function. + // If authorization was successful you'll receive an RPT + // with the necessary permissions to access the resource server + console.log(rpt) + //console.log("rpt expires: " + expDt(parseJwt(rpt).exp)) + }) + } + + service(endpoint, method, params, onSuccess, onForbidden) { + if (this.#authorization == null) { + if (this.#attempts-- > 0) { + setTimeout(() => this.service(endpoint, method, params, onSuccess, onForbidden), this.#timer) + return + } else { + console.error("Impossible to initialize D4Science authorization component") + throw "Fatal error" + } + } + this.#keycloak.updateToken(30).then(() => { + const audience = encodeURIComponent(this.#audience) + this.#authorization.entitlement(audience).then(function (rpt) { + var req = new XMLHttpRequest() + req.open(method, endpoint, true) + req.setRequestHeader('Accept', 'application/json') + req.setRequestHeader('Authorization', 'Bearer ' + rpt) + + req.onreadystatechange = function () { + if (req.readyState == 4) { + if (req.status == 200) { + onSuccess(req.response) + } else if (req.status == 403) { + onForbidden(req.statusText) + } + } + } + req.send(params) + }) + }).catch(function() { + onForbidden('Failed to refresh token') + }) + } + + download(endpoint, name) { + this.#keycloak.updateToken(30).then(() => { + const audience = encodeURIComponent(this.#audience) + this.#authorization.entitlement(audience).then(function (rpt) { + fetch(endpoint, { + method: 'GET', + headers: { + 'Authorization': 'Bearer ' + rpt + } + }) + .then(response => response.blob()) + .then(blob => { + const objectURL = URL.createObjectURL(blob) + var tmplnk = document.createElement("a") + tmplnk.download = name + tmplnk.href = objectURL + document.body.appendChild(tmplnk) + tmplnk.click() + document.body.removeChild(tmplnk) + }) + }) + }).catch(function() { + onForbidden('Failed to refresh token') + }) + } + + logout() { + if (this.#keycloak) { + if (!this.#redirectUrl) { + console.error("Missing required @redirectUrl attribute in d4s-boot") + } else { + this.#keycloak.logout({ + redirectUri: this.#redirectUrl + }) + } + } + } + + static get observedAttributes() { + return ["url", "realm", "gateway", "redirect-url", "context"]; + } + + attributeChangedCallback(name, oldValue, newValue) { + if (oldValue !== newValue) { + switch (name) { + case "url": + this.#url = newValue + break + case "realm": + this.#realm = newValue + break + case "gateway": + this.#clientId = newValue + break + case "redirect-url": + this.#redirectUrl = newValue + break + case "context": + this.#audience = newValue + break + } + } + } + + get url() { + return this.#url + } + + set url(url) { + this.#url = url + this.setAttribute("url", url) + } + + get realm() { + return this.#realm + } + + set realm(realm) { + this.#realm = realm + this.setAttribute("realm", realm) + } + + get clientId() { + return this.#clientId + } + + set clientId(clientId) { + this.#clientId = clientId + this.setAttribute("gateway", clientId) + } + + get redirectUrl() { + return this.#redirectUrl + } + + set redirectUrl(redirectUrl) { + this.#redirectUrl = redirectUrl + this.setAttribute("redirect-url", redirectUrl) + } + + get context() { + return this.#audience + } + + set context(context) { + this.#audience = context + this.setAttribute("context", context) + } +}) \ No newline at end of file diff --git a/boot/d4s-boot.js b/boot/d4s-boot.js index 602fafe..5306e7b 100644 --- a/boot/d4s-boot.js +++ b/boot/d4s-boot.js @@ -1,12 +1,3 @@ -/** - * D4Science boot component that handles keykloak authz - * { + + console.log("Keycloak loaded") + return this.initKeycloak() + + }).then((authenticated)=>{ + + if(!authenticated) throw "Failed to authenticate"; + + console.log("Keycloak initialized and user authenticated") + return this.loadKeycloakAuthorization() + + }).then(()=>{ + + this.#authorization = new KeycloakAuthorization(this.#keycloak) + console.log("Keycloak authorization loaded and initialized", this.#authorization) + this.unlock() + + }).catch(err=>{ + console.error("Unable to initialize Keycloak",err) + }) + } + + loadKeycloak(){ + return new Promise((resolve, reject)=>{ if (typeof Keycloak === 'undefined') { const script = document.createElement('script') script.src = this.#url + '/js/keycloak.js' script.type = 'text/javascript' - script.addEventListener('load', () => { - this.initKeycloak() - }) + script.addEventListener('load', resolve) document.head.appendChild(script) } else { - this.initKeycloak() + resolve() } + }) + } + + initKeycloak(){ + this.#keycloak = new Keycloak({ + url: this.#url, + realm: this.#realm, + clientId: this.#clientId + }) + + return this.#keycloak.init({onLoad: 'login-required', checkLoginIframe: false }) + } + + loadKeycloakAuthorization(){ + return new Promise((resolve, reject)=>{ + if (typeof KeycloakAuthorization === 'undefined') { + const authz = document.createElement('script') + authz.src = this.#url + '/js/keycloak-authz.js' + authz.type = 'text/javascript' + authz.addEventListener('load', resolve) + document.head.appendChild(authz) + } else { + resolve() + } + }) } - initKeycloak() { - this.#keycloak = new Keycloak({ - url: this.#url, - realm: this.#realm, - clientId: this.#clientId - }) - - const keycloak = this.#keycloak - const url = this.#url - - keycloak.init({ - onLoad: 'login-required' - , checkLoginIframe: false - - }).then((authenticated) => { - console.log(authenticated ? 'Authenticated in ' + this.#realm : 'Not authenticated') - if (typeof KeycloakAuthorization === 'undefined') { - const authz = document.createElement('script') - authz.src = url + '/js/keycloak-authz.js' - authz.type = 'text/javascript' - authz.addEventListener('load', () => { - this.#authorization = new KeycloakAuthorization(keycloak) + startStateChecker(){ + this.#interval = window.setInterval(()=>{ + if(this.#locked){ + console.log("Still locked. Currently has " + this.#queue.length + " pending requests.") + }else{ + if(this.#queue.length > 0){ + const audience = encodeURIComponent(this.#audience) + console.log("Updating token") + this.#keycloak.updateToken(30).then(()=>{ + console.log("Checking entitlement") + return this.#authorization.entitlement(audience) + }).then( + rpt => { + console.log("Authorized") + //transform all queued requests to fetches + console.log("All pending requests to promises") + let promises = this.#queue.map(r => { + r.request.headers["Authorization"] = "Bearer " + rpt + return r.resolve( fetch(r.url, r.request) ) }) - document.head.appendChild(authz) - } else { - this.#authorization = new KeycloakAuthorization(keycloak) - } - //console.log("expires: " + this.expirationDate(keycloak.tokenParsed.exp)) - - }).catch(function(err) { - console.error("Failed to initialize d4science boot component") - console.error(err) - }) + //clear queue + this.#queue = [] + console.log("Resolving all fetches") + return Promise.all(promises) + } + ).catch(err => alert("Unable to make calls: " + err)) + } + } + }, 300) } parseJwt(token) { @@ -95,72 +141,41 @@ window.customElements.define('d4s-boot-2', class extends HTMLElement { const expDt = this.expirationDate const audience = encodeURIComponent(this.#audience) this.#authorization.entitlement(audience).then(function (rpt) { - // onGrant callback function. - // If authorization was successful you'll receive an RPT - // with the necessary permissions to access the resource server - console.log(rpt) - //console.log("rpt expires: " + expDt(parseJwt(rpt).exp)) + // onGrant callback function. + // If authorization was successful you'll receive an RPT + // with the necessary permissions to access the resource server + console.log(rpt) + //console.log("rpt expires: " + expDt(parseJwt(rpt).exp)) }) } - service(endpoint, method, params, onSuccess, onForbidden) { - if (this.#authorization == null) { - if (this.#attempts-- > 0) { - setTimeout(() => this.service(endpoint, method, params, onSuccess, onForbidden), this.#timer) - return - } else { - console.error("Impossible to initialize D4Science authorization component") - throw "Fatal error" - } + secureFetch(url, request){ + const p = new Promise((resolve, reject)=>{ + let req = request ? request : {} + if("headers" in req){ + req.headers["Authorization"] = null + } else { + req.headers = { "Authorization" : null} } - this.#keycloak.updateToken(30).then(() => { - const audience = encodeURIComponent(this.#audience) - this.#authorization.entitlement(audience).then(function (rpt) { - var req = new XMLHttpRequest() - req.open(method, endpoint, true) - req.setRequestHeader('Accept', 'application/json') - req.setRequestHeader('Authorization', 'Bearer ' + rpt) - - req.onreadystatechange = function () { - if (req.readyState == 4) { - if (req.status == 200) { - onSuccess(req.response) - } else if (req.status == 403) { - onForbidden(req.statusText) - } - } - } - req.send(params) - }) - }).catch(function() { - onForbidden('Failed to refresh token') - }) + console.log("Queued request to url ", url) + this.#queue.push({ url : url, request : req, resolve : resolve, reject : reject}) + }) + return p } - download(endpoint, name) { - this.#keycloak.updateToken(30).then(() => { - const audience = encodeURIComponent(this.#audience) - this.#authorization.entitlement(audience).then(function (rpt) { - fetch(endpoint, { - method: 'GET', - headers: { - 'Authorization': 'Bearer ' + rpt - } - }) - .then(response => response.blob()) - .then(blob => { - const objectURL = URL.createObjectURL(blob) - var tmplnk = document.createElement("a") - tmplnk.download = name - tmplnk.href = objectURL - document.body.appendChild(tmplnk) - tmplnk.click() - document.body.removeChild(tmplnk) - }) - }) - }).catch(function() { - onForbidden('Failed to refresh token') - }) + download(url, name) { + this.secureFetch(url).then(reply=>{ + if(reply.status !== 200) throw "Unable to download"; + return reply.blob() + }).then(blob=>{ + const objectURL = URL.createObjectURL(blob) + var tmplnk = document.createElement("a") + tmplnk.download = name + tmplnk.href = objectURL + document.body.appendChild(tmplnk) + tmplnk.click() + document.body.removeChild(tmplnk) + }).catch(err=>alert(err) } logout() { @@ -245,4 +260,87 @@ window.customElements.define('d4s-boot-2', class extends HTMLElement { this.#audience = context this.setAttribute("context", context) } -}) \ No newline at end of file +}) + + /*connectedCallback() { + if (typeof Keycloak === 'undefined') { + const script = document.createElement('script') + script.src = this.#url + '/js/keycloak.js' + script.type = 'text/javascript' + script.addEventListener('load', () => { + this.initKeycloak() + }) + document.head.appendChild(script) + } else { + this.initKeycloak() + } + }*/ + + /*initKeycloak() { + this.#keycloak = new Keycloak({ + url: this.#url, + realm: this.#realm, + clientId: this.#clientId + }) + + const keycloak = this.#keycloak + const url = this.#url + + keycloak.init({ + onLoad: 'login-required' + , checkLoginIframe: false + + }).then((authenticated) => { + console.log(authenticated ? 'Authenticated in ' + this.#realm : 'Not authenticated') + if (typeof KeycloakAuthorization === 'undefined') { + const authz = document.createElement('script') + authz.src = url + '/js/keycloak-authz.js' + authz.type = 'text/javascript' + authz.addEventListener('load', () => { + this.#authorization = new KeycloakAuthorization(keycloak) + }) + document.head.appendChild(authz) + } else { + this.#authorization = new KeycloakAuthorization(keycloak) + } + //console.log("expires: " + this.expirationDate(keycloak.tokenParsed.exp)) + + }).catch(function(err) { + console.error("Failed to initialize d4science boot component") + + }) + }*/ + + /*service(endpoint, method, params, onSuccess, onForbidden) { + if (this.#authorization == null) { + if (this.#attempts-- > 0) { + setTimeout(() => this.service(endpoint, method, params, onSuccess, onForbidden), this.#timer) + return + } else { + console.error("Impossible to initialize D4Science authorization component") + throw "Fatal error" + } + } + this.#keycloak.updateToken(30).then(() => { + const audience = encodeURIComponent(this.#audience) + this.#authorization.entitlement(audience).then(function (rpt) { + var req = new XMLHttpRequest() + req.open(method, endpoint, true) + req.setRequestHeader('Accept', 'application/json') + req.setRequestHeader('Authorization', 'Bearer ' + rpt) + + req.onreadystatechange = function () { + if (req.readyState == 4) { + if (req.status == 200) { + onSuccess(req.response) + } else if (req.status == 403) { + onForbidden(req.statusText) + } + } + } + req.send(params) + }) + }).catch(function() { + onForbidden('Failed to refresh token') + }) + }*/ diff --git a/social/d4s-social.js b/social/d4s-social.js index fdc353a..ac3c427 100644 --- a/social/d4s-social.js +++ b/social/d4s-social.js @@ -3,88 +3,99 @@ */ window.customElements.define('d4s-social-posts', class extends HTMLElement { - #basepath = 'https://api.d4science.org/rest' + //#basepath = 'https://api.d4science.org/rest' + #basepath = 'https://api.dev.d4science.org/rest' #quantity = 20 - + #boot = null; + #data = null; + #shadowRoot = null; + constructor() { - super() + super() + this.#boot = document.querySelector("d4s-boot-2") + this.#shadowRoot = this.attachShadow({mode: 'open'}) } connectedCallback() { - const shadowRoot = this.attachShadow({mode: 'open'}) - - var d4s = document.querySelector('d4s-boot-2') - d4s.service( - this.#basepath + '/2/posts/get-posts-vre', - 'GET', null, - (resp) => { - var jresp = JSON.parse(resp) - if (jresp.success) { - var ul = document.createElement('ul') - jresp.result.reverse().forEach(post => { - if (this.#quantity-- > 0) { - var li = document.createElement('li') - li.setAttribute('class', 'd4s-social-post') - const datetime = new Date(0) - datetime.setUTCMilliseconds(post.time) - const thumbnail = (!post.thumbnail_url.startsWith('http')) ? 'https://' + d4s.clientId + post.thumbnail_url : post.thumbnail_url - li.innerHTML = ` -
-
- -
- -
-
${post.description}
- - ` - ul.appendChild(li) - } - }) - shadowRoot.appendChild(ul) - } - }, - () => { alert('Forbidden') } - ) + this.fetchPosts() } - static get observedAttributes() { - return ["url", "quantity"]; - } - - attributeChangedCallback(name, oldValue, newValue) { - if (oldValue !== newValue) { - switch (name) { - case "url": - this.#basepath = newValue - break - case "quantity": - this.#quantity = newValue - break - } + fetchPosts(){ + const url = this.#basepath + '/2/posts/get-posts-vre' + this.#boot.secureFetch(url).then( + reply=>{ + if(reply.status !== 200) throw "Unable to fetch posts"; + return reply.json() } + ).then(data => { + this.#data = data + this.renderPosts() + }).catch(err=>alert("Unable to fetch posts " + err)) + } - get url() { - return this.#basepath - } + renderPosts(){ + var ul = document.createElement('ul') + this.#data.result.reverse().forEach(post => { + if (this.#quantity-- > 0) { + var li = document.createElement('li') + li.setAttribute('class', 'd4s-social-post') + const datetime = new Date(0) + datetime.setUTCMilliseconds(post.time) + const thumbnail = (!post.thumbnail_url.startsWith('http')) ? 'https://' + this.#boot.clientId + post.thumbnail_url : post.thumbnail_url + li.innerHTML = ` +
+
+ +
+ +
+
${post.description}
+ + ` + ul.appendChild(li) + } + }) + this.#shadowRoot.appendChild(ul) + } - set url(url) { - this.#basepath = url - this.setAttribute("url", url) - } + static get observedAttributes() { + return ["url", "quantity"]; + } - get quantity() { - return this.#quantity + attributeChangedCallback(name, oldValue, newValue) { + if (oldValue !== newValue) { + switch (name) { + case "url": + this.#basepath = newValue + break + case "quantity": + this.#quantity = newValue + break + } } + } + + get url() { + return this.#basepath + } - set quantity(quantity) { - this.#quantity = quantity - this.setAttribute("quantity", quantity) - } + set url(url) { + this.#basepath = url + this.setAttribute("url", url) + } + + get quantity() { + return this.#quantity + } + + set quantity(quantity) { + this.#quantity = quantity + this.setAttribute("quantity", quantity) + } }) diff --git a/storage/d4s-storage.js b/storage/d4s-storage.js index ce4f414..c71885d 100644 --- a/storage/d4s-storage.js +++ b/storage/d4s-storage.js @@ -3,11 +3,10 @@ */ class D4SStorageHtmlElement extends HTMLElement { - #d4s = null + #d4smissmsg = 'Required d4s-boot-2 component not found' - - #baseurl = 'https://api.d4science.org/workspace' - //#baseurl = 'https://storagehub.pre.d4science.net/storagehub/workspace' + //#baseurl = 'https://api.d4science.org/workspace' + #baseurl = 'https://workspace-repository.dev.d4science.org/storagehub/workspace' constructor() { super() @@ -20,16 +19,6 @@ class D4SStorageHtmlElement extends HTMLElement { root.appendChild(linkElem1) } - get d4s() { - if (this.#d4s == null) { - this.#d4s = document.querySelector('d4s-boot-2') - if (!this.#d4s) { - console.error(this.#d4smissmsg) - } - } - return this.#d4s - } - buildListUrl(id) { return this.#baseurl + '/items/' + id + '/children' } @@ -58,9 +47,13 @@ window.customElements.define('d4s-storage-tree', class extends D4SStorageHtmlEle #loadedClass = 'loaded' #selectedClass = 'selected' #selectedbgcolor = 'lightgray' - + #shadowRoot + #boot = null + constructor() { super() + this.#boot = document.querySelector('d4s-boot-2') + this.#shadowRoot = this.attachShadow({mode: 'open'}) } /* @@ -75,8 +68,7 @@ window.customElements.define('d4s-storage-tree', class extends D4SStorageHtmlEle download of a file (domenica.jpg): https://storagehub.pre.d4science.net/storagehub/workspace/items/a5086811-59d1-4230-99fa-98431e820cf1/download */ connectedCallback() { - const shadowRoot = this.attachShadow({mode: 'open'}) - this.appendStylesheets(shadowRoot) + this.appendStylesheets(this.#shadowRoot) var div = document.createElement('div') const style = ` @@ -94,22 +86,22 @@ window.customElements.define('d4s-storage-tree', class extends D4SStorageHtmlEle ` div.innerHTML = style - this.d4s.service( - this.baseUrl + '/vrefolder', - 'GET', null, - (resp) => { - this.parseResponse(div, resp) - shadowRoot.appendChild(div) - }, - (err) => { alert(err) } - ) + + this.#boot.secureFetch(this.baseUrl + '/vrefolder').then( + (reply)=>{ + if(reply.status !== 200) throw "Unable to load folder"; + return reply.json() + } + ).then(data => { + this.parseResponse(div, data) + this.#shadowRoot.appendChild(div) + }).catch(err=>alert(err)) } - parseResponse(parentElement, response) { + parseResponse(parentElement, jresp) { parentElement.classList.add(this.#loadedClass) var ul = document.createElement('ul') ul.classList.add('nested') - var jresp = JSON.parse(response) if (jresp.item) { const itemname = jresp.item.displayName const itemid = jresp.item.id @@ -172,12 +164,12 @@ window.customElements.define('d4s-storage-tree', class extends D4SStorageHtmlEle if (li.classList.contains(this.#loadedClass)) { return } - this.d4s.service( - this.buildListUrl(id), - 'GET', null, - (resp) => this.parseResponse(li, resp), - (err) => { alert(err) } - ) + this.#boot.secureFetch(this.buildListUrl(id)).then(reply=>{ + if(reply.status !== 200) throw "Unable to load folder"; + return reply.json() + }).then(data=>{ + this.parseResponse(li, data) + }).catch(err=>alert(err)) } displayPath(li) { @@ -315,7 +307,7 @@ window.customElements.define('d4s-storage-folder', class extends D4SStorageHtmlE } } else { console.info("Download of " + item.id) - this.d4s.download(this.buildDownloadUrl(item.id), item.name) + this.#boot.download(this.buildDownloadUrl(item.id), item.name) } })