intermittent commit (squash me later)
This commit is contained in:
parent
413bc29ec4
commit
632c03a2b9
@ -1,143 +1,187 @@
|
|||||||
class AbstractUserEntry {
|
$(function() {
|
||||||
constructor(username, tableRow) {
|
$("table").tablesorter({
|
||||||
this.username = username;
|
theme: 'bootstrap',
|
||||||
this.tableRow = tableRow;
|
headerTemplate: '{content} {icon}',
|
||||||
this.membershipToggleButton = tableRow.querySelector(".toggleMembershipButton");
|
cssIcon: 'bi-arrow-down-up',
|
||||||
this.membershipToggleButton.addEventListener("click", this.onButtonPress.bind(this));
|
cssIconNone: '',
|
||||||
};
|
cssIconAsc: 'bi-arrow-up',
|
||||||
};
|
cssIconDesc: 'bi-arrow-down',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
function removeUserFromFormField(username) {
|
class UserEntry {
|
||||||
let formField = document.getElementById("updated_members");
|
constructor(username, isMember, rowElement) {
|
||||||
let oldMembersList = JSON.parse(formField.value);
|
this.username = username;
|
||||||
let newMembersList = Array();
|
this.isMember = isMember;
|
||||||
for (let member of oldMembersList) {
|
this.rowElement = rowElement;
|
||||||
if (member != username) {
|
this.buttonElement = $(rowElement).find(".toggleMembershipButton");
|
||||||
newMembersList.push(member);
|
if (isMember) {
|
||||||
|
$(this.buttonElement).click(() => this.onClickLeave());
|
||||||
|
} else {
|
||||||
|
$(this.buttonElement).click(() => this.onClickJoin());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
formField.value = JSON.stringify(newMembersList);
|
|
||||||
adjustLastMemberButtonState();
|
|
||||||
};
|
|
||||||
|
|
||||||
function addUserToFormField(username) {
|
onClickLeave() {
|
||||||
let formField = document.getElementById("updated_members");
|
// Deactivate the last remaining member's togglebutton before leaving
|
||||||
let oldMembersList = JSON.parse(formField.value);
|
if ($(membersTable).find(".userEntry").length < 2) {
|
||||||
oldMembersList.push(username);
|
for (entry of $(membersTable).find(".userEntry")) {
|
||||||
formField.value = JSON.stringify(oldMembersList);
|
if ($(entry).id != this.username) {
|
||||||
adjustLastMemberButtonState();
|
$(entry).find(".toggleMembershipButton")[0].prop("disabled", true);
|
||||||
};
|
}
|
||||||
|
|
||||||
class MemberEntry extends AbstractUserEntry {
|
|
||||||
onButtonPress() {
|
|
||||||
this.tableRow.remove();
|
|
||||||
createRemovedMemberRow(this.username);
|
|
||||||
removeUserFromFormField(this.username);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
class NonMemberEntry extends AbstractUserEntry {
|
|
||||||
onButtonPress() {
|
|
||||||
this.tableRow.remove();
|
|
||||||
createAddedMemberRow(this.username);
|
|
||||||
addUserToFormField(this.username);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
function createRemovedMemberRow(username) {
|
|
||||||
let newTableRow = nonMembersTable.querySelector("tbody").insertRow(0);
|
|
||||||
newTableRow.className = "userEntry text-center bg-danger bg-gradient";
|
|
||||||
newTableRow.id = username;
|
|
||||||
|
|
||||||
let newTableHeader = document.createElement("th");
|
|
||||||
newTableHeader.scope = "row";
|
|
||||||
let newImage = document.createElement("img");
|
|
||||||
newImage.src = `/static/images/users/${username}/thumbnail.jpg`;
|
|
||||||
newImage.alt = `Profile picture for user ${username}.`;
|
|
||||||
newImage.className = "img-fluid rounded";
|
|
||||||
newImage.style = "max-width: 50px";
|
|
||||||
newTableHeader.appendChild(newImage);
|
|
||||||
newTableRow.appendChild(newTableHeader);
|
|
||||||
|
|
||||||
let newTableDataUsername = document.createElement("td");
|
|
||||||
let newUsernameAnchor = document.createElement("a");
|
|
||||||
newUsernameAnchor.href = `/users/view/${username}`;
|
|
||||||
newUsernameAnchor.textContent = username;
|
|
||||||
newTableDataUsername.appendChild(newUsernameAnchor);
|
|
||||||
newTableRow.appendChild(newTableDataUsername);
|
|
||||||
|
|
||||||
|
|
||||||
let newTableDataButton = document.createElement("td");
|
|
||||||
let newTableButton = document.createElement("button");
|
|
||||||
newTableButton.type = "button";
|
|
||||||
newTableButton.className = "toggleMembershipButton inProgress btn btn-outline-light";
|
|
||||||
newTableButton.disabled = true;
|
|
||||||
newTableButton.textContent = "Will be removed";
|
|
||||||
newTableDataButton.appendChild(newTableButton);
|
|
||||||
newTableRow.appendChild(newTableDataButton);
|
|
||||||
};
|
|
||||||
|
|
||||||
function createAddedMemberRow(username) {
|
|
||||||
let newTableRow = membersTable.querySelector("tbody").insertRow(0);
|
|
||||||
newTableRow.className = "userEntry text-center bg-success bg-gradient";
|
|
||||||
newTableRow.id = username;
|
|
||||||
|
|
||||||
let newTableHeader = document.createElement("th");
|
|
||||||
newTableHeader.scope = "row";
|
|
||||||
let newImage = document.createElement("img");
|
|
||||||
newImage.src = `/static/images/users/${username}/thumbnail.jpg`;
|
|
||||||
newImage.alt = `Profile picture for user ${username}.`;
|
|
||||||
newImage.className = "img-fluid rounded";
|
|
||||||
newImage.style = "max-width: 50px";
|
|
||||||
newTableHeader.appendChild(newImage);
|
|
||||||
newTableRow.appendChild(newTableHeader);
|
|
||||||
|
|
||||||
let newTableDataUsername = document.createElement("td");
|
|
||||||
let newUsernameAnchor = document.createElement("a");
|
|
||||||
newUsernameAnchor.href = `/users/view/${username}`;
|
|
||||||
newUsernameAnchor.textContent = username;
|
|
||||||
newTableDataUsername.appendChild(newUsernameAnchor);
|
|
||||||
newTableRow.appendChild(newTableDataUsername);
|
|
||||||
|
|
||||||
|
|
||||||
let newTableDataButton = document.createElement("td");
|
|
||||||
let newTableButton = document.createElement("button");
|
|
||||||
newTableButton.type = "button";
|
|
||||||
newTableButton.className = "toggleMembershipButton inProgress btn btn-outline-light";
|
|
||||||
newTableButton.disabled = true;
|
|
||||||
newTableButton.textContent = "Will be added";
|
|
||||||
newTableDataButton.appendChild(newTableButton);
|
|
||||||
newTableRow.appendChild(newTableDataButton);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If there is only one member in the group member table, disables that member's
|
|
||||||
* remove button.
|
|
||||||
* If there is more than one member, activates the removal button.
|
|
||||||
*/
|
|
||||||
function adjustLastMemberButtonState() {
|
|
||||||
memberRows = membersTable.querySelectorAll(".userEntry");
|
|
||||||
if (memberRows.length == 1) {
|
|
||||||
memberRows[0].querySelector(".toggleMembershipButton").disabled = true;
|
|
||||||
} else {
|
|
||||||
for (let button of membersTable.querySelectorAll(".toggleMembershipButton")) {
|
|
||||||
if (!button.className.includes("inProgress")) {
|
|
||||||
button.disabled = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.setButtonAppearanceInProgress();
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
context: {
|
||||||
|
"userEntry": this
|
||||||
|
},
|
||||||
|
url: `/api/group/${groupname}`,
|
||||||
|
type: "GET",
|
||||||
|
dataType: "json",
|
||||||
|
}).done(function(groupJson) {
|
||||||
|
$.ajax({
|
||||||
|
context: {
|
||||||
|
"userEntry": this.userEntry,
|
||||||
|
},
|
||||||
|
url: `/api/group/${groupname}`,
|
||||||
|
type: "PUT",
|
||||||
|
dataType: "json",
|
||||||
|
data: JSON.stringify(groupJson),
|
||||||
|
contentType: "application/json",
|
||||||
|
})
|
||||||
|
.done(function(groupJson) {
|
||||||
|
this.userEntry.isMember = false;
|
||||||
|
$(this.userEntry.buttonElement).off("click");
|
||||||
|
$(this.userEntry.buttonElement).click(() => this.userEntry.onClickJoin());
|
||||||
|
$(this.userEntry.rowElement).prependTo($("#tableNonMembers").find("tbody"));
|
||||||
|
this.userEntry.setButtonAppearanceJoinGroup();
|
||||||
|
})
|
||||||
|
.fail(function(xhr, status, errorThrown) {
|
||||||
|
console.log(`Error: ${errorThrown}`);
|
||||||
|
console.log(`Status: ${status}`);
|
||||||
|
console.dir(xhr);
|
||||||
|
alert("Sorry, there was a problem sending information to the server.");
|
||||||
|
|
||||||
|
this.userEntry.setButtonAppearanceLeaveGroup();
|
||||||
|
});
|
||||||
|
}).fail(function(xhr, status, errorThrown) {
|
||||||
|
console.log(`Error: ${errorThrown}`);
|
||||||
|
console.log(`Status: ${status}`);
|
||||||
|
alert("Sorry, there was a problem retrieving information from the server.");
|
||||||
|
|
||||||
|
this.userEntry.setButtonAppearanceLeaveGroup();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onClickJoin() {
|
||||||
|
this.setButtonAppearanceInProgress();
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
context: {
|
||||||
|
"userEntry": this
|
||||||
|
},
|
||||||
|
url: `/api/group/${groupname}`,
|
||||||
|
type: "GET",
|
||||||
|
dataType: "json",
|
||||||
|
}).done(function(groupJson) {
|
||||||
|
// Add current user to the members array
|
||||||
|
groupJson.group.members.push(this.userEntry.username);
|
||||||
|
$.ajax({
|
||||||
|
context: {
|
||||||
|
"userEntry": this.userEntry,
|
||||||
|
},
|
||||||
|
url: `/api/group/${groupname}`,
|
||||||
|
type: "PUT",
|
||||||
|
dataType: "json",
|
||||||
|
data: JSON.stringify(groupJson),
|
||||||
|
contentType: "application/json",
|
||||||
|
})
|
||||||
|
.done(function(groupJson) {
|
||||||
|
this.userEntry.isMember = true;
|
||||||
|
$(this.userEntry.buttonElement).off("click");
|
||||||
|
$(this.userEntry.buttonElement).click(() => this.userEntry.onClickLeave());
|
||||||
|
$(this.userEntry.rowElement).prependTo($("#tableMembers").find("tbody"));
|
||||||
|
this.userEntry.setButtonAppearanceLeaveGroup();
|
||||||
|
})
|
||||||
|
.fail(function(xhr, status, errorThrown) {
|
||||||
|
console.log(`Error: ${errorThrown}`);
|
||||||
|
console.log(`Status: ${status}`);
|
||||||
|
console.dir(xhr);
|
||||||
|
alert("Sorry, there was a problem sending information to the server.");
|
||||||
|
|
||||||
|
this.userEntry.setButtonAppearanceJoinGroup();
|
||||||
|
});
|
||||||
|
}).fail(function(xhr, status, errorThrown) {
|
||||||
|
console.log(`Error: ${errorThrown}`);
|
||||||
|
console.log(`Status: ${status}`);
|
||||||
|
alert("Sorry, there was a problem retrieving information from the server.");
|
||||||
|
this.userEntry.setButtonAppearanceJoinGroup();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setButtonAppearanceInProgress() {
|
||||||
|
this.buttonElement.removeClass("btn-danger btn-success btn-secondary");
|
||||||
|
this.buttonElement.addClass("btn-secondary");
|
||||||
|
this.buttonElement.empty();
|
||||||
|
this.buttonElement.html(
|
||||||
|
'<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>' +
|
||||||
|
'<span> Loading...</span>'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
setButtonAppearanceLeaveGroup() {
|
||||||
|
this.buttonElement.removeClass("btn-danger btn-success btn-secondary");
|
||||||
|
this.buttonElement.addClass("btn-danger");
|
||||||
|
this.buttonElement.empty();
|
||||||
|
this.buttonElement.html(
|
||||||
|
'<i class="bi-box-arrow-right"></i> Remove from group'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
setButtonAppearanceJoinGroup() {
|
||||||
|
this.buttonElement.removeClass("btn-danger btn-success btn-secondary");
|
||||||
|
this.buttonElement.addClass("btn-success");
|
||||||
|
this.buttonElement.empty();
|
||||||
|
this.buttonElement.html(
|
||||||
|
'<i class="bi-box-arrow-right"></i> Add to group'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const membersTable = document.getElementById("groupMembers");
|
function getUserEntries() {
|
||||||
const nonMembersTable = document.getElementById("groupNonMembers");
|
let userEntries = [];
|
||||||
let memberEntries = new Set();
|
|
||||||
let nonMemberEntries = new Set();
|
|
||||||
|
|
||||||
for (let userEntry of document.body.querySelectorAll(".userEntry")) {
|
// Construct member entries
|
||||||
if (userEntry.parentElement.parentElement.id === "groupMembers") {
|
for (let entry of membersTable.find("tbody").find(".userEntry")) {
|
||||||
memberEntries.add(new MemberEntry(userEntry.id, userEntry));
|
userEntries.push(new UserEntry(
|
||||||
} else if (userEntry.parentElement.parentElement.id === "groupNonMembers") {
|
entry.id,
|
||||||
nonMemberEntries.add(new NonMemberEntry(userEntry.id, userEntry));
|
true,
|
||||||
|
entry
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Construct nonmember entries
|
||||||
|
for (let entry of nonMembersTable.find("tbody").find(".userEntry")) {
|
||||||
|
userEntries.push(new UserEntry(
|
||||||
|
entry.id,
|
||||||
|
false,
|
||||||
|
entry
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return userEntries;
|
||||||
}
|
}
|
||||||
adjustLastMemberButtonState();
|
|
||||||
|
let nonMembersTable = undefined;
|
||||||
|
let membersTable = undefined;
|
||||||
|
let entries = undefined;
|
||||||
|
const groupname = window.location.pathname.split("/").pop();
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
nonMembersTable = $("#tableNonMembers");
|
||||||
|
membersTable = $("#tableMembers");
|
||||||
|
entries = getUserEntries();
|
||||||
|
});
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
$(function() {
|
|
||||||
$("table").tablesorter({
|
|
||||||
theme: 'bootstrap',
|
|
||||||
headerTemplate: '{content} {icon}',
|
|
||||||
cssIcon: 'bi-arrow-down-up',
|
|
||||||
cssIconNone: '',
|
|
||||||
cssIconAsc: 'bi-arrow-up',
|
|
||||||
cssIconDesc: 'bi-arrow-down',
|
|
||||||
});
|
|
||||||
});
|
|
@ -4,13 +4,11 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<h1>Editing group: {{ groupname }}</h1>
|
<h1>Editing group: {{ groupname }}</h1>
|
||||||
<p class="text-muted">Add or remove members from <i>{{ groupname }}</i> here. Hit the <kbd>Apply</kbd> button to save your changes.</p>
|
|
||||||
<p class="text-muted">Note that Groups must always have at least one member.</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm border rounded m-2">
|
<div class="col-sm border rounded m-2">
|
||||||
<table class="table table-hover align-middle" id="groupNonMembers">
|
<table class="table table-hover align-middle" id="tableNonMembers">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col" colspan="3" class="text-center text-opacity-75 fs-4">Other users</th>
|
<th scope="col" colspan="3" class="text-center text-opacity-75 fs-4">Other users</th>
|
||||||
@ -31,7 +29,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<button type="button" class="toggleMembershipButton btn btn-success">
|
<button type="button" class="toggleMembershipButton btn btn-success">
|
||||||
Add
|
<i class="bi-box-arrow-in-right"></i> Add to group
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -40,7 +38,7 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm border border-primary rounded m-2">
|
<div class="col-sm border border-primary rounded m-2">
|
||||||
<table class="table table-hover align-middle" id="groupMembers">
|
<table class="table table-hover align-middle" id="tableMembers">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col" colspan="3" class="text-center text-primary text-opacity-75 fs-4">{{ groupname }}</th>
|
<th scope="col" colspan="3" class="text-center text-primary text-opacity-75 fs-4">{{ groupname }}</th>
|
||||||
@ -61,7 +59,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<button type="button" class="toggleMembershipButton btn btn-danger">
|
<button type="button" class="toggleMembershipButton btn btn-danger">
|
||||||
Remove
|
<i class="bi-box-arrow-right"></i> Remove from group
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -70,17 +68,5 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
|
||||||
<div class="d-grid gap-2 col-2 mx-auto">
|
|
||||||
<form method="post" enctype="application/json">
|
|
||||||
{{ form.csrf_token }}
|
|
||||||
<a class="btn btn-secondary"
|
|
||||||
href="{{ url_for('usermanager.group_update', groupname=groupname) }}"
|
|
||||||
role="button">Reset</a>
|
|
||||||
{{ form.updated_members }}
|
|
||||||
{{ form.submit(class="btn btn-primary") }}
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script src="{{ url_for('static', filename='js/group_edit.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/group_edit.js') }}"></script>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
@ -367,11 +367,11 @@ class GroupUpdateForm(FlaskForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/groups/update/<string:groupname>", methods=("GET", "POST"))
|
@bp.route("/groups/update/<string:groupname>")
|
||||||
def group_update(groupname: str):
|
def group_update(groupname: str):
|
||||||
"""Detail and Update view for a group.
|
"""Detail and Update view for a group.
|
||||||
|
|
||||||
Shows a form allowing the modification of user memberships for this group.
|
Shows a table allowing the modification of user memberships for this group.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -387,36 +387,10 @@ def group_update(groupname: str):
|
|||||||
|
|
||||||
members = {user for user in group.members}
|
members = {user for user in group.members}
|
||||||
non_members = {user for user in ldap.get_users(conn)} - members
|
non_members = {user for user in ldap.get_users(conn)} - members
|
||||||
form = GroupUpdateForm()
|
|
||||||
|
|
||||||
if request.method == 'GET':
|
|
||||||
form.updated_members.data = dumps([user.username for user in members])
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
updated_usernames_list = loads(form.updated_members.data)
|
|
||||||
except JSONDecodeError:
|
|
||||||
abort(400)
|
|
||||||
if not isinstance(updated_usernames_list, list):
|
|
||||||
abort(400)
|
|
||||||
updated_members = set()
|
|
||||||
for username in updated_usernames_list:
|
|
||||||
if not isinstance(username, str):
|
|
||||||
abort(400)
|
|
||||||
try:
|
|
||||||
updated_members.add(ldap.get_user(conn, username))
|
|
||||||
except ldap.EntryNotFoundException:
|
|
||||||
abort(400)
|
|
||||||
if not len(updated_members):
|
|
||||||
abort(400)
|
|
||||||
ldap.update_group(conn, Group(group.groupname, updated_members))
|
|
||||||
flash(f"The Group '{group.groupname}' was updated.")
|
|
||||||
# TODO redirect to group list view
|
|
||||||
return redirect(url_for('usermanager.group_update', groupname=group.groupname))
|
|
||||||
|
|
||||||
conn.unbind()
|
conn.unbind()
|
||||||
return render_template(
|
return render_template(
|
||||||
'usermanager/group_edit.html',
|
'usermanager/group_edit.html',
|
||||||
form=form,
|
|
||||||
groupname=group.groupname,
|
groupname=group.groupname,
|
||||||
members=members,
|
members=members,
|
||||||
non_members=non_members,
|
non_members=non_members,
|
||||||
|
198
lumi2/webapi.py
198
lumi2/webapi.py
@ -1,10 +1,11 @@
|
|||||||
from json import JSONEncoder, JSONDecoder, loads, dumps
|
from json import JSONEncoder, JSONDecoder, loads, dumps, JSONDecodeError
|
||||||
|
|
||||||
from flask import Blueprint
|
from flask import Blueprint, request
|
||||||
from flask_restful import Resource
|
from flask_restful import Resource
|
||||||
|
|
||||||
import lumi2.ldap as ldap
|
import lumi2.ldap as ldap
|
||||||
from lumi2.usermodel import User, Group
|
from lumi2.usermodel import User, Group
|
||||||
|
from lumi2.exceptions import InvalidStringFormatException
|
||||||
|
|
||||||
|
|
||||||
class UserEncoder(JSONEncoder):
|
class UserEncoder(JSONEncoder):
|
||||||
@ -57,8 +58,106 @@ class UserResource(Resource):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_group_from_json(input_dict: dict) -> Group:
|
||||||
|
"""Creates a Group object using the input dictionary.
|
||||||
|
|
||||||
|
The input must be a dictionary of the following format:
|
||||||
|
|
||||||
|
{
|
||||||
|
"group": {
|
||||||
|
"groupname": "mygroup",
|
||||||
|
"members": [
|
||||||
|
"alice",
|
||||||
|
"bob",
|
||||||
|
"carlie"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Group
|
||||||
|
The Group object equivalent to the input dict.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
TypeError
|
||||||
|
If the input is not of type dict.
|
||||||
|
json.JSONDecodeError
|
||||||
|
If the input string is not valid JSON.
|
||||||
|
lumi2.ldap.EntryNotFoundException
|
||||||
|
If any of the users listed as members do not exist.
|
||||||
|
ValueError
|
||||||
|
If the input string's format is incorrect, or if the list of Group
|
||||||
|
members is empty.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not isinstance(input_dict, dict):
|
||||||
|
raise ValueError(f"Expected a dictionary but got: '{type(input_dict)}'.")
|
||||||
|
if len(input_dict) != 1 or 'group' not in input_dict.keys():
|
||||||
|
raise ValueError(f"Expected exactly one entry called 'group'.")
|
||||||
|
|
||||||
|
group_dict = input_dict['group']
|
||||||
|
if not isinstance(group_dict, dict):
|
||||||
|
raise ValueError(f"Expected a dictionary but got: '{type(group_dict)}'.")
|
||||||
|
if len(group_dict.keys()) != 2:
|
||||||
|
raise ValueError("Invalid number of keys in Group entry.")
|
||||||
|
for required_key in ['groupname', 'members']:
|
||||||
|
if required_key not in group_dict.keys():
|
||||||
|
raise ValueError(f"Expected a key called '{required_key}' in Group entry.")
|
||||||
|
|
||||||
|
groupname = group_dict['groupname']
|
||||||
|
if not isinstance(groupname, str):
|
||||||
|
raise ValueError("Expected the value for 'groupname' to be a string.")
|
||||||
|
try:
|
||||||
|
Group.assert_is_valid_groupname(groupname)
|
||||||
|
except InvalidStringFormatException as e:
|
||||||
|
raise ValueError(f"Invalid group name: {e}")
|
||||||
|
|
||||||
|
member_usernames = group_dict['members']
|
||||||
|
if not isinstance(member_usernames, list):
|
||||||
|
raise ValueError("Expected the value for 'members' to be a list.")
|
||||||
|
if not len(member_usernames):
|
||||||
|
raise ValueError("Group must have at least one member.")
|
||||||
|
members = set()
|
||||||
|
conn = ldap.get_connection()
|
||||||
|
for username in member_usernames:
|
||||||
|
if not isinstance(username, str):
|
||||||
|
raise ValueError("Member list may contain only strings.")
|
||||||
|
members.add(ldap.get_user(conn, username))
|
||||||
|
|
||||||
|
conn.unbind()
|
||||||
|
return Group(groupname, members)
|
||||||
|
|
||||||
|
|
||||||
class GroupResource(Resource):
|
class GroupResource(Resource):
|
||||||
def get(self, groupname):
|
"""The GroupResource represents a Group object in the REST API.
|
||||||
|
|
||||||
|
In JSON, a Group is represented as follows:
|
||||||
|
{
|
||||||
|
"groupname": "mygroup",
|
||||||
|
"members": [
|
||||||
|
"alice",
|
||||||
|
"bob",
|
||||||
|
"charlie"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get(self, groupname: str):
|
||||||
|
"""Retrieves the group specified by the groupname as a JSON object.
|
||||||
|
|
||||||
|
Attributes
|
||||||
|
----------
|
||||||
|
groupname : str
|
||||||
|
The name of the group to be retrieved.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
json : str , status : int
|
||||||
|
A JSON string and HTTP status code.
|
||||||
|
If the request was handled successfully,
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
conn = ldap.get_connection()
|
conn = ldap.get_connection()
|
||||||
except:
|
except:
|
||||||
@ -74,3 +173,96 @@ class GroupResource(Resource):
|
|||||||
"members": [user.username for user in group.members],
|
"members": [user.username for user in group.members],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def put(self, groupname):
|
||||||
|
try:
|
||||||
|
conn = ldap.get_connection()
|
||||||
|
except:
|
||||||
|
return 500
|
||||||
|
try:
|
||||||
|
# Make sure the requested group exists
|
||||||
|
group = ldap.get_group(conn, groupname)
|
||||||
|
except ldap.EntryNotFoundException:
|
||||||
|
conn.unbind()
|
||||||
|
return {"message": f"Group '{groupname}' does not exist."}, 400
|
||||||
|
try:
|
||||||
|
# Parse the JSON-submitted group
|
||||||
|
group = get_group_from_json(request.get_json())
|
||||||
|
except JSONDecodeError as e:
|
||||||
|
conn.unbind()
|
||||||
|
return {"message": f"Invalid JSON format: {e}"}, 400
|
||||||
|
except ValueError as e:
|
||||||
|
conn.unbind()
|
||||||
|
return {"message": f"Invalid string format: {e}"}, 400
|
||||||
|
except ldap.EntryNotFoundException as e:
|
||||||
|
conn.unbind()
|
||||||
|
return {"message": f"Entry not found: {e}"}
|
||||||
|
if not group.groupname == groupname:
|
||||||
|
conn.unbind()
|
||||||
|
return {"message": "Groupname mismatch between endpoint and submitted data."}
|
||||||
|
|
||||||
|
ldap.update_group(conn, group)
|
||||||
|
conn.unbind()
|
||||||
|
return {
|
||||||
|
"group": {
|
||||||
|
"groupname": group.groupname,
|
||||||
|
"members": [user.username for user in group.members],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def add_user_to_group(self, groupname):
|
||||||
|
"""Accepts a PUT request to add a user to the specified Group.
|
||||||
|
|
||||||
|
The request data must be a JSON string of the following format:
|
||||||
|
|
||||||
|
{
|
||||||
|
"username": "my-user"
|
||||||
|
}
|
||||||
|
|
||||||
|
The user must exist on the server.
|
||||||
|
If the user is already a group member, no action is taken.
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
conn = ldap.get_connection()
|
||||||
|
except:
|
||||||
|
return 500
|
||||||
|
try:
|
||||||
|
group = ldap.get_group(conn, groupname)
|
||||||
|
except ldap.EntryNotFoundException:
|
||||||
|
conn.unbind()
|
||||||
|
return {"message": f"Group '{groupname}' does not exist."}, 404
|
||||||
|
|
||||||
|
container_dict = request.get_json()
|
||||||
|
if not isinstance(container_dict, dict):
|
||||||
|
conn.unbind()
|
||||||
|
return {"message": "Invalid data: expected a JSON object."}, 400
|
||||||
|
if len(container_dict.keys()) != 1:
|
||||||
|
conn.unbind()
|
||||||
|
return {"message": "Invalid data: too many keys (expected 1)."}, 400
|
||||||
|
if not "username" in container_dict.keys():
|
||||||
|
conn.unbind()
|
||||||
|
return {"message": "Invalid data: no 'username' key found."}, 400
|
||||||
|
username = container_dict['username']
|
||||||
|
if not isinstance(username, str):
|
||||||
|
conn.unbind()
|
||||||
|
return {"message": "Invalid data: 'username' must be a string."}, 400
|
||||||
|
try:
|
||||||
|
User.assert_is_valid_username(username)
|
||||||
|
except InvalidStringFormatException as e:
|
||||||
|
conn.unbind()
|
||||||
|
return {"message": f"Invalid username: {e}"}, 400
|
||||||
|
try:
|
||||||
|
user = ldap.get_user(conn, username)
|
||||||
|
except ldap.EntryNotFoundException as e:
|
||||||
|
conn.unbind()
|
||||||
|
return {"message": f"User '{username}' does not exist."}, 400
|
||||||
|
if username in group.members:
|
||||||
|
conn.unbind()
|
||||||
|
return {"username": username}, 200
|
||||||
|
|
||||||
|
group.members.add(user)
|
||||||
|
ldap.update_group(conn, group)
|
||||||
|
conn.unbind()
|
||||||
|
return {"username": username}, 200
|
||||||
|
Loading…
Reference in New Issue
Block a user