refactored boot and fixed corresponding calls in social and storage

This commit is contained in:
dcore94 2022-04-12 19:08:38 +02:00
parent a9b018b39f
commit 918edda1f6
4 changed files with 562 additions and 213 deletions

248
boot/d4s-boot-old.js Normal file
View File

@ -0,0 +1,248 @@
/**
* D4Science boot component that handles keykloak authz
* <d4s-boot-2
* /@url: keycloak url, for example: https://accounts.pre.d4science.org/auth]
* /@realm: is the realm [defaults to: d4science]
* /@gateway: is the client id, for example: "pre.d4science.org"
* /@context: is the audience, for example: "%2Fpred4s%2Fpreprod%2FpreVRE"
* /@redirect-url: where to go on logout
*/
window.customElements.define('d4s-boot-2', class extends HTMLElement {
#keycloak = null
#authorization = null
#url = null
#realm = "d4science"
#clientId = null
#redirectUrl = null
#audience = null
// loading attempts nr and timer between attempts
#attempts = 6
#timer = 500
constructor() {
super()
}
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")
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)
}
})

View File

@ -1,12 +1,3 @@
/**
* D4Science boot component that handles keykloak authz
* <d4s-boot-2
* /@url: keycloak url, for example: https://accounts.pre.d4science.org/auth]
* /@realm: is the realm [defaults to: d4science]
* /@gateway: is the client id, for example: "pre.d4science.org"
* /@context: is the audience, for example: "%2Fpred4s%2Fpreprod%2FpreVRE"
* /@redirect-url: where to go on logout
*/
window.customElements.define('d4s-boot-2', class extends HTMLElement { window.customElements.define('d4s-boot-2', class extends HTMLElement {
#keycloak = null #keycloak = null
@ -18,25 +9,60 @@ window.customElements.define('d4s-boot-2', class extends HTMLElement {
#audience = null #audience = null
// loading attempts nr and timer between attempts // loading attempts nr and timer between attempts
#attempts = 6 //#attempts = 6
#timer = 500 //#timer = 500
#locked = true
#queue = []
#interval = null
constructor() { constructor() {
super() super()
} }
unlock(){
this.#locked = false
}
connectedCallback(){ connectedCallback(){
this.startStateChecker()
this.loadKeycloak().then(()=>{
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') { if (typeof Keycloak === 'undefined') {
const script = document.createElement('script') const script = document.createElement('script')
script.src = this.#url + '/js/keycloak.js' script.src = this.#url + '/js/keycloak.js'
script.type = 'text/javascript' script.type = 'text/javascript'
script.addEventListener('load', () => { script.addEventListener('load', resolve)
this.initKeycloak()
})
document.head.appendChild(script) document.head.appendChild(script)
} else { } else {
this.initKeycloak() resolve()
} }
})
} }
initKeycloak(){ initKeycloak(){
@ -46,34 +72,54 @@ window.customElements.define('d4s-boot-2', class extends HTMLElement {
clientId: this.#clientId clientId: this.#clientId
}) })
const keycloak = this.#keycloak return this.#keycloak.init({onLoad: 'login-required', checkLoginIframe: false })
const url = this.#url }
keycloak.init({ loadKeycloakAuthorization(){
onLoad: 'login-required' return new Promise((resolve, reject)=>{
, checkLoginIframe: false
}).then((authenticated) => {
console.log(authenticated ? 'Authenticated in ' + this.#realm : 'Not authenticated')
if (typeof KeycloakAuthorization === 'undefined') { if (typeof KeycloakAuthorization === 'undefined') {
const authz = document.createElement('script') const authz = document.createElement('script')
authz.src = url + '/js/keycloak-authz.js' authz.src = this.#url + '/js/keycloak-authz.js'
authz.type = 'text/javascript' authz.type = 'text/javascript'
authz.addEventListener('load', () => { authz.addEventListener('load', resolve)
this.#authorization = new KeycloakAuthorization(keycloak)
})
document.head.appendChild(authz) document.head.appendChild(authz)
} else { } else {
this.#authorization = new KeycloakAuthorization(keycloak) resolve()
} }
//console.log("expires: " + this.expirationDate(keycloak.tokenParsed.exp))
}).catch(function(err) {
console.error("Failed to initialize d4science boot component")
console.error(err)
}) })
} }
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) )
})
//clear queue
this.#queue = []
console.log("Resolving all fetches")
return Promise.all(promises)
}
).catch(err => alert("Unable to make calls: " + err))
}
}
}, 300)
}
parseJwt(token) { parseJwt(token) {
var base64Url = token.split('.')[1] var base64Url = token.split('.')[1]
var base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/') var base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/')
@ -103,52 +149,25 @@ window.customElements.define('d4s-boot-2', class extends HTMLElement {
}) })
} }
service(endpoint, method, params, onSuccess, onForbidden) { secureFetch(url, request){
if (this.#authorization == null) { const p = new Promise((resolve, reject)=>{
if (this.#attempts-- > 0) { let req = request ? request : {}
setTimeout(() => this.service(endpoint, method, params, onSuccess, onForbidden), this.#timer) if("headers" in req){
return req.headers["Authorization"] = null
} else { } else {
console.error("Impossible to initialize D4Science authorization component") req.headers = { "Authorization" : null}
throw "Fatal error"
} }
} console.log("Queued request to url ", url)
this.#keycloak.updateToken(30).then(() => { this.#queue.push({ url : url, request : req, resolve : resolve, reject : reject})
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')
}) })
return p
} }
download(endpoint, name) { download(url, name) {
this.#keycloak.updateToken(30).then(() => { this.secureFetch(url).then(reply=>{
const audience = encodeURIComponent(this.#audience) if(reply.status !== 200) throw "Unable to download";
this.#authorization.entitlement(audience).then(function (rpt) { return reply.blob()
fetch(endpoint, { }).then(blob=>{
method: 'GET',
headers: {
'Authorization': 'Bearer ' + rpt
}
})
.then(response => response.blob())
.then(blob => {
const objectURL = URL.createObjectURL(blob) const objectURL = URL.createObjectURL(blob)
var tmplnk = document.createElement("a") var tmplnk = document.createElement("a")
tmplnk.download = name tmplnk.download = name
@ -156,11 +175,7 @@ window.customElements.define('d4s-boot-2', class extends HTMLElement {
document.body.appendChild(tmplnk) document.body.appendChild(tmplnk)
tmplnk.click() tmplnk.click()
document.body.removeChild(tmplnk) document.body.removeChild(tmplnk)
}) }).catch(err=>alert(err)
})
}).catch(function() {
onForbidden('Failed to refresh token')
})
} }
logout() { logout() {
@ -246,3 +261,86 @@ window.customElements.define('d4s-boot-2', class extends HTMLElement {
this.setAttribute("context", context) this.setAttribute("context", context)
} }
}) })
/*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')
})
}*/

View File

@ -3,31 +3,46 @@
*/ */
window.customElements.define('d4s-social-posts', class extends HTMLElement { 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 #quantity = 20
#boot = null;
#data = null;
#shadowRoot = null;
constructor() { constructor() {
super() super()
this.#boot = document.querySelector("d4s-boot-2")
this.#shadowRoot = this.attachShadow({mode: 'open'})
} }
connectedCallback() { connectedCallback() {
const shadowRoot = this.attachShadow({mode: 'open'}) this.fetchPosts()
}
var d4s = document.querySelector('d4s-boot-2') fetchPosts(){
d4s.service( const url = this.#basepath + '/2/posts/get-posts-vre'
this.#basepath + '/2/posts/get-posts-vre', this.#boot.secureFetch(url).then(
'GET', null, reply=>{
(resp) => { if(reply.status !== 200) throw "Unable to fetch posts";
var jresp = JSON.parse(resp) return reply.json()
if (jresp.success) { }
).then(data => {
this.#data = data
this.renderPosts()
}).catch(err=>alert("Unable to fetch posts " + err))
}
renderPosts(){
var ul = document.createElement('ul') var ul = document.createElement('ul')
jresp.result.reverse().forEach(post => { this.#data.result.reverse().forEach(post => {
if (this.#quantity-- > 0) { if (this.#quantity-- > 0) {
var li = document.createElement('li') var li = document.createElement('li')
li.setAttribute('class', 'd4s-social-post') li.setAttribute('class', 'd4s-social-post')
const datetime = new Date(0) const datetime = new Date(0)
datetime.setUTCMilliseconds(post.time) datetime.setUTCMilliseconds(post.time)
const thumbnail = (!post.thumbnail_url.startsWith('http')) ? 'https://' + d4s.clientId + post.thumbnail_url : post.thumbnail_url const thumbnail = (!post.thumbnail_url.startsWith('http')) ? 'https://' + this.#boot.clientId + post.thumbnail_url : post.thumbnail_url
li.innerHTML = ` li.innerHTML = `
<div class="d4s-post-heading"> <div class="d4s-post-heading">
<div class="d4s-post-avatar"> <div class="d4s-post-avatar">
@ -44,11 +59,7 @@ window.customElements.define('d4s-social-posts', class extends HTMLElement {
ul.appendChild(li) ul.appendChild(li)
} }
}) })
shadowRoot.appendChild(ul) this.#shadowRoot.appendChild(ul)
}
},
() => { alert('Forbidden') }
)
} }
static get observedAttributes() { static get observedAttributes() {

View File

@ -3,11 +3,10 @@
*/ */
class D4SStorageHtmlElement extends HTMLElement { class D4SStorageHtmlElement extends HTMLElement {
#d4s = null
#d4smissmsg = 'Required d4s-boot-2 component not found'
#baseurl = 'https://api.d4science.org/workspace' #d4smissmsg = 'Required d4s-boot-2 component not found'
//#baseurl = 'https://storagehub.pre.d4science.net/storagehub/workspace' //#baseurl = 'https://api.d4science.org/workspace'
#baseurl = 'https://workspace-repository.dev.d4science.org/storagehub/workspace'
constructor() { constructor() {
super() super()
@ -20,16 +19,6 @@ class D4SStorageHtmlElement extends HTMLElement {
root.appendChild(linkElem1) 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) { buildListUrl(id) {
return this.#baseurl + '/items/' + id + '/children' return this.#baseurl + '/items/' + id + '/children'
} }
@ -58,9 +47,13 @@ window.customElements.define('d4s-storage-tree', class extends D4SStorageHtmlEle
#loadedClass = 'loaded' #loadedClass = 'loaded'
#selectedClass = 'selected' #selectedClass = 'selected'
#selectedbgcolor = 'lightgray' #selectedbgcolor = 'lightgray'
#shadowRoot
#boot = null
constructor() { constructor() {
super() 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 download of a file (domenica.jpg): https://storagehub.pre.d4science.net/storagehub/workspace/items/a5086811-59d1-4230-99fa-98431e820cf1/download
*/ */
connectedCallback() { connectedCallback() {
const shadowRoot = this.attachShadow({mode: 'open'}) this.appendStylesheets(this.#shadowRoot)
this.appendStylesheets(shadowRoot)
var div = document.createElement('div') var div = document.createElement('div')
const style = ` const style = `
@ -94,22 +86,22 @@ window.customElements.define('d4s-storage-tree', class extends D4SStorageHtmlEle
` `
div.innerHTML = style div.innerHTML = style
this.d4s.service(
this.baseUrl + '/vrefolder', this.#boot.secureFetch(this.baseUrl + '/vrefolder').then(
'GET', null, (reply)=>{
(resp) => { if(reply.status !== 200) throw "Unable to load folder";
this.parseResponse(div, resp) return reply.json()
shadowRoot.appendChild(div) }
}, ).then(data => {
(err) => { alert(err) } this.parseResponse(div, data)
) this.#shadowRoot.appendChild(div)
}).catch(err=>alert(err))
} }
parseResponse(parentElement, response) { parseResponse(parentElement, jresp) {
parentElement.classList.add(this.#loadedClass) parentElement.classList.add(this.#loadedClass)
var ul = document.createElement('ul') var ul = document.createElement('ul')
ul.classList.add('nested') ul.classList.add('nested')
var jresp = JSON.parse(response)
if (jresp.item) { if (jresp.item) {
const itemname = jresp.item.displayName const itemname = jresp.item.displayName
const itemid = jresp.item.id const itemid = jresp.item.id
@ -172,12 +164,12 @@ window.customElements.define('d4s-storage-tree', class extends D4SStorageHtmlEle
if (li.classList.contains(this.#loadedClass)) { if (li.classList.contains(this.#loadedClass)) {
return return
} }
this.d4s.service( this.#boot.secureFetch(this.buildListUrl(id)).then(reply=>{
this.buildListUrl(id), if(reply.status !== 200) throw "Unable to load folder";
'GET', null, return reply.json()
(resp) => this.parseResponse(li, resp), }).then(data=>{
(err) => { alert(err) } this.parseResponse(li, data)
) }).catch(err=>alert(err))
} }
displayPath(li) { displayPath(li) {
@ -315,7 +307,7 @@ window.customElements.define('d4s-storage-folder', class extends D4SStorageHtmlE
} }
} else { } else {
console.info("Download of " + item.id) console.info("Download of " + item.id)
this.d4s.download(this.buildDownloadUrl(item.id), item.name) this.#boot.download(this.buildDownloadUrl(item.id), item.name)
} }
}) })