def message_unpin(token, message_id): ''' Unpins the message with ID message_id in the channel that it is in Parameters: valid token (str): of authorised user message_id (int): ID of the message to be unpinned Returns: empty dictionary Raises: InputError: if message with ID message_id is not a valid message, or message is currently unpinned AccessError: if token is invalid, or if authorised user is not a slackr owner nor an admin of the channel in which the message is ''' # verify the user if verify_token(token) is False: raise AccessError(description='Invalid token') # get database data = get_store() # getting id of the user u_id = get_tokens()[token] data.unpin(u_id, message_id) return {}
def find_u_id(email): ''' Returns the user id given the email. If the email does not correspond to an existing user in the database => returns None ''' data = get_store() return data.users.email_used(email)
def channel_details(token, channel_id): ''' Given a Channel with ID channel_id that the authorised user is part of, provide basic details about the channel. Args: token (str), channel_id (int) InputError: channel_id is not a valid channel AccessError: authorised user is not part of the channel with channel_id Output: (channel) name, owner_members, all_members ''' data = get_store() # verify the token is valid if verify_token(token) is False: raise AccessError(description="Invalid token") if not data.channels.channel_exists(channel_id): raise InputError(description='Channel does not exist') # check that the authorised user is member of said channel auth_u_id = get_tokens()[token] if not data.user_channel.is_member(auth_u_id, channel_id): raise AccessError( description= "The authorised user is not a member of channel with this channel ID" ) # Create two lists and append details about owner members of the channel and all its members # return the dictionary containing details of the channel return { "name": data.channels.channel_details(channel_id)['name'], "owner_members": data.channel_owners(channel_id), "all_members": data.channel_members(channel_id) }
def user_profile(token, u_id): ''' Returns profile information about the specified user; specifically, u_id, email, name_first, name_last, handle_str, profile_img_url Args: token (str): of the user authorising this action u_id (int): user ID of the user whose profile information is being sought Raises: AccessError: if token is invalid Returns: dictionary: a dictionary containing the 'user' dictionary, which contains u_id, email, name_first, name_last, handle_str, profile_img_url ''' # check validity of user's token if verify_token(token) is False: raise AccessError(description="Invalid token") # check that the u_id of the user whose information the authorised user # wants to access is valid data = get_store() return {'user': data.users.user_details(u_id)}
def message_unreact(token, message_id, react_id): ''' Removes a certain 'react' from a specified message within a channel Parameters: valid token (str): of authorised user message_id (int): ID of the message to be given a react react_id (int): a valid react ID to be removed from the message Raises: InputError: if message with ID message_id is not a valid message or does not exist, or if react_id is not a valid react, or message already has no current react of react_id from the current user AccessError: if token is invalid, or if the user is not part of the channel in which the message exists ''' # verify the user if verify_token(token) is False: raise AccessError(description='Invalid token') # get database data = get_store() # getting id of the user u_id = get_tokens()[token] if not data.messages.message_exists(message_id): raise InputError(description='Invalid message ID') channel_id = data.user_message.message_channel(message_id) if not data.user_channel.link_exists(u_id, channel_id): raise InputError( description='Not valid message ID within channel you have joined') data.user_message.unreact(u_id, message_id, react_id) return {}
def auth_login(email, password): ''' Given a registered users' email and password and generates a valid token for the user to remain authenticated. Args: email (str), password (str) Raise: InputError: email entered not valid, email does not belong to a user, password is incorrect Return: Dictionary: u_id (int), token (str) ''' data = get_store() # Ensure user's password matches otherwise raise an error encrypted_pass = hashlib.sha256(password.encode()).hexdigest() u_id = data.users.validate_login(email, encrypted_pass) # check if the user is not already logged in token = get_token(u_id) if token is not None: raise InputError(description='User is already logged on') token = generate_token(u_id) get_tokens()[token] = u_id return {"u_id": u_id, "token": token}
def auth_passwordreset_reset(reset_code, new_password): ''' Given a valid reset code change the user's password to the new password given Args: reset_code (str), new_password (str) Returns: None ''' if len(new_password) < 6: raise InputError( description="Password too short, must be at least 6 characters") data = get_store() # Check if reset_code exists within dictionary and return the email key # paired with that code data.codes.code_exists(reset_code) email = data.codes.find_email(reset_code) # Retrieve user's id and set their new password u_id = data.users.find_u_id(email[0]) data.users.set_password(u_id, new_password) # Delete reset code from the dictionary data.codes.delete(email[0]) return {}
def user_profile_setname(token, name_first, name_last): ''' Updates the authorised user's first name and last name. Args: token (str): of the user authorising this action name_first (str): user's new first name (1-50 char) name_last (str): user's new last name (1-50 char) Raises: AccessError: if token is invalid InputError: if either name_first or name_last is shorter than 1 char or longer than 50 char ''' # verify token is valid if verify_token(token) is False: raise AccessError(description="Invalid token") # verify that changes to name are allowed if len(name_first) < MIN_NAME_LEN or len(name_last) < MIN_NAME_LEN \ or len(name_first) > MAX_NAME_LEN or len(name_last) > MAX_NAME_LEN: raise InputError( description= "Names must be between 1 and 50 characters long inclusive.") # another verification that names are not just spaces if name_first.isspace() or name_last.isspace(): raise InputError( description="Names cannot exclusively contain whitespaces.") # modify name_first and name_last in the database as per the user's changes u_id = get_tokens()[token] data = get_store() data.users.set_first_name(u_id, name_first) data.users.set_last_name(u_id, name_last)
def has_message_edit_permission(auth_u_id, message_id): ''' A helper function for message_edit which determines whether a user has edit permissions Parameters: user ID (int): of the user invoking message_edit message_id (int): of the message to be edited Returns: True / False (bool): whether the user has permission to edit the message ''' data = get_store() # check if auth user is a slackr owner if data.admins.is_admin(auth_u_id): return True # check if auth user wrote the message if data.user_message.is_sender(message_id, auth_u_id): return True # check if auth user is owner of channel which contains the message # find the message # find the channel it belongs to ch_id = data.user_message.message_channel(message_id) return data.user_channel.is_owner(auth_u_id, ch_id)
def channel_addowner(token, channel_id, u_id): ''' Promotes user with 'u_id' to an owner of channel with 'channel_id' Args: token (str): of the user authorising this action channel_id (int): of the channel to which to promote the user to owner u_id (int): of the user to be promoted Raises: AccessError: if token invalid if token does not belong to a user with permission to promote InputError: if channel_id does not correspond to a valid channel ''' # verify the user if verify_token(token) is False: raise AccessError(description='Invalid token') # get database information data = get_store() # getting id of the user u_id_invoker = get_tokens()[token] # verify the channel exists if not data.channels.channel_exists(channel_id): raise InputError(description="Invalid channel id") # verify the invoker is either an owner of the channel or an admin if not data.admins.is_admin(u_id_invoker) and \ not data.user_channel.is_owner(u_id_invoker, channel_id): raise AccessError( description="You do not have privileges to add owners") data.user_channel.add_owner(u_id, channel_id)
def channel_invite(token, channel_id, u_id): ''' Invites a user (with user id u_id) to join a channel with ID channel_id. Once invited the user is added to the channel immediately. Args: token (str), channel_id (int), u_id (int) InputError: channel ID does not correspond to a valid channel; user ID does not refer to a valid user. AccessError: authorised user is not already a member of channel with channel ID. ''' # verify the validity of the authorised user's token if verify_token(token) is False: raise AccessError(description="Invalid token") # check that channel_id corresponds to a valid channel data = get_store() if not data.channels.channel_exists(channel_id): raise InputError( description="Channel with this channel ID does not exist") # check that the authorised user belongs to this valid channel auth_u_id = get_tokens()[token] if not data.user_channel.is_member(auth_u_id, channel_id): raise AccessError( description="The authorised user is not a member of this channel") # check that u_id corresponds to a valid user if not data.users.user_exists(u_id): raise InputError(description="User ID is not valid") # add the user with u_id into the channel # update the database by adding a link between the user and the channel # checks if user is already apart of the channel data.user_channel.join_channel(u_id, channel_id)
def channel_leave(token, channel_id): ''' Allows a user with 'token' to leave the channel with 'channel_id' Args: token (str), channel_id (int) Raises: AccessError: if token invalid if user with u_id was not a member of the channel in the first place InputError: if channel_id does not correspond to a valid channel Return: an empty dictionary ''' # verify the user if verify_token(token) is False: raise AccessError(description='Invalid token') # get database information data = get_store() # getting id of the user u_id = get_tokens()[token] # verify the channel exists if not data.channels.channel_exists(channel_id): raise InputError(description="Invalid channel id") # verify the user is a member of the channel if not data.user_channel.is_member(u_id, channel_id): raise AccessError(description="Cannot leave channel: not a member") # deleting the user from the channel list data.user_channel.leave_channel(u_id, channel_id) return {}
def test_reseting_password(): workspace_reset() data = get_store() reg_dict = auth_register('*****@*****.**', 'password123', 'Max', 'Smith') auth_logout(reg_dict['token']) data.users.set_password(reg_dict['u_id'], 'wubbalubba') auth_login('*****@*****.**', 'wubbalubba')
def is_handle_unique(handle_str): ''' Determines whether handle_str is unique in the slackr Args: handle_str (str) Return: unique (bool) ''' data = get_store() unique = data.users.handle_unique(handle_str) return unique
def message_sendlater(token, channel_id, message, time_sent): ''' Sends a message into channel with ID channel_id at a specified time in the future Parameters: valid token (str): of authorised user channel_id (int): the channel into which the message is to be sent message (str): the message to be sent by the authorised user into channel time_sent (float): unix timestamp of a time in the future for message to be sent Returns: dictionary containing: message_id (int): ID assigned to the new message Raises: InputError: if message length is greater than 1000 strings or message is empty, or channel_id is invalid, or time_sent is not a correctly formatted future timestamp AccessError: if token is invalid, or the authorised user is not part of the channel with ID channel_id ''' # verify the user if verify_token(token) is False: raise AccessError(description='Invalid token') # checking message string is valid if not isinstance(message, str) or len(message) > MAX_MSG_LEN or not message: raise InputError(description='Invalid message') # get database data = get_store() # getting id of the user u_id = get_tokens()[token] # checking channel_id is valid (user is part of) if not data.user_channel.link_exists(u_id, channel_id): raise AccessError( description='You do not have access to send message in this channel' ) # checking time_sent is valid (it is a time in the future) if time_sent < time(): raise InputError(description='Scheduled send time is invalid') # assigning new ID for the message new_id = data.messages.next_id() # the action to be completed at time time_sent sched_thread = Thread(target=run_scheduler, args=(message_send, time_sent, ( token, channel_id, message, ))) # run the schedular (target=message_send, time_sent=time_sent, ) sched_thread.start() return {'message_id': new_id}
def message_send(token, channel_id, message): ''' Sends a message into channel with ID channel_id Parameters: valid token (str): of authorised user channel_id (int): the channel into which the message is to be sent message (str): the message to be sent by the authorised user into channel Returns: dictionary containing: message_id (int): ID assigned to the new message Raises: InputError: if message length is greater than 1000 strings or message is empty, or channel_id is invalid AccessError: if token is invalid, or the authorised user is not part of the channel with ID channel_id ''' # verify the user if verify_token(token) is False: raise AccessError(description='Invalid token') # checking message string is valid if not isinstance(message, str) or len(message) > MAX_MSG_LEN or not message: raise InputError(description='Invalid message') # get database data = get_store() # getting id of the user u_id = get_tokens()[token] # checking channel_id is valid (user is part of) if not data.user_channel.link_exists(u_id, channel_id): raise AccessError( description= 'You do not have access to send messages in this channel') # send the message details = message, time() new_id = data.add_message(u_id, channel_id, details) # Facilitation of Hangman Game hbot_output = hangman(message, channel_id, u_id) if hbot_output is not None: # obtain the token of the hangman bot for the channel we are currently # in hbot_token = data.channels.get_hbot_details(channel_id)[1] message_send(hbot_token, channel_id, hbot_output) return {'message_id': new_id}
def userpermission_change(token, u_id, permission_id): ''' Sets the user's permission to either owner/admin (1) or normal member (2). Args: token (str): of the user authorising this action u_id (int): of the user whose permission is being changed permission_id (int): 1 for owner, 2 for member Raises: AccessError: if token is invalid if the user invoking this action is not an owner/admin of the slackr if the owner/admin attempts to demote themselves to a normal member InputError: if u_id does not correspond to an existent user if permission_id does not correspond to a valid permission ''' # verify the user if verify_token(token) is False: raise AccessError(description='Invalid token') # get database information data = get_store() # getting id of the user u_id_invoker = get_tokens()[token] # verify that u_id is a valid user if not data.users.user_exists(u_id): raise InputError(description="User does not exist") # verify permission_id is valid (1 or 2) if not data.admins.is_valid_permission(permission_id): raise InputError(description="Invalid permission id") # verify the invoker is an admin if not data.admins.is_admin(u_id_invoker): raise AccessError( description="You do not have permission to change permissions") # the admin cannot demote himself if u_id_invoker == u_id and permission_id == SLACKR_MEMBER: raise InputError(description='Cannot demote current user') # set new permissions if permission_id == SLACKR_OWNER: data.admins.add(u_id) else: data.admins.remove(u_id)
def standup_start(token, channel_id, length): ''' Input: channel_id: int, length: int Returns: a dictionary containing the finish time of the standup Raises: InputError, AccessError Start a standup in a given channel ''' # verify the user if verify_token(token) is False: raise AccessError(description='Invalid token') # get database information data = get_store() # getting id of the user u_id = get_tokens()[token] # verify the channel exists if not data.channels.channel_exists(channel_id): raise InputError(description="Invalid channel id") # getting all the standups standups_info = get_standup() # verify there are currently no standups in the channel for standup in standups_info: if standup['channel_id'] == channel_id: raise InputError(description="Active standup already in channel") # verifying length is a float or an integer if not isinstance(length, int) and not isinstance(length, float): raise InputError(description="Invalid length type") # creating a new standup time_finish = time.time() + length with STANDUP_LOCK: standups_info.append({ 'channel_id': channel_id, 'u_id': u_id, 'time_start': time.time(), 'time_finish': time_finish, 'messages': [], }) # schedule flushing the standup running_time = time.time() + length sched_thread = Thread(target=run_scheduler, args=(flush_standup, running_time, (channel_id, ))) #run_scheduler(target=flush_standup, running_time=time.time() + length, args=(channel_id,)) sched_thread.start() return {'time_finish': time_finish}
def workspace_reset(): ''' Resets the workspace state. Assumes that the base state of database.p has a Database() instance. ''' # clear the tokens dictionary get_tokens().clear() # clear database.p data = get_store() data.reset() # clear standups with get_lock(): standups = get_standup() standups.clear() with open("database.p", "wb") as database_file: pickle.dump(data, database_file)
def auth_passwordreset_request(email): ''' Sends a reset code to a user's email and adds the reset code to a dictionary Args: email (str) ''' data = get_store() if not data.users.email_used(email): return {} # Generates reset code for user and sends it to their email # add reset code to dictionary for user data.codes.push(email) return {}
def users_all(token): ''' Lists all users on the slackr Args: token (str) Raises: AccessError if token is invalid Returns: a dictionary containing a list of all users and their associated details - u_id, email, name_first, name_last, handle_str ''' # verify the token is valid if verify_token(token) is False: raise AccessError(description="invalid token") # return a dictionary which contains one key, "users", which is itself a list of dictionaries # containing types u_id, email, name_first, name_last, handle_str data = get_store() return {"users": data.users.all()}
def channels_listall(token): ''' Provides users with details of all channels existing in Slackr Parameter: token Returns: list of ALL channels in Slackr ''' # verify the user if verify_token(token) is False: raise AccessError(description='Invalid token') # get database data = get_store() # return all existing channels all_channels = data.channels.all() return { 'channels': all_channels }
def channel_removeowner(token, channel_id, u_id): ''' Demote user with 'u_id' from an owner to normal member of channel with 'channel_id' Args: token (str): of the user authorising this action channel_id (int): of the channel to which to demote the user to normal member u_id (int): of the user to be demoted Raises: AccessError: if token invalid if token does not belong to a user with permission to demote if attempting to demote an admin of the slackr InputError: if channel_id does not correspond to a valid channel ''' # verify the user if verify_token(token) is False: raise AccessError(description='Invalid token') # get database information data = get_store() # getting id of the user u_id_invoker = get_tokens()[token] # verify the channel exists if not data.channels.channel_exists(channel_id): raise InputError(description="Invalid channel id") # verify the user to remove is not a Slackr owner # and the remover has valid privileges if not data.admins.is_admin(u_id_invoker) and data.admins.is_admin(u_id): raise AccessError( description="You do not have permission to remove the current user" ) # verify the invoker is either an owner of the channel or slackr if not data.user_channel.is_owner(u_id_invoker, channel_id): raise AccessError( description="You do not have premission to remove the current user" ) # remove the ownership data.user_channel.remove_owner(u_id, channel_id)
def user_profile_sethandle(token, handle_str): ''' Updates the authorised user's handle. Args: token (str): of the user authorising this action handle_str (str): user's new handle Raises: AccessError: if token not valid InputError: if handle_str not between 2 and 20 characters inclusive if handle_str not unique to user ''' # verify the token is valid if verify_token(token) is False: raise AccessError(description="Invalid token") # verify the new handle_str is of the correct length if len(handle_str) < MIN_HANDLE_LEN: raise InputError( description= "handle_str too short - it must be between 2 and 20 characters inclusive" ) if len(handle_str) > MAX_HANDLE_LEN: raise InputError( description= "handle_str too long - - it must be between 2 and 20 characters inclusive" ) u_id = get_tokens()[token] data = get_store() # verify the new handle_str is unique # allow the "change" if the authorised user's new handle_str is identical # to their old one. if data.users.get_handle( u_id) != handle_str and not data.users.handle_unique(handle_str): raise InputError(description="new handle_str not unique to this user") # change the handle_str in the database data.users.set_handle(u_id, handle_str)
def test_reseting_multiple_passwords(): workspace_reset() data = get_store() reg_dict = auth_register('*****@*****.**', 'password123', 'Max', 'Smith') reg_dict2 = auth_register('*****@*****.**', 'password123', 'Max', 'Smith') reg_dict3 = auth_register('*****@*****.**', 'password123', 'Bob', 'Smith') data.users.set_password(reg_dict['u_id'], 'wubbalubba') data.users.set_password(reg_dict2['u_id'], 'poorpassword') data.users.set_password(reg_dict3['u_id'], 'great_password') auth_logout(reg_dict['token']) auth_logout(reg_dict2['token']) auth_logout(reg_dict3['token']) auth_login('*****@*****.**', 'wubbalubba') auth_login('*****@*****.**', 'poorpassword') auth_login('*****@*****.**', 'great_password')
def user_remove(token, u_id): ''' Removes a user from slackr Arguments: token and u_id of user to be removed Returns: empty dictionary, but the user is entirely removed. Exceptions: InputError: u_id is not a valid user AccessError: remover is not an admin Assumptions: removing the user means removing all traces of him including his messages ''' # verify the user if verify_token(token) is False: raise AccessError(description='Invalid token') data = get_store() # getting id of the user u_id_invoker = get_tokens()[token] # verify the invoker is an admin if not data.admins.is_admin(u_id_invoker): raise AccessError( description="You do not have permission to change permissions") if not data.users.user_exists(u_id): raise InputError(description="Invalid user id") # verify the admin is not removing himself if u_id == u_id_invoker: raise InputError(description='Cannot remove current user') # cannot remove the hangman bot if user_profile(token, u_id)['user']['name_first'] == 'Hangman': raise InputError(description='Cannot remove Hangman B0T') # removing his user details data.users.remove(u_id) # removing all his subscriptions to channels data.user_channel.remove_link_by_user(u_id) # removing all the messages sent by that user data.remove_messages(u_id) # remove the user the token store if he is logged on token = get_token(u_id) if token is not None: get_tokens().pop(token)
def channel_join(token, channel_id): ''' Allows a user with a valid 'token' to join a public channel with 'channel_id' Args: token (str), channel_id (int) Raises: AccessError: if token invalid if the channel is private InputError: if channel_id does not correspond to an existing channel if user with u_id is already a member of the channel Return: an empty dictionary ''' # verify the user if verify_token(token) is False: raise AccessError(description='Invalid token') # get database information data = get_store() # getting id of the user u_id = get_tokens()[token] # verify the channel exists if not data.channels.channel_exists(channel_id): raise InputError(description="Invalid channel id") # verify user is not already a member if data.user_channel.is_member(u_id, channel_id): raise InputError(description="Already a member") # verify the channel is public unless user is a slackr owner if not data.admins.is_admin(u_id) and data.channels.is_private(channel_id): raise AccessError( description="Cannot join channel: channel is private") # ... joining channel: if admin if data.admins.is_admin(u_id): data.user_channel.add_owner(u_id, channel_id) else: data.user_channel.join_channel(u_id, channel_id)
def profile_uploadphoto(token, url, box): ''' Crops a given image from a url and stores it in the database Arguments: token, image url, coordinates to be cropped Returns: nothing, but with url for the cropped image stored in the users details ''' # # verify that the token is valid # if verify_token(token) is False: # raise AccessError(description="Invalid token") # preparing the coordinates x_start, y_start, x_end, y_end = box # getting the image response = requests.get(url) # checking the image url given is valid if response.status_code != OK_STATUS: raise InputError(description='Cannot open image') # getting the image image = Image.open(io.BytesIO(response.content)) # making sure the image type is JPG if image.format != 'JPEG': raise InputError('Invalid image format') # checking the supplied coordinates are valid img_width, img_height = image.size if x_start < 0 or y_start < 0 or \ x_end > img_width or y_end > img_height: raise InputError('Invalid crop coordinates') # crop the image cropped_img = image.crop(box) # get the database data = get_store() # saving the image into the database u_id = get_tokens()[token] # pylint: disable=global-statement cropped_img.save(f"{image_config()['path']}/{u_id}.jpg") data.users.set_image(u_id)
def channels_list(token): ''' Provides users with details of all channels the requesting user is part of Parameter: authorised token Returns: list of channels (and associated details) that the authorised user is part of ''' # verify the user if verify_token(token) is False: raise AccessError(description='Invalid token') # get database data = get_store() # getting id of the user # get_tokens() should return a dictionary where token key corresponds to # that user's id u_id = get_tokens()[token] # return details about all channels the user is part of return { 'channels': data.user_channels(u_id) }
def channel_messages(token, channel_id, start): ''' Lists up to 50 messages within 'channel_id', beginning from the message indexed 'start'. Args: token (str): of the user authorising this action channel_id (int): of the channel whose messages require displaying start (int): index of the first message to display Raises: AccessError: if token invalid if authorised user does not hav permission to view the channel's messages InputError: if channel_id does not correspond to a valid channel Return: List of 50 messages from channel with channel_id starting from index 'start', if we reached the end of the list we set the 'end' index to -1 ''' # verify the user if verify_token(token) is False: raise AccessError(description='Invalid token') # get database information data = get_store() # getting id of the user u_id = get_tokens()[token] # verify the channel exists if not data.channels.channel_exists(channel_id): raise InputError(description='Channel does not exist') # verify the user is a member of the channel if not data.user_channel.is_member(u_id, channel_id): raise AccessError( description= "You do not have permission to view this channel's messages") # getting the messages of the channel details = channel_id, start messages, more = data.channel_messages(u_id, details) return { "messages": messages, "start": start, "end": -1 if not more else start + MESSAGE_BLOCK }