feat(webapi): implement Group resource endpoints
This commit is contained in:
parent
00c2715a83
commit
2d48a26c51
@ -51,6 +51,7 @@ def create_app(test_config=None):
|
|||||||
from . import webapi
|
from . import webapi
|
||||||
api.add_resource(webapi.UserResource, '/api/user/<string:username>')
|
api.add_resource(webapi.UserResource, '/api/user/<string:username>')
|
||||||
api.add_resource(webapi.GroupResource, '/api/group/<string:groupname>')
|
api.add_resource(webapi.GroupResource, '/api/group/<string:groupname>')
|
||||||
|
api.add_resource(webapi.GroupMemberResource, '/api/group/<string:groupname>/member/<string:username>')
|
||||||
api.init_app(app)
|
api.init_app(app)
|
||||||
|
|
||||||
# TODO create OUs
|
# TODO create OUs
|
||||||
|
357
lumi2/webapi.py
357
lumi2/webapi.py
@ -35,7 +35,11 @@ class GroupEncoder(JSONEncoder):
|
|||||||
|
|
||||||
|
|
||||||
class UserResource(Resource):
|
class UserResource(Resource):
|
||||||
|
"""The UserResource is used for API access to users."""
|
||||||
|
|
||||||
def get(self, username):
|
def get(self, username):
|
||||||
|
"""Returns the specified in JSON format."""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
conn = ldap.get_connection()
|
conn = ldap.get_connection()
|
||||||
except:
|
except:
|
||||||
@ -46,103 +50,18 @@ class UserResource(Resource):
|
|||||||
return {"message": f"User '{username}' does not exist."}, 400
|
return {"message": f"User '{username}' does not exist."}, 400
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"user": {
|
"username": user.username,
|
||||||
"username": user.username,
|
"password_hash": user.password_hash,
|
||||||
"password_hash": user.password_hash,
|
"email": user.email,
|
||||||
"email": user.email,
|
"first_name": user.first_name,
|
||||||
"first_name": user.first_name,
|
"last_name": user.last_name,
|
||||||
"last_name": user.last_name,
|
"display_name": user.display_name,
|
||||||
"display_name": user.display_name,
|
"picture": user.get_picture_url(),
|
||||||
"picture": user.get_picture_url(),
|
}, 200
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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):
|
||||||
"""The GroupResource represents a Group object in the REST API.
|
"""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):
|
def get(self, groupname: str):
|
||||||
"""Retrieves the group specified by the groupname as a JSON object.
|
"""Retrieves the group specified by the groupname as a JSON object.
|
||||||
@ -156,8 +75,8 @@ class GroupResource(Resource):
|
|||||||
-------
|
-------
|
||||||
json : str , status : int
|
json : str , status : int
|
||||||
A JSON string and HTTP status code.
|
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:
|
||||||
@ -165,131 +84,123 @@ class GroupResource(Resource):
|
|||||||
try:
|
try:
|
||||||
group = ldap.get_group(conn, groupname)
|
group = ldap.get_group(conn, groupname)
|
||||||
except ldap.EntryNotFoundException:
|
except ldap.EntryNotFoundException:
|
||||||
return {"message": f"Group '{groupname}' does not exist."}, 400
|
return {"message": f"Group '{groupname}' does not exist."}, 404
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"group": {
|
"groupname": group.groupname,
|
||||||
"groupname": group.groupname,
|
"members": [user.username for user in group.members],
|
||||||
"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"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
def put(self, groupname):
|
Returns
|
||||||
|
-------
|
||||||
|
json : str , status : int
|
||||||
|
A JSON string and HTTP status code.
|
||||||
|
"""
|
||||||
|
|
||||||
|
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:
|
try:
|
||||||
conn = ldap.get_connection()
|
conn = ldap.get_connection()
|
||||||
except:
|
except:
|
||||||
return 500
|
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)
|
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()
|
conn.unbind()
|
||||||
return {
|
return {
|
||||||
"group": {
|
"groupname": group.groupname,
|
||||||
"groupname": group.groupname,
|
"members": [user.username for user in group.members],
|
||||||
"members": [user.username for user in group.members],
|
}, 200
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def add_user_to_group(self, groupname):
|
def delete(self, groupname):
|
||||||
"""Accepts a PUT request to add a user to the specified Group.
|
"""Deletes the specified Group.
|
||||||
|
|
||||||
The request data must be a JSON string of the following format:
|
Returns
|
||||||
|
-------
|
||||||
{
|
json : str , status : int
|
||||||
"username": "my-user"
|
A JSON string and HTTP status code.
|
||||||
}
|
|
||||||
|
|
||||||
The user must exist on the server.
|
|
||||||
If the user is already a group member, no action is taken.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
conn = ldap.get_connection()
|
conn = ldap.get_connection()
|
||||||
except:
|
except:
|
||||||
return 500
|
return 500
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# Make sure the requested exists
|
||||||
group = ldap.get_group(conn, groupname)
|
group = ldap.get_group(conn, groupname)
|
||||||
except ldap.EntryNotFoundException:
|
except ldap.EntryNotFoundException:
|
||||||
conn.unbind()
|
conn.unbind()
|
||||||
return {"message": f"Group '{groupname}' does not exist."}, 404
|
return {"message": f"Group '{groupname}' does not exist."}, 404
|
||||||
|
|
||||||
container_dict = request.get_json()
|
ldap.delete_group(conn, groupname)
|
||||||
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()
|
conn.unbind()
|
||||||
return {"username": username}, 200
|
return None, 200
|
||||||
|
|
||||||
|
|
||||||
class GroupMemberResource(Resource):
|
class GroupMemberResource(Resource):
|
||||||
"""This resource represents the member of a group.
|
"""This resource represents the member of a Group."""
|
||||||
|
|
||||||
In JSON, a GroupMember is represented as follows:
|
def post(self, groupname: str, username: str):
|
||||||
{
|
"""Adds the specified user to the specified Group.
|
||||||
"username": "myuser"
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
|
|
||||||
def post(self, groupname):
|
|
||||||
"""Adds the user specified in the POST data to the specified Group."""
|
|
||||||
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def delete(self, groupname):
|
|
||||||
"""Removes the user specified in the POST data from the specified Group.
|
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
|
username : str
|
||||||
|
The username of the User who will be added to the specified Group.
|
||||||
groupname : str
|
groupname : str
|
||||||
The name of the Group from which a member will be deleted.
|
The name of the Group to which the specified User will be added.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@ -301,4 +212,78 @@ class GroupMemberResource(Resource):
|
|||||||
error message and HTTP error code are returned.
|
error message and HTTP error code are returned.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
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.
|
||||||
|
"""
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
group.members.remove(user)
|
||||||
|
ldap.update_group(conn, group)
|
||||||
|
conn.unbind()
|
||||||
|
return None, 200
|
||||||
|
Loading…
Reference in New Issue
Block a user