Add API call for frontend to fetch list of valid roles from backend

- only relevant for admin config (addUser, editRole)
- admin only (double-checked)
This commit is contained in:
Christoph Kluge 2023-01-30 17:01:11 +01:00
parent 7d4f4ab2c8
commit 7fb94c33cf
5 changed files with 78 additions and 28 deletions

View File

@ -76,6 +76,7 @@ func (api *RestApi) MountRoutes(r *mux.Router) {
if api.Authentication != nil { if api.Authentication != nil {
r.HandleFunc("/jwt/", api.getJWT).Methods(http.MethodGet) r.HandleFunc("/jwt/", api.getJWT).Methods(http.MethodGet)
r.HandleFunc("/roles/", api.getRoles).Methods(http.MethodGet)
r.HandleFunc("/users/", api.createUser).Methods(http.MethodPost, http.MethodPut) r.HandleFunc("/users/", api.createUser).Methods(http.MethodPost, http.MethodPut)
r.HandleFunc("/users/", api.getUsers).Methods(http.MethodGet) r.HandleFunc("/users/", api.getUsers).Methods(http.MethodGet)
r.HandleFunc("/users/", api.deleteUser).Methods(http.MethodDelete) r.HandleFunc("/users/", api.deleteUser).Methods(http.MethodDelete)
@ -880,6 +881,22 @@ func (api *RestApi) getUsers(rw http.ResponseWriter, r *http.Request) {
json.NewEncoder(rw).Encode(users) json.NewEncoder(rw).Encode(users)
} }
func (api *RestApi) getRoles(rw http.ResponseWriter, r *http.Request) {
user := auth.GetUser(r.Context())
if (!user.HasRole(auth.RoleAdmin)) {
http.Error(rw, "only admins are allowed to fetch a list of roles", http.StatusForbidden)
return
}
roles, err := auth.GetValidRoles(user)
if err != nil {
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}
json.NewEncoder(rw).Encode(roles)
}
func (api *RestApi) updateUser(rw http.ResponseWriter, r *http.Request) { func (api *RestApi) updateUser(rw http.ResponseWriter, r *http.Request) {
if user := auth.GetUser(r.Context()); !user.HasRole(auth.RoleAdmin) { if user := auth.GetUser(r.Context()); !user.HasRole(auth.RoleAdmin) {
http.Error(rw, "only admins are allowed to update a user", http.StatusForbidden) http.Error(rw, "only admins are allowed to update a user", http.StatusForbidden)

View File

@ -12,6 +12,7 @@ import (
"net/http" "net/http"
"os" "os"
"time" "time"
"fmt"
"github.com/ClusterCockpit/cc-backend/pkg/log" "github.com/ClusterCockpit/cc-backend/pkg/log"
"github.com/gorilla/sessions" "github.com/gorilla/sessions"
@ -139,6 +140,15 @@ func IsValidRole(role string) bool {
return false return false
} }
func GetValidRoles(user *User) ([5]string, error) {
var vals [5]string
if (!user.HasRole(RoleAdmin)) {
return vals, fmt.Errorf("%#v: only admins are allowed to fetch a list of roles", user.Username)
} else {
return validRoles, nil
}
}
func GetUser(ctx context.Context) *User { func GetUser(ctx context.Context) *User {
x := ctx.Value(ContextUserKey) x := ctx.Value(ContextUserKey)
if x == nil { if x == nil {

View File

@ -8,6 +8,7 @@
import Options from './admin/Options.svelte' import Options from './admin/Options.svelte'
let users = [] let users = []
let roles = []
function getUserList() { function getUserList() {
fetch('/api/users/?via-ldap=false&not-just-user=true') fetch('/api/users/?via-ldap=false&not-just-user=true')
@ -17,19 +18,32 @@
}) })
} }
onMount(() => getUserList()) function getValidRoles() {
fetch('/api/roles/')
.then(res => res.json())
.then(rolesRaw => {
roles = rolesRaw
})
}
function initAdmin() {
getUserList()
getValidRoles()
}
onMount(() => initAdmin())
</script> </script>
<Row cols={2} class="p-2 g-2" > <Row cols={2} class="p-2 g-2" >
<Col class="mb-1"> <Col class="mb-1">
<AddUser on:reload={getUserList}/> <AddUser roles={roles} on:reload={getUserList}/>
</Col> </Col>
<Col class="mb-1"> <Col class="mb-1">
<ShowUsers on:reload={getUserList} bind:users={users}/> <ShowUsers on:reload={getUserList} bind:users={users}/>
</Col> </Col>
<Col> <Col>
<EditRole on:reload={getUserList}/> <EditRole roles={roles} on:reload={getUserList}/>
</Col> </Col>
<Col> <Col>
<EditProject on:reload={getUserList}/> <EditProject on:reload={getUserList}/>

View File

@ -7,6 +7,15 @@
let message = {msg: '', color: '#d63384'} let message = {msg: '', color: '#d63384'}
let displayMessage = false let displayMessage = false
let roleLabel = {
api: 'API',
user: 'User (regular user, same as if created via LDAP sync.)',
manager: 'Manager',
support: 'Support',
admin: 'Admin'
}
export let roles = []
async function handleUserSubmit() { async function handleUserSubmit() {
let form = document.querySelector('#create-user-form') let form = document.querySelector('#create-user-form')
@ -73,26 +82,19 @@
<div class="mb-3"> <div class="mb-3">
<p>Role:</p> <p>Role:</p>
{#each roles as role, i}
{#if i == 0}
<div> <div>
<input type="radio" id="user" name="role" value="user" checked/> <input type="radio" id={role} name="role" value={role} checked/>
<label for="user">User (regular user, same as if created via LDAP sync.)</label> <label for={role}>{roleLabel[role]}</label>
</div> </div>
{:else}
<div> <div>
<input type="radio" id="api" name="role" value="api"/> <input type="radio" id={role} name="role" value={role}/>
<label for="api">API</label> <label for={role}>{roleLabel[role]}</label>
</div>
<div>
<input type="radio" id="manager" name="role" value="manager"/>
<label for="manager">Manager</label>
</div>
<div>
<input type="radio" id="support" name="role" value="support"/>
<label for="support">Support</label>
</div>
<div>
<input type="radio" id="admin" name="role" value="admin"/>
<label for="admin">Admin</label>
</div> </div>
{/if}
{/each}
</div> </div>
<p style="display: flex; align-items: center;"> <p style="display: flex; align-items: center;">
<Button type="submit" color="primary">Submit</Button> <Button type="submit" color="primary">Submit</Button>

View File

@ -8,6 +8,15 @@
let message = {msg: '', color: '#d63384'} let message = {msg: '', color: '#d63384'}
let displayMessage = false let displayMessage = false
export let roles = []
let roleLabel = {
api: 'API',
user: 'User',
manager: 'Manager',
support: 'Support',
admin: 'Admin'
}
async function handleAddRole() { async function handleAddRole() {
const username = document.querySelector('#role-username').value const username = document.querySelector('#role-username').value
const role = document.querySelector('#role-select').value const role = document.querySelector('#role-select').value
@ -86,11 +95,9 @@
<input type="text" class="form-control" placeholder="username" id="role-username"/> <input type="text" class="form-control" placeholder="username" id="role-username"/>
<select class="form-select" id="role-select"> <select class="form-select" id="role-select">
<option selected value="">Role...</option> <option selected value="">Role...</option>
<option value="user">User</option> {#each roles as role}
<option value="manager">Manager</option> <option value={role}>{roleLabel[role]}</option>
<option value="support">Support</option> {/each}
<option value="admin">Admin</option>
<option value="api">API</option>
</select> </select>
<!-- PreventDefault on Sveltestrap-Button more complex to achieve than just use good ol' html button --> <!-- PreventDefault on Sveltestrap-Button more complex to achieve than just use good ol' html button -->
<!-- see: https://stackoverflow.com/questions/69630422/svelte-how-to-use-event-modifiers-in-my-own-components --> <!-- see: https://stackoverflow.com/questions/69630422/svelte-how-to-use-event-modifiers-in-my-own-components -->