314 lines
9.7 KiB
Python
314 lines
9.7 KiB
Python
from json import JSONEncoder, JSONDecoder, loads, dumps, JSONDecodeError
|
|
|
|
from flask import request, g
|
|
from flask_restful import Resource, abort
|
|
|
|
import lumi2.ldap as ldap
|
|
from lumi2.usermodel import User, Group
|
|
from lumi2.exceptions import InvalidStringFormatException
|
|
|
|
|
|
def _assert_is_authenticated():
|
|
if not g.is_authenticated:
|
|
abort(401, message="You are not logged in.")
|
|
|
|
|
|
class UserEncoder(JSONEncoder):
|
|
def default(self, obj):
|
|
if isinstance(obj, User):
|
|
return {
|
|
"username": obj.username,
|
|
"password_hash": obj.password_hash,
|
|
"email": obj.email,
|
|
"first_name": obj.first_name,
|
|
"last_name": obj.last_name,
|
|
"display_name": obj.display_name,
|
|
"picture": obj.get_picture_url(),
|
|
}
|
|
|
|
return JSONEncoder.default(self, obj)
|
|
|
|
|
|
class GroupEncoder(JSONEncoder):
|
|
def default(self, obj):
|
|
if isinstance(obj, Group):
|
|
return {
|
|
"groupname": obj.groupname,
|
|
"members": [user.username for user in obj.members],
|
|
}
|
|
return JSONEncoder.default(self, obj)
|
|
|
|
|
|
class UserResource(Resource):
|
|
"""The UserResource is used for API access to users."""
|
|
|
|
def get(self, username):
|
|
"""Returns the specified user in JSON format."""
|
|
|
|
_assert_is_authenticated()
|
|
|
|
try:
|
|
conn = ldap.get_connection()
|
|
except:
|
|
return 500
|
|
try:
|
|
user = ldap.get_user(conn, username)
|
|
except ldap.EntryNotFoundException:
|
|
return {"message": f"User '{username}' does not exist."}, 400
|
|
|
|
return {
|
|
"username": user.username,
|
|
"password_hash": user.password_hash,
|
|
"email": user.email,
|
|
"first_name": user.first_name,
|
|
"last_name": user.last_name,
|
|
"display_name": user.display_name,
|
|
"picture": user.get_picture_url(),
|
|
}, 200
|
|
|
|
|
|
class GroupResource(Resource):
|
|
"""The GroupResource represents a Group object in the REST API."""
|
|
|
|
def get(self, groupname: str):
|
|
"""Retrieves the group specified by the groupname as a JSON object.
|
|
|
|
Parameters
|
|
----------
|
|
groupname : str
|
|
The name of the group to be retrieved.
|
|
|
|
Returns
|
|
-------
|
|
json : str , status : int
|
|
A JSON string and HTTP status code.
|
|
"""
|
|
|
|
_assert_is_authenticated()
|
|
|
|
try:
|
|
conn = ldap.get_connection()
|
|
except:
|
|
return 500
|
|
try:
|
|
group = ldap.get_group(conn, groupname)
|
|
except ldap.EntryNotFoundException:
|
|
return {"message": f"Group '{groupname}' does not exist."}, 404
|
|
|
|
return {
|
|
"groupname": group.groupname,
|
|
"members": [user.username for user in group.members],
|
|
}, 200
|
|
|
|
|
|
def post(self, groupname: str):
|
|
"""Creates the specified Group with the members listed in the JSON data.
|
|
|
|
The request is expected to contain JSON data in the following format:
|
|
{
|
|
"members": [
|
|
"alice",
|
|
"bob",
|
|
"charlie"
|
|
]
|
|
}
|
|
|
|
Returns
|
|
-------
|
|
json : str , status : int
|
|
A JSON string and HTTP status code.
|
|
"""
|
|
|
|
_assert_is_authenticated()
|
|
|
|
group_dict = request.get_json()
|
|
if not isinstance(group_dict, dict):
|
|
return {"message": f"Invalid format: expected an object but got: '{type(group_dict)}'."}, 400
|
|
if len(group_dict.keys()) != 1:
|
|
return {"message": "Invalid number of keys in Group object: expected exactly one key."}, 400
|
|
if "members" not in group_dict.keys():
|
|
return {"message": "Expected a key called 'members' in the object."}, 400
|
|
|
|
try:
|
|
Group.assert_is_valid_groupname(groupname)
|
|
except InvalidStringFormatException as e:
|
|
return {"message": f"Invalid group name: {e}"}, 400
|
|
|
|
member_usernames = group_dict['members']
|
|
if not isinstance(member_usernames, list):
|
|
return {"message": "Expected the value for 'members' to be a list."}, 400
|
|
if not len(member_usernames):
|
|
return {"message": "Group must have at least one member."}, 400
|
|
members = set()
|
|
|
|
try:
|
|
conn = ldap.get_connection()
|
|
except:
|
|
return 500
|
|
|
|
for username in member_usernames:
|
|
if not isinstance(username, str):
|
|
conn.unbind()
|
|
return {"message": "Member list may contain only strings."}, 400
|
|
try:
|
|
members.add(ldap.get_user(conn, username))
|
|
except ldap.EntryNotFoundException:
|
|
conn.unbind()
|
|
return {"message": f"No such user: '{username}'."}, 400
|
|
|
|
group = Group(groupname, members)
|
|
|
|
try:
|
|
# Make sure the requested group does not exist yet
|
|
group = ldap.get_group(conn, group.groupname)
|
|
conn.unbind()
|
|
return {"message": f"Group '{group.groupname}' already exists."}, 400
|
|
except ldap.EntryNotFoundException:
|
|
pass
|
|
|
|
ldap.create_group(conn, group)
|
|
conn.unbind()
|
|
return {
|
|
"groupname": group.groupname,
|
|
"members": [user.username for user in group.members],
|
|
}, 200
|
|
|
|
|
|
def delete(self, groupname):
|
|
"""Deletes the specified Group.
|
|
|
|
Returns
|
|
-------
|
|
json : str , status : int
|
|
A JSON string and HTTP status code.
|
|
"""
|
|
|
|
_assert_is_authenticated()
|
|
|
|
try:
|
|
conn = ldap.get_connection()
|
|
except:
|
|
return 500
|
|
|
|
try:
|
|
# Make sure the requested exists
|
|
group = ldap.get_group(conn, groupname)
|
|
except ldap.EntryNotFoundException:
|
|
conn.unbind()
|
|
return {"message": f"Group '{groupname}' does not exist."}, 404
|
|
|
|
ldap.delete_group(conn, groupname)
|
|
conn.unbind()
|
|
return None, 200
|
|
|
|
|
|
class GroupMemberResource(Resource):
|
|
"""This resource represents the member of a Group."""
|
|
|
|
def post(self, groupname: str, username: str):
|
|
"""Adds the specified user to the specified Group.
|
|
|
|
Parameters
|
|
----------
|
|
username : str
|
|
The username of the User who will be added to the specified Group.
|
|
groupname : str
|
|
The name of the Group to which the specified User will be added.
|
|
|
|
Returns
|
|
-------
|
|
json : str , status : int
|
|
A JSON string and HTTP status code.
|
|
If the request was handled successfully, the POST-data is
|
|
replied to the client and HTTP code 200 is returned.
|
|
If a failure occurred while processing the request, an appropriate
|
|
error message and HTTP error code are returned.
|
|
"""
|
|
|
|
_assert_is_authenticated()
|
|
|
|
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
|
|
|
|
try:
|
|
user = ldap.get_user(conn, username)
|
|
except ldap.EntryNotFoundException:
|
|
conn.unbind()
|
|
return {"message": f"User '{username}' does not exist."}, 404
|
|
|
|
if user in group.members:
|
|
conn.unbind()
|
|
return {"message": f"User '{username}' is already a member of the Group '{group.groupname}'."}, 400
|
|
|
|
group.members.add(user)
|
|
ldap.update_group(conn, group)
|
|
conn.unbind()
|
|
return {
|
|
"groupname": group.groupname,
|
|
"members": [user.username for user in group.members],
|
|
}, 200
|
|
|
|
|
|
def delete(self, groupname: str, username: str):
|
|
"""Removes the specified User from the specified Group.
|
|
|
|
Parameters
|
|
----------
|
|
username : str
|
|
The username of the User who will be removed from the specified Group.
|
|
groupname : str
|
|
The name of the Group from which the specified User will be removed.
|
|
|
|
Returns
|
|
-------
|
|
json : str , status : int
|
|
A JSON string and HTTP status code.
|
|
If the request was handled successfully, the POST-data is
|
|
replied to the client and HTTP code 200 is returned.
|
|
If a failure occurred while processing the request, an appropriate
|
|
error message and HTTP error code are returned.
|
|
"""
|
|
|
|
_assert_is_authenticated()
|
|
|
|
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
|
|
|
|
try:
|
|
user = ldap.get_user(conn, username)
|
|
except ldap.EntryNotFoundException:
|
|
conn.unbind()
|
|
return {"message": f"User '{username}' does not exist."}, 404
|
|
|
|
if user not in group.members:
|
|
conn.unbind()
|
|
return {"message": f"User '{username}' is not a member of the Group '{group.groupname}'."}, 400
|
|
|
|
if len(group.members) == 1:
|
|
conn.unbind()
|
|
return {
|
|
"message": f"Cannot remove user '{username}', because they are currently the only member " \
|
|
f"of '{group.groupname}'. Empty groups are not permitted in LDAP, so either " \
|
|
f"delete the group or add another user before removing '{username}'."
|
|
}, 400
|
|
|
|
group.members.remove(user)
|
|
ldap.update_group(conn, group)
|
|
conn.unbind()
|
|
return None, 200
|