def admin_update_user_superuser(user): try: data = forms.check_confirm_key(request.form.get('confirm_token'), 'admin_update_user') except BadSignature: logger.error( log_action_error('update user', 'invalid confirmation key', ('user', _get_qfuser_log(user)))) logger.debug('request.form=' + repr_user_input(request.form)) raise BadRequest('Invalid confirmation key.') valid_request = user.qfuserid == data['target_user'] and \ current_user.qfuserid == data['current_user'] and \ 'superuser' == data['update'] if not valid_request: logger.error( log_action_error('update user', 'invalid confirmation data', ('user', _get_qfuser_log(user)))) logger.debug('confirmation data=' + repr_user_input(data)) logger.debug('request.form=' + repr_user_input(request.form)) raise BadRequest('Invalid confirmation key.') user.superuser = bool(data.get('value', '0') == '1') flash( 'Set user {} as {}'.format( user.username, 'superuser' if user.superuser else 'normal user'), 'notice') db.session.commit() logger.info( log_action('update user', ('user', _get_qfuser_log(user)), ('set', 'superuser'), ('to', repr(user.superuser)))) return safe_redirect(get_next_url('POST'))
def admin_update_user_status(user: QfUser): status_arg = request.form.get('status', '0') status_val = bool(status_arg == '1') user.active = status_val db.session.commit() flash(('Enabled' if status_val else 'Disabled') + ' user {}'.format(user.username), 'notice') logger.info( log_action('update user', ('user', _get_qfuser_log(user)), ('set', 'status'), ('to', repr(user.active)))) return safe_redirect(get_next_url('POST'))
def admin_update_user_email(user): if 'email' in request.form: user.email = request.form.get('email') if request.form.get('confirm_email', '0') == '1': user.confirmed_at = datetime.now() else: user.confirmed_at = None db.session.commit() flash( 'User {user} updated with email {email} ({confirm})'.format( user=user.username, email=user.email, confirm='must confirm' if user.confirmed_at is None else 'confirmed'), 'notice') logger.info( log_action('update user', ('user', _get_qfuser_log(user)), ('set', 'email'), ('email', user.email), ('confirmed', 'True' if user.confirmed_at else 'False'))) return safe_redirect(get_next_url('POST'))
def admin_update_user_superuser_confirm(user: QfUser): from quasselflask import forms update_key = 'superuser' update_value = '1' if request.form.get('superuser', '0') == '1' else '0' confirm_key = forms.generate_confirm_key( { 'target_user': user.qfuserid, 'current_user': current_user.qfuserid, 'update': update_key, 'value': update_value }, 'admin_update_user') return render_template('admin/update_user_confirm.html', target=url_for('admin_update_user', userid=user.qfuserid), user=user, action='set superuser' if update_value == '1' else 'set as normal user', next_url=get_next_url('POST'), confirm_key=confirm_key)
def user_update(): """ Update current user's information. Any fields not included will remain unchanged. Some actions return a confirmation page. It is necessary to submit the confirmation form to complete the action. If acting on the current user, only the email address may be updated. This prevents the current user from locking themselves out of the application or removing a sole superuser. POST command parameter (exactly one required): * `email`: new email address. This will require the user to reconfirm their email address before their account is re-enabled. * `themeid`: new theme ID (int). Other POST parameters, common to all requests: * `next`: URL, the URL to return to after the update. :return: """ commands = frozenset(('email', 'themeid')) request_commands = set(request.form.keys()) & commands # validate request_valid = len(request_commands) == 1 if not request_valid: raise BadRequest( "No command or multiple commands to user update API endpoint") # Valid - let's process it user = current_user # laziness and because this mirrors the admin_update_user endpoint if 'email' in request_commands: try: user.email = request.form.get('email') if userman.enable_confirm_email: user.confirmed_at = None # confirmation email send_confirm_email_email(user) db.session.commit() flash( 'Updated email to {email} (must re-confirm to reactivate account)' .format(email=user.email), 'notice') logger.info( log_action('update user', ('set', 'email'), ('email', user.email))) return safe_redirect(get_next_url('POST')) except Exception as e: logger.error(log_action_error('error while updating user profile', repr(e.args), ('id', user.qfuserid), ('name', user.username), ('email', user.email), ('superuser', repr(user.superuser))), exc_info=True) db.session.rollback() if app.debug: raise # let us debug this else: flash( 'Error occurred while updating user. Please try again later, or contact the server administrator ' 'with the date/time of this error and your IP address in order to trace error information in the' 'logs.') return safe_redirect(get_next_url('POST')) elif 'themeid' in request_commands: try: newid = int(request.form.get('themeid')) except ValueError: newid = None logger.debug(repr(app.config['QF_THEMES'])) logger.debug(repr(newid)) if newid in app.config['QF_THEMES']: user.themeid = newid db.session.commit() return safe_redirect(get_next_url('POST')) else: raise BadRequest("Invalid theme ID.") flash("Something happened. You shouldn't have gotten this far.", "error") logger.error( log_action_error('user update', 'should have returned earlier in code - bug?')) logger.debug('request.form=' + repr_user_input(request.form)) return safe_redirect(get_next_url('POST'))
def admin_user_reset_password(userid): """ Reset the user's password and send them an email inviting them to change their password. This action return a confirmation page. It is necessary to submit the confirmation form to complete the action. POST command parameters: * `confirm_token`: Confirmation token. Optional (returns the confirmation page if not present). * `next`: URL, the URL to return to after the update. :param userid: The user ID to modify. :return: """ from quasselflask import forms if userid == current_user.get_id(): flash( "Can't force reset your own password: go change it from the user profile like a normal person, " "you dummy!", 'error') return safe_redirect(get_next_url('POST')) user = query_qfuser(db.session, userid) # check if confirmed is_confirmed = False data = {} if 'confirm_token' in request.form: try: data = forms.check_confirm_key(request.form.get('confirm_token'), 'admin_user_reset_password') is_confirmed = True except BadSignature: logger.error( log_action_error('update user', 'invalid confirmation key', ('user', _get_qfuser_log(user)))) logger.debug('request.form=' + repr_user_input(request.form)) is_confirmed = False if not is_confirmed: confirm_key = forms.generate_confirm_key( { 'current_user': current_user.qfuserid, 'target_user': user.qfuserid }, 'admin_user_reset_password') return render_template('admin/update_user_confirm.html', target=url_for('admin_user_reset_password', userid=userid), user=user, action='reset password', next_url=get_next_url('POST'), confirm_key=confirm_key) else: valid_request = data.get('target_user', -1) == user.qfuserid and \ data.get('current_user', -1) == current_user.qfuserid if not valid_request: logger.error( log_action_error('reset user password', 'invalid confirmation key', ('user', _get_qfuser_log(user)))) logger.debug('confirmation data=' + repr_user_input(data)) logger.debug('request.form=' + repr_user_input(request.form)) raise BadRequest('Invalid confirmation key.') try: user.password = userman.hash_password(make_reset_password()) send_new_user_set_password_email(user) db.session.commit() flash( 'User {user} login disabled; this user cannot login until they reset their password. A password ' 'reset email has been sent to {email}.'.format( user=user.username, email=user.email), 'success') except Exception as e: logger.error(log_action_error('error while creating user', repr(e.args), ('id', user.qfuserid), ('name', user.username), ('email', user.email), ('superuser', repr(user.superuser))), exc_info=True) db.session.rollback() if app.debug: raise # let us debug this else: flash( 'Error occurred resetting password. Please try again later, or contact the server administrator ' 'with the date/time of this error and your IP address in order to trace error information in the' 'logs.', 'error') return redirect( url_for('admin_manage_user', userid=user.qfuserid)) logger.info( log_action('reset user password', ('user', _get_qfuser_log(user)))) return safe_redirect(get_next_url('POST'))
def admin_delete_user(userid): """ Delete a user. The current user cannot be deleted. This action return a confirmation page. It is necessary to submit the confirmation form to complete the action. POST command parameters: * `confirm_token`: Confirmation token. Only applies to superuser/delete requests. Optional (returns the confirmation page if not present). * `next`: URL, the URL to return to after the update. :param userid: The user ID to modify. :return: """ from quasselflask import forms # validate if userid == current_user.qfuserid: flash("Oops! You can't delete yourself.", "error") return safe_redirect(get_next_url('POST')) # Valid - let's process it user = query_qfuser(db.session, userid) # check if confirmed is_confirmed = False data = {} if 'confirm_token' in request.form: try: data = forms.check_confirm_key(request.form.get('confirm_token'), 'admin_user_delete') is_confirmed = True except BadSignature: logger.error( log_action_error('update user', 'invalid confirmation key', ('user', _get_qfuser_log(user)))) logger.debug('request.form=' + repr_user_input(request.form)) is_confirmed = False if not is_confirmed: confirm_key = forms.generate_confirm_key( { 'current_user': current_user.qfuserid, 'target_user': user.qfuserid }, 'admin_user_delete') return render_template('admin/update_user_confirm.html', target=url_for('admin_delete_user', userid=userid), user=user, action='delete', next_url=get_next_url('POST'), confirm_key=confirm_key) else: valid_request = data.get('target_user', -1) == user.qfuserid and \ data.get('current_user', -1) == current_user.qfuserid if not valid_request: logger.error( log_action_error('delete user', 'invalid confirmation key', ('user', _get_qfuser_log(user)))) logger.debug('confirmation data=' + repr_user_input(data)) logger.debug('request.form=' + repr_user_input(request.form)) raise BadRequest('Invalid confirmation key.') db.session.delete(user) flash('Deleted user {}'.format(user.username)) db.session.commit() logger.info(log_action('delete user', ('user', _get_qfuser_log(user)))) return redirect(url_for('admin_users'))
def admin_permissions(userid): """ On POST request, update a user's permissions. Redirects to the user management page. POST parameters: * `permissions`: new permissions to set. Data in the following structure: .. code-block:: json { "permissions": [ { "access": "allow|deny", "type": "user|network|buffer", "id": 0 }, { ... } ], "default": "allow|deny" } ``id`` represents the quasseluser, network or buffer ID, depending on the set `type` value. :param userid: The user ID to modify. :return: """ user = query_qfuser(db.session, userid) try: perm_data = json.loads(request.form.get('permissions')) except json.JSONDecodeError: logger.error( log_action_error('update permissions', 'invalid JSON', ('user', _get_qfuser_log(user)))) logger.debug('request.form=' + repr_user_input(request.form)) raise BadRequest('Invalid permissions data.') try: logger.debug( log_action('old permissions', ('user', _get_qfuser_log(user)), ('default', user.access), ('permissions', user.permissions))) user.access = PermissionAccess.from_name(perm_data['default']) # If any permissions for this user, delete them first if user.permissions: del user.permissions[:] # Add the new permissions. Validations: # access, type: from_name validates against enum python-side; db columns are enums # ID: foreign key constraint, database will complain if non-existent for the given type # All: If not present in in_perm (malformed permission object), KeyError raised # TODO: figure out what kind of SQLAlchemy exceptions can be thrown here to throw in with Invalid permissions for in_perm in perm_data['permissions']: user.permissions.append( QfPermission(PermissionAccess.from_name(in_perm['access']), PermissionType.from_name(in_perm['type']), in_perm['id'])) db.session.commit() logger.info( log_action('update permissions', ('user', _get_qfuser_log(user)), ('default', user.access), ('permissions', user.permissions))) except (KeyError, TypeError, ValueError, sqlalchemy.exc.NoReferencedColumnError): logger.error( log_action_error('update permissions', 'invalid perm data', ('user', _get_qfuser_log(user)))) logger.debug('perm_data=' + repr_user_input(perm_data), exc_info=True, stack_info=True) db.session.rollback() raise BadRequest('Invalid permissions data.') # other SQLAlchemy errors are not caught: legitimate server error (HTTP 500), let Flask handle it return safe_redirect(get_next_url('POST'))
def admin_update_user(userid): """ Update a user's information. Any fields not included will remain unchanged. Some actions return a confirmation page. It is necessary to submit the confirmation form to complete the action. If acting on the current user, only the email address may be updated. This prevents the current user from locking themselves out of the application or removing a sole superuser. POST command parameter (exactly one required, except email and confirm_email may appear together): * `status`: 1 (enabled) or 0 (disabled). Other values treated as 0. * `superuser`: 1 (superuser) or 0 (normal user). Other values treated as 0. Returns a confirmation page. * `confirm_token`: Confirmation token. Only applies to superuser/delete requests. * `email`: new email address. Can only be combined with the `confirm_email` parameter. * `confirm_email`: 1 (confirm) or 0 (unconfirm). Force the email confirmation status. Other values are treated as 0. Can only be combined with the `email` parameter. Other POST parameters, common to all requests: * `next`: URL, the URL to return to after the update. :param userid: The user ID to modify. :return: """ commands = frozenset( ('status', 'superuser', 'email', 'confirm_email', 'confirm_token')) request_commands = set(request.form.keys()) & commands # validate request_valid = len(request_commands) == 1 or \ (len(request_commands) == 2 and 'email' in request_commands and 'confirm_email' in request_commands) user_valid = (userid != current_user.qfuserid or ('email' in request_commands and request.form.get('confirm_email', '0') == '1')) if not request_valid: raise BadRequest( "No command or multiple commands to user update API endpoint") if not user_valid: flash("Oops! You can't make changes that would disable yourself.", "error") return safe_redirect(get_next_url('POST')) # Valid - let's process it user = query_qfuser(db.session, userid) if 'status' in request.form: return admin_update_user_status(user) elif 'superuser' in request.form: # non-confirmed return admin_update_user_superuser_confirm(user) elif 'confirm_token' in request.form: # confirm superuser/delete return admin_update_user_superuser(user) elif 'email' in request.form or 'confirm_email' in request.form: return admin_update_user_email(user) flash("Something happened. You shouldn't have gotten this far.", "error") logger.error( log_action_error('update user', 'should have returned earlier in code - bug?', ('user', _get_qfuser_log(user)))) logger.debug('request.form=' + repr_user_input(request.form)) return safe_redirect(get_next_url('POST'))