2023-12-01 14:05:42 +01:00
|
|
|
class ExtAppRoleController extends HTMLElement{
|
|
|
|
|
|
|
|
#boot;
|
|
|
|
#roles;
|
|
|
|
#users;
|
|
|
|
#serviceurl;
|
|
|
|
#appid;
|
2024-07-04 11:20:11 +02:00
|
|
|
#groupid;
|
2023-12-01 14:05:42 +01:00
|
|
|
#loading = false;
|
2024-07-04 11:20:11 +02:00
|
|
|
|
|
|
|
#header;
|
|
|
|
#section;
|
|
|
|
|
|
|
|
#userfilter = null;
|
|
|
|
#rolefilter = null;
|
2023-12-01 14:05:42 +01:00
|
|
|
|
|
|
|
constructor(){
|
|
|
|
super()
|
2024-07-04 11:20:11 +02:00
|
|
|
this.attachShadow({ mode: "open" });
|
2023-12-01 14:05:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
connectedCallback(){
|
|
|
|
this.#appid = this.getAttribute("appid")
|
2024-07-04 11:20:11 +02:00
|
|
|
this.#groupid = this.getAttribute("groupid")
|
2024-07-04 13:32:03 +02:00
|
|
|
this.#boot = document.querySelector("d4s-boot-2")
|
|
|
|
this.#serviceurl = this.#boot.url
|
2023-12-01 14:05:42 +01:00
|
|
|
if(!this.#loading){
|
|
|
|
this.#loading = true
|
|
|
|
this.loadData()
|
|
|
|
}
|
2024-07-04 11:20:11 +02:00
|
|
|
this.renderStructure()
|
2023-12-01 14:05:42 +01:00
|
|
|
}
|
|
|
|
|
2024-07-04 11:20:11 +02:00
|
|
|
renderStructure(){
|
|
|
|
this.shadowRoot.innerHTML = `
|
|
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous"/>
|
|
|
|
<style>
|
|
|
|
.dyn-opacity{
|
|
|
|
opacity: .8;
|
|
|
|
transition: opacity .5s;
|
|
|
|
}
|
|
|
|
.dyn-opacity:hover{
|
|
|
|
opacity: 1;
|
|
|
|
}
|
|
|
|
</style>
|
|
|
|
<header></header>
|
|
|
|
<section></section>
|
|
|
|
`
|
|
|
|
this.#header = this.shadowRoot.querySelector("header")
|
|
|
|
this.#section = this.shadowRoot.querySelector("section")
|
|
|
|
}
|
|
|
|
|
|
|
|
renderHeader(){
|
|
|
|
this.#header.innerHTML = `
|
|
|
|
<div class="row g-0 mb-3" style="gap: 1rem;align-items:baseline;">
|
|
|
|
<input class="col-3 form-control" id="search_user_filter" placeholder="Search by name" style="min-width: 10rem;max-width: 100rem;width: 30rem">
|
|
|
|
<div class="role-filter col-9 d-flex" style="gap:1rem;">
|
|
|
|
${
|
|
|
|
this.#roles.map(r=>{
|
|
|
|
return r.name === 'uma_protection' ? '' : `
|
|
|
|
<div class="form-check form-switch">
|
|
|
|
<label class="form-check-label">
|
|
|
|
<input class="form-check-input" type="checkbox" name="${r.id}">
|
|
|
|
<span>${r.name}</span>
|
|
|
|
</label>
|
|
|
|
</div>
|
|
|
|
`
|
|
|
|
}).join("")
|
|
|
|
}
|
|
|
|
</div>
|
|
|
|
</div>`
|
|
|
|
this.attachHeaderEvents()
|
|
|
|
}
|
|
|
|
|
|
|
|
renderUsers(){
|
|
|
|
var users = this.#rolefilter && this.#rolefilter.length ?
|
|
|
|
this.#users.filter(u=>{
|
|
|
|
const assigned = u.mappings.map(m=>m.id)
|
|
|
|
return this.#rolefilter.reduce((a,f)=>{
|
|
|
|
return a && (assigned.indexOf(f) !== -1)
|
|
|
|
}, true)
|
|
|
|
}) : this.#users
|
|
|
|
users = this.#userfilter ?
|
|
|
|
this.#users.filter(u=>{
|
|
|
|
return u.username.toLowerCase().startsWith(this.#userfilter) ||
|
|
|
|
u.lastName.toLowerCase().startsWith(this.#userfilter) ||
|
|
|
|
u.firstName.toLowerCase().startsWith(this.#userfilter) ||
|
|
|
|
u.email.toLowerCase().startsWith(this.#userfilter)}) : users
|
|
|
|
const html = `
|
|
|
|
<ul name="users" style="list-style:none" class="d-flex flex-row flex-wrap gap-2 p-0">
|
|
|
|
${users.map( u=>{
|
|
|
|
return `
|
|
|
|
<li name="user" data-userid="${u.id}" style="user-select:none">
|
|
|
|
<div class="card" style="width:20rem;height:10rem">
|
|
|
|
<div class="card-header">
|
|
|
|
<span name="name" class="fw-bold">${u.firstName} ${u.lastName}</span>
|
|
|
|
</div>
|
|
|
|
<div class="card-body d-flex" style="overflow-y:auto;padding:.5rem">
|
|
|
|
<ul name="roles" style="list-style:none;min-width:70%;overflow-y:auto;align-items: flex-start" class="d-flex flex-row flex-wrap gap-1 pt-1 pb-1 ps-0 me-2">
|
|
|
|
${this.renderMappings(u)}
|
|
|
|
</ul>
|
|
|
|
</divExtAppRoleController>
|
|
|
|
</div>
|
|
|
|
</li>`
|
|
|
|
}).join('')}
|
|
|
|
</li>
|
|
|
|
</ul>
|
|
|
|
`
|
|
|
|
this.#section.innerHTML = html
|
|
|
|
this.attachSectionEvents()
|
|
|
|
}
|
|
|
|
|
|
|
|
renderMappings(u){
|
|
|
|
const html = `
|
|
|
|
<ul name="roles" style="list-style:none;min-width:70%;overflow-y:auto" class="d-flex flex-row flex-wrap gap-1 pt-1 pb-1 ps-0 me-2">
|
|
|
|
${this.#roles ? this.#roles.map(r=>{
|
|
|
|
const assigned = this.isAssigned(u, r)
|
|
|
|
return r.name !== 'uma_protection' ? `
|
|
|
|
<li data-userid="${u.id}" data-roleid="${r.id}" name="role" class="dyn-opacity btn m-0 p-0 ${assigned ? 'active' : ''}">
|
|
|
|
<span style="pointer-events: none" name="name" class="badge bg-${assigned ? 'success' : 'secondary'}">${r.name}</span>
|
|
|
|
</li>` : ''
|
|
|
|
}).join('') : ''}
|
|
|
|
</ul>
|
|
|
|
`
|
|
|
|
return html
|
|
|
|
}
|
|
|
|
|
|
|
|
isAssigned(u, r){
|
|
|
|
return u.mappings && u.mappings.filter(m=>m.id === r.id).length > 0
|
|
|
|
}
|
|
|
|
|
|
|
|
attachSectionEvents(){
|
|
|
|
this.#section.querySelector("ul[name=users]").addEventListener("click", ev=>{
|
|
|
|
const tgt = ev.target
|
|
|
|
if(tgt.getAttribute("name") === "role"){
|
|
|
|
const uid = tgt.getAttribute("data-userid")
|
|
|
|
const rid = tgt.getAttribute("data-roleid")
|
|
|
|
if(tgt.classList.contains("active")){
|
|
|
|
this.toggleMapping(uid, rid)
|
|
|
|
}else{
|
|
|
|
this.toggleMapping(uid, rid, true)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
attachHeaderEvents(){
|
|
|
|
this.#header.querySelector("#search_user_filter").addEventListener("input", ev=>{
|
|
|
|
this.#userfilter = ev.target.value.toLowerCase()
|
|
|
|
this.renderUsers()
|
|
|
|
})
|
|
|
|
|
|
|
|
this.#header.querySelector("div.role-filter").addEventListener("change", ev=>{
|
|
|
|
this.#rolefilter = Array.prototype.slice.call(ev.currentTarget.querySelectorAll("input:checked")).map(r=>r.name)
|
|
|
|
this.renderUsers()
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-12-01 14:05:42 +01:00
|
|
|
loadData(){
|
|
|
|
Promise.all([
|
|
|
|
this.loadEligibleUsers(), this.loadRoles()
|
|
|
|
]).then(()=>{
|
2024-07-04 11:20:11 +02:00
|
|
|
//console.log("Done. Ui should be complete now")
|
2023-12-01 14:05:42 +01:00
|
|
|
}).catch(err=>alert(err))
|
|
|
|
}
|
|
|
|
|
|
|
|
loadEligibleUsers(){
|
2024-07-04 11:20:11 +02:00
|
|
|
const url = this.#serviceurl + `/admin/realms/d4science/groups/${this.#groupid}/members?first=0&max=-1&briefRepresentation=true`
|
2023-12-01 14:05:42 +01:00
|
|
|
return this.#boot.secureFetch(url).then(resp=>{
|
|
|
|
if(resp.ok){
|
|
|
|
return resp.json()
|
|
|
|
}else if(resp.status === 403){
|
|
|
|
throw "Fetching users: You are not allowed to manage roles for this application."
|
|
|
|
}else{
|
|
|
|
throw "Fetching users: Unspecified error"
|
|
|
|
}
|
|
|
|
}).then(json=>{
|
|
|
|
this.#users = json
|
2024-07-04 11:20:11 +02:00
|
|
|
const prom = this.loadMappings()
|
|
|
|
this.renderUsers()
|
|
|
|
return prom
|
2023-12-01 14:05:42 +01:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
loadRoles(){
|
|
|
|
const url = this.#serviceurl + `/admin/realms/d4science/clients/${this.#appid}/roles`
|
|
|
|
return this.#boot.secureFetch(url).then(resp=>{
|
|
|
|
if(resp.ok){
|
|
|
|
return resp.json()
|
|
|
|
}else if(resp.status === 403){
|
|
|
|
throw "Fetching roles: You are not allowed to manage roles for this application."
|
|
|
|
}else{
|
|
|
|
throw "Fetching roles: Unspecified error"
|
|
|
|
}
|
|
|
|
}).then(json=>{
|
|
|
|
this.#roles = json
|
2024-07-04 11:20:11 +02:00
|
|
|
this.renderHeader()
|
2023-12-01 14:05:42 +01:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
loadMappings(){
|
|
|
|
return Promise.all(
|
2024-07-04 11:20:11 +02:00
|
|
|
this.#users.map(u => {
|
|
|
|
return this.loadMapping(u)
|
2023-12-01 14:05:42 +01:00
|
|
|
})
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2024-07-04 11:20:11 +02:00
|
|
|
loadMapping(u){
|
|
|
|
const url = this.#serviceurl + `/admin/realms/d4science/users/${u.id}/role-mappings/clients/${this.#appid}`
|
|
|
|
return this.#boot.secureFetch(url).then(resp=>{
|
|
|
|
if(resp.ok){
|
|
|
|
return resp.json()
|
|
|
|
}else if(resp.status === 403){
|
|
|
|
throw "Fetching role mappings: You are not allowed to manage roles for this application."
|
|
|
|
}else{
|
|
|
|
throw "Fetching role mappings: Unspecified error"
|
|
|
|
}
|
|
|
|
}).then(json=>{
|
|
|
|
u.mappings = json
|
|
|
|
this.renderUsers()
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
toggleMapping(uid, rid, add){
|
|
|
|
const url = this.#serviceurl + `/admin/realms/d4science/users/${uid}/role-mappings/clients/${this.#appid}`
|
|
|
|
const role = this.#roles.filter(r=>r.id === rid)
|
|
|
|
if(role.length !== 1){
|
|
|
|
alert("Something went wrong with looking up the role")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
this.#boot.secureFetch(url, { method : add ? "POST" : "DELETE", body : JSON.stringify(role), headers : { "Content-Type" : "application/json"}}).then(resp=>{
|
|
|
|
if(resp.ok){
|
|
|
|
return resp.text()
|
|
|
|
}else if(resp.status === 403){
|
|
|
|
throw "Assigning or removing role: You are not allowed to manage roles for this application."
|
|
|
|
}else{
|
|
|
|
throw "Assigning or removing role: Unspecified error"
|
|
|
|
}
|
|
|
|
}).then(()=>{
|
|
|
|
return this.loadMapping(this.#users.filter(u=>u.id === uid)[0])
|
|
|
|
}).catch(err=>alert(err))
|
|
|
|
}
|
2023-12-01 14:05:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
window.customElements.define('d4s-extapp-role-manager', ExtAppRoleController);
|