Esempio n. 1
0
def _send_token(req, info):
    """Send an email token if the password was forgotten.

       _send_token(req, info)

    Returns a status page.
    """
    import logging
    import os
    import manage_kbasix
    import manage_users
    import smtplib
    import aux
    from email.mime.text import MIMEText
    logging.debug('Starting forgotten password procedure for "%s"' % \
                      info['login_name'])
    if not manage_users._info(info['login_name']) or \
            manage_users._info(info['login_name'])['locked'] or \
            info['login_name'] in info['banned_logins']:
        info['class'] = 'fail'
        info['main_header'] = aux._make_header(info)
        info['title'] = 'Password reset'
        info['details'] = 'Either that user name was not found, you are \
not allowed into the system, or the account has yet to be activated.'
        info['status_button_1'] = ''
        info['status_button_2'] = ''
        logging.debug('Token not sent to "%s" because "%s"' % \
                          (info['login_name'], info['details']))
        return aux._fill_page(info['status_page_'], info)
    uid = manage_users._info(info['login_name'])['uid']
    user_email = manage_kbasix._account_info(info['login_name'], \
                                                 'profile')['user_email']
    # The token will only allow access to the profile module (which allows
    # to change the password). It should work from any IP address.
    token = manage_kbasix._create_token(req, uid, ip_set=False, \
                                            access='profile.py')
    www_path = os.path.dirname((req.subprocess_env['SCRIPT_NAME']))
    info['profile_url'] = \
        req.construct_url(www_path + \
                              '/profile.py/process?start&token=%s' % token)
    msg = MIMEText(aux._fill_str(info['reset_password_notice'], info))
    msg['Subject'] = aux._fill_str(info['reset_password_subject'], info)
    msg['From'] = info['reset_password_from_email_']
    msg['To'] = user_email
    s = smtplib.SMTP(info['smtp_host_'])
    s.sendmail(info['reset_password_from_email_'], [user_email], \
                   msg.as_string())
    s.quit()
    info['class'] = 'success'
    info['main_header'] = aux._make_header(info)
    info['title'] = 'Password reset'
    info['details'] = aux._fill_str(info['reset_password_blurb'], info)
    info['status_button_1'] = ''
    info['status_button_2'] = ''
    logging.info('Token sent due to forgotten password by "%s" to "%s"' % \
                     (info['login_name'], user_email))
    return aux._fill_page(info['status_page_'], info)
Esempio n. 2
0
def _account_del(login_name, wipe=False):
    """Delete a KBasix user account.

       _account_del(login_name, wipe=False)

    If 'wipe' is True, the user files are deleted. Otherwise it is
    feasable to re-open the account (perhaps even under a different
    user name, if the former has been taken already).
    """
    (OK, status) = _check_args(locals())
    if not OK:
        raise AccountDelError(status)
    import manage_users
    user_name = _account_info(login_name, 'profile')['user_name']
    uid = str(_info(login_name)['uid'])
    user_dir = os.path.join(users_root_dir_, uid)
    if not wipe:
        _account_mod(login_name, 'profile', {'login_name': '', \
                                                 'user_name': ''})
    (OK, status) = manage_users._del(login_name)
    if not OK:
        raise AccountDelError(status)
    # We have to remove the world symlink in case we want to re-use the
    # user name.
    try:
        os.remove(os.path.join(www_dir_, user_name))
    except Exception as reason:
        raise AccountDelError(reason)
    if wipe:
        try:
            import shutil
            shutil.rmtree(user_dir)
        except Exception as reason:
            raise AccountDelError(reason)
    return
Esempio n. 3
0
def _account_mod(login_name, ext, settings):
    """Modify a KBasix user account.

       _account_mod(login_name, ext, settings)

    The 'ext' can be 'profile' or 'prefs' depending if a user's
    profile information or preferences are being changed. The
    'settings' is a dictionary. Nothing is returned.
    """
    (OK, status) = _check_args(locals())
    if not OK:
        raise AccountModError(status)
    uid = str(_info(login_name)['uid'])
    f = os.path.join(users_root_dir_, uid, uid + '.%s' % ext)
    try:
        extfile = _read_file(f)
    except Exception as reason:
        raise AccountModError(reason)
    for key in settings:
        extfile[key] = settings[key]
    try:
        _save_file(extfile, f)
    except Exception as reason:
        raise AccountModError(reason)
    return
Esempio n. 4
0
def _check_permissions(file_info, info):
    """Check access permission on a file.

       _check_permissions(file_info, info)

    Returns a string: empty if access granted or a "denied access" page
    otherwise.
    """
    import aux
    import manage_users
    gids = manage_users._info(info['login_name'])['gids']
    denied_page = ''
    info['title'] = 'File unavailable'
    info['details'] = 'You no longer have access to this file.'
    info['status_button_1'] = """
        <form action="../file_manager.py/process?start" method="post">
        <input type="hidden" name="token" value="%s" />
        <input type="submit" value="Files" />
        </form>
""" % info['token']
    info['status_button_2'] = ''
    info['class'] = 'warning'
    if not file_info:
        denied_page = aux._fill_page(info['status_page_'], info)
    elif not info['uid'] == file_info['owner_uid'] and \
            not file_info['local_share'] and \
            not info['uid'] in file_info['uid_shares'] and \
            not set(gids).intersection(set(file_info['gid_shares'])):
        denied_page = aux._fill_page(info['status_page_'], info)
    return denied_page
Esempio n. 5
0
def _confirm(req, info):
    import logging
    import manage_kbasix
    import manage_users
    import aux
    logging.debug('Starting confirmation')
    info['title'] = aux._fill_str(info['confirmation_title'], info)
    info['status_button_1'] = ''
    info['status_button_2'] = ''
    if 'token' in req.form:
        token = req.form['token']
    else:
        token = ''
    session = manage_kbasix._check_token(token, first_time=True)
    if not session:
        info['class'] = 'fail'
        info[
            'details'] = 'Failed to confirm account due to an invalid or stale token (probably the account confirmation deadline has passed)'
        logging.warn(info['details'] + ' [%s]' % token)
        return aux._fill_page(info['status_page_'], info)
    else:
        session['login_name'] = manage_users._info(
            session['uid'])['login_name']
        info.update(session)
    manage_users._mod(info['login_name'], {'locked': False})
    manage_kbasix._delete_token(info['uid'])
    profile = manage_kbasix._account_info(info['login_name'], 'profile')
    info.update(profile)
    info['class'] = 'success'
    info['details'] = aux._fill_str(info['successful_confirmation_blurb'],
                                    info)
    logging.info('Successfully confirmed account "%s"' % info['login_name'])
    return aux._fill_page(info['status_page_'], info)
Esempio n. 6
0
def _account_del(login_name, wipe=False):
    """Delete a KBasix user account.

       _account_del(login_name, wipe=False)

    If 'wipe' is True, the user files are deleted. Otherwise it is
    feasable to re-open the account (perhaps even under a different
    user name, if the former has been taken already).
    """
    (OK, status) = _check_args(locals())
    if not OK:
        raise AccountDelError(status)
    import manage_users
    user_name = _account_info(login_name, 'profile')['user_name']
    uid = str(_info(login_name)['uid'])
    user_dir = os.path.join(users_root_dir_, uid)
    if not wipe:
        _account_mod(login_name, 'profile', {'login_name': '', \
                                                 'user_name': ''})
    (OK, status) = manage_users._del(login_name)
    if not OK:
        raise AccountDelError(status)
    # We have to remove the world symlink in case we want to re-use the
    # user name.
    try:
        os.remove(os.path.join(www_dir_, user_name))
    except Exception as reason:
        raise AccountDelError(reason)
    if wipe:
        try:
            import shutil
            shutil.rmtree(user_dir)
        except Exception as reason:
            raise AccountDelError(reason)
    return
Esempio n. 7
0
def _check_permissions(file_info, info):
    """Check access permission on a file.

       _check_permissions(file_info, info)

    Returns a string: empty if access granted or a "denied access" page
    otherwise.
    """
    import aux
    import manage_users
    gids = manage_users._info(info['login_name'])['gids']
    denied_page = ''
    info['title'] = 'File unavailable'
    info['details'] = 'You no longer have access to this file.'
    info['status_button_1'] = """
        <form action="../file_manager.py/process?start" method="post">
        <input type="hidden" name="token" value="%s" />
        <input type="submit" value="Files" />
        </form>
""" % info['token']
    info['status_button_2'] = ''
    info['class'] = 'warning'
    if not file_info:
        denied_page = aux._fill_page(info['status_page_'], info)
    elif not info['uid'] == file_info['owner_uid'] and \
            not file_info['local_share'] and \
            not info['uid'] in file_info['uid_shares'] and \
            not set(gids).intersection(set(file_info['gid_shares'])):
        denied_page = aux._fill_page(info['status_page_'], info)
    return denied_page
Esempio n. 8
0
def _initialize(info):
    import logging
    import manage_kbasix
    import manage_users
    import aux
    profile = manage_kbasix._account_info(info['login_name'], 'profile')
    if '*' in info['allowed_internal_logins'] or \
            info['login_name'] in info['allowed_internal_logins']:
        info['internal_disabled'] = ''
    else:
        info['internal_disabled'] = 'disabled'
    user = manage_users._info(info['login_name'])
    info['first_name'] = user['first_name']
    info['last_name'] = user['last_name']
    if user['auth_method'] == 'ldap':
        info['use_ldap'] = 'checked'
    else:
        info['use_ldap'] = ''
    info['ldap_options'] = ''
    for key in info['ldap_servers']:
        if user['auth_server'] == info['ldap_servers'][key]['server_']:
            info['ldap_servers'][key]['selected_'] = 'selected'
        info['ldap_options'] += info['ldap_servers'][key]['option_'] % info[
            'ldap_servers'][key]
    info['user_ldap_name'] = user['user_auth_name']
    info.update(profile)
    logging.debug('Starting profile page (%s)' % info['login_name'])
    return aux._fill_page(info['profile_page_'], info)
Esempio n. 9
0
def _initialize(info):
    import logging
    import manage_kbasix
    import manage_users
    import aux
    profile = manage_kbasix._account_info(info['login_name'], 'profile')
    if '*' in info['allowed_internal_logins'] or \
            info['login_name'] in info['allowed_internal_logins']:
        info['internal_disabled'] = ''
    else:
        info['internal_disabled'] = 'disabled'
    user = manage_users._info(info['login_name'])
    info['first_name'] = user['first_name']
    info['last_name'] = user['last_name']
    if user['auth_method'] == 'ldap':
        info['use_ldap'] = 'checked'
    else:
        info['use_ldap'] = ''
    info['ldap_options'] = ''
    for key in info['ldap_servers']:
        if user['auth_server'] == info['ldap_servers'][key]['server_']:
            info['ldap_servers'][key]['selected_'] = 'selected'
        info['ldap_options'] += info['ldap_servers'][key]['option_'] % info['ldap_servers'][key]
    info['user_ldap_name'] = user['user_auth_name']
    info.update(profile)
    logging.debug('Starting profile page (%s)' % info['login_name'])
    return aux._fill_page(info['profile_page_'], info)
Esempio n. 10
0
def _account_mod(login_name, ext, settings):
    """Modify a KBasix user account.

       _account_mod(login_name, ext, settings)

    The 'ext' can be 'profile' or 'prefs' depending if a user's
    profile information or preferences are being changed. The
    'settings' is a dictionary. Nothing is returned.
    """
    (OK, status) = _check_args(locals())
    if not OK:
        raise AccountModError(status)
    uid = str(_info(login_name)['uid'])
    f = os.path.join(users_root_dir_, uid, uid + '.%s' % ext)
    try:
        extfile = _read_file(f)
    except Exception as reason:
        raise AccountModError(reason)
    for key in settings:
        extfile[key] = settings[key]
    try:
        _save_file(extfile, f)
    except Exception as reason:
        raise AccountModError(reason)
    return
Esempio n. 11
0
def _parse_shares(shares, shares_type, info):
    """Parse the string of user/group name shares into a list. Checks
    are made for deleted shares and missing users (prepended by a '-' and
    '*', respectively).

       (ids, err) = _parse_shares(shares, shares_type, info)

    Returns a tuple (list, str) with the uids/gids and an error string
    (if any).
    """
    import logging
    import manage_users
    ids = []
    err = ''
    # Note that the shares have already been forced to be ASCII-encoded in
    # '_update'.
    if shares_type == 'uid_shares':
        shares_type = 'account'
        shares_id = 'uid'
        # You cannot share with yourself
        names = list(set([i.strip().lower() for i in shares.split(',') if \
                              i.strip().lower() != info['login_name']]))
    else:
        # Groups are case-sensitive (since their names are not enforced by
        # manage_kbasix).
        shares_type = 'group'
        shares_id = 'gid'
        names = list(set([i.strip() for i in shares.split(',')]))
    logging.debug('Parsed shares (%s): %s' % (info['login_name'], names))
    if names == ['']:
        return (ids, err)
    for i in names:
        try:
            # _get_shares will prepend the '-' if the share was deleted,
            # and use *.# for deleted users.
            if i[0] == '-':
                i = i[1:]
            if i[0] != '*':
                ids.append(
                    manage_users._info(i, is_type=shares_type)[shares_id])
            else:
                ids.append(int(i.split('.')[-1]))
        except:
            err += 'Ignoring unknown %s "%s"<br>' % (shares_type, i)
            logging.warn('Ignoring unknown id "%s" (%s)' % \
                             (i, info['login_name']))
    return (sorted(ids), err)
Esempio n. 12
0
def _finger(login_name):
    """Interactively retrieve information about a user account.

       info = _finger(login_name)

    The return type may vary, should only be used interactively.
    """
    (OK, status) = _check_args(locals())
    if not OK:
        raise FingerError(status)
    import pprint
    user_info = _info(login_name)
    if not user_info:
        return 'User "%s" not found.' % login_name
    uid = str(user_info['uid'])
    f = os.path.join(users_root_dir_, uid, uid + '.%s' % 'profile')
    try:
        return pprint.pprint(_read_file(f, lock=False))
    except Exception as reason:
        raise FingerError(reason)
Esempio n. 13
0
def _finger(login_name):
    """Interactively retrieve information about a user account.

       info = _finger(login_name)

    The return type may vary, should only be used interactively.
    """
    (OK, status) = _check_args(locals())
    if not OK:
        raise FingerError(status)
    import pprint
    user_info = _info(login_name)
    if not user_info:
        return 'User "%s" not found.' % login_name
    uid = str(user_info['uid'])
    f = os.path.join(users_root_dir_, uid, uid + '.%s' % 'profile')
    try:
        return pprint.pprint(_read_file(f, lock=False))
    except Exception as reason:
        raise FingerError(reason)
Esempio n. 14
0
def _confirmation_email(req, info):
    """Send a confirmation email to complete registration.

       _confirmation_email(req, info)

    Returns nothing.
    """
    import logging
    import os
    import manage_kbasix
    import manage_users
    import smtplib
    import aux
    from email.mime.text import MIMEText
    logging.debug('Sending confirmation email for "%s"' % \
                      info['login_name'])
    uid = manage_users._info(info['login_name'])['uid']
    # We set 'ip_set=False' so that users can confirm their account
    # from any IP.
    token = manage_kbasix._create_token(req, uid, ip_set=False)
    www_path = os.path.dirname((req.subprocess_env['SCRIPT_NAME']))
    confirm_urn = '/confirm.py/process?start&token=%s' % token
    info['confirm_url'] = req.construct_url(www_path + confirm_urn)
    msg = MIMEText(aux._fill_str(info['confirmation_notice'], info))
    msg['Subject'] = aux._fill_str(info['email_subject'], info)
    msg['From'] = info['email_from_']
    msg['To'] = info['user_email']
    try:
        s = smtplib.SMTP(info['smtp_host_'])
        s.sendmail(info['email_from_'], [info['user_email']], \
                       msg.as_string())
        s.quit()
    except Exception as reason:
        logging.error('Unable to send confirmation email because \
"%s" (%s)' % (reason, info['login_name']))
        raise ConfirmationEmailError(reason)
    logging.debug('Confirmation email sent sucessfully for "%s" to "%s"' % \
                      (info['login_name'], info['user_email']))
    return
Esempio n. 15
0
def _account_info(login_name, ext):
    """Retrieve information about a user account.

       info = _account_info(login_name, ext)

    The 'ext' can be 'profile' or 'prefs' depending if a user's
    profile information or preferences are being accessed. Return
    is a dictionary (possibly an empty one). This function is suitable
    for function calls from other programs.
    """
    (OK, status) = _check_args(locals())
    if not OK:
        raise AccountInfoError(status)
    user_info = _info(login_name)
    if not user_info:
        return {}
    uid = str(user_info['uid'])
    f = os.path.join(users_root_dir_, uid, uid + '.%s' % ext)
    try:
        return _read_file(f, lock=False)
    except Exception as reason:
        raise AccountInfoError(reason)
Esempio n. 16
0
def _account_info(login_name, ext):
    """Retrieve information about a user account.

       info = _account_info(login_name, ext)

    The 'ext' can be 'profile' or 'prefs' depending if a user's
    profile information or preferences are being accessed. Return
    is a dictionary (possibly an empty one). This function is suitable
    for function calls from other programs.
    """
    (OK, status) = _check_args(locals())
    if not OK:
        raise AccountInfoError(status)
    user_info = _info(login_name)
    if not user_info:
        return {}
    uid = str(user_info['uid'])
    f = os.path.join(users_root_dir_, uid, uid + '.%s' % ext)
    try:
        return _read_file(f, lock=False)
    except Exception as reason:
        raise AccountInfoError(reason)
Esempio n. 17
0
def _confirmation_email(req, info):
    """Send a confirmation email to complete registration.

       _confirmation_email(req, info)

    Returns nothing.
    """
    import logging
    import os
    import manage_kbasix
    import manage_users
    import smtplib
    import aux
    from email.mime.text import MIMEText
    logging.debug('Sending confirmation email for "%s"' % \
                      info['login_name'])
    uid = manage_users._info(info['login_name'])['uid']
    # We set 'ip_set=False' so that users can confirm their account
    # from any IP.
    token = manage_kbasix._create_token(req, uid, ip_set=False)
    www_path = os.path.dirname((req.subprocess_env['SCRIPT_NAME']))
    confirm_urn = '/confirm.py/process?start&token=%s' % token
    info['confirm_url'] = req.construct_url(www_path + confirm_urn)
    msg = MIMEText(aux._fill_str(info['confirmation_notice'], info))
    msg['Subject'] = aux._fill_str(info['email_subject'], info)
    msg['From'] = info['email_from_']
    msg['To'] = info['user_email']
    try:
        s = smtplib.SMTP(info['smtp_host_'])
        s.sendmail(info['email_from_'], [info['user_email']], \
                       msg.as_string())
        s.quit()
    except Exception as reason:
        logging.error('Unable to send confirmation email because \
"%s" (%s)' % (reason, info['login_name']))
        raise ConfirmationEmailError(reason)
    logging.debug('Confirmation email sent sucessfully for "%s" to "%s"' % \
                      (info['login_name'], info['user_email']))
    return
Esempio n. 18
0
def _login(req, info):
    """Try to login the user, or send an email token if the password is
    forgotten.

       _login(req, info)

    Returns a status page.
    """
    if 'forgot_password' in req.form:
        try:
            return _send_token(req, info)
        except Exception as reason:
            raise SendTokenError(reason)
    import logging
    from mod_python import apache
    import manage_users
    import manage_kbasix
    import time
    import aux
    from mod_python import util
    logging.debug('Starting login process for "%s"' % info['login_name'])
    if '*' in info['allowed_internal_logins'] or \
            info['login_name'] in info['allowed_internal_logins']:
        allow_internal = True
    else:
        allow_internal = False
    password = req.form['user_password'].value
    # Note that '_authenticate' only returns the uid if 'is_usr' is
    # True, otherwise it'll return a keyword specifying the authentication
    # failure point.
    try:
        (is_usr, status) = manage_users._authenticate(info['login_name'], \
                                                          password)
        logging.info('Authentication for "%s" was "%s" with \
status/uid: %s' % (info['login_name'], is_usr, status))
    except Exception as reason:
        raise LoginError(reason)
    blocked = False
    # We don't just show the reasons for authentication failures, but
    # instead use codes defined in 'defs.py' (the log is explicit in this
    # respect).
    if is_usr:
        uid = status
        locked = manage_users._info(info['login_name'])['locked']
        if locked:
            blocked = True
            msg = info['reason_not_active_']
    else:
        blocked = True
        msg = info['reason_auth_fail_']
    if not blocked:
        auth_method = manage_users._info(info['login_name'])['auth_method']
        # An empty 'auth_method' means the auth is internal.
        if not auth_method and not allow_internal:
            blocked = True
            msg = info['reason_not_allowed_']
        elif info['login_name'] in info['banned_logins']:
            blocked = True
            msg = info['reason_banned_']
    if not blocked:
        try:
            info['token'] = manage_kbasix._create_token(req, uid)
            info['user_name'] = \
                manage_kbasix._account_info(info['login_name'], \
                                                'profile')['user_name']
            info['access'] = 'all'
            info['class'] = 'information'
            info['main_header'] = aux._make_header(info)
            info['title'] = aux._fill_str(info['welcome_title'], info)
            info['details'] = aux._fill_str(info['welcome_blurb'], info)
            info['status_button_1'] = """
<form action="../%(referrer)s/process?start" method="post">
<input type="hidden" name="token" value="%(token)s">
<input type="submit" value="Continue" />
</form>
""" % info
            info['status_button_2'] = ''
            manage_kbasix._account_mod(info['login_name'], \
                                           'profile', {'last_login': \
                                                           time.time()})
            logging.info('Successful login from %s (%s)' % \
                             (req.get_remote_host(apache.REMOTE_NOLOOKUP), \
                                  info['login_name']))
            return aux._fill_page(info['status_page_'], info)
        except Exception as reason:
            raise LoginError(reason)
    else:
        info['class'] = 'fail'
        info['title'] = 'Login'
        info['details'] = aux._fill_str(msg, info)
        info['status_button_1'] = aux._go_back_button(req, token = '')
        info['status_button_2'] = ''
        logging.info('Failed login for "%s" because "%s"' % \
                         (info['login_name'], info['details']))
        return aux._fill_page(info['status_page_'], info)
Esempio n. 19
0
def _get_file_list(info):
    """Get the file listing.

       file_list = _get_file_list(info)

    Returns a string.
    """
    # This function is way too big, it needs to be sensibly split up.
    import logging
    import os
    import fnmatch
    import time
    import aux
    import manage_users
    import manage_kbasix
    login_name = info['login_name']
    logging.debug('Creating a file list (%s)' % login_name)
    prefs = manage_kbasix._account_info(login_name, 'prefs')
    user_dir = os.path.join(info['users_root_dir_'], str(info['uid']))
    entries = {}
    count = {}
    gids = manage_users._info(login_name)['gids']
    # Check for local- and GID-shared files.
    for subdir in ['local'] + [str(i) for i in gids]:
        subpath = os.path.join(info['shared_dir_'], subdir)
        if os.path.exists(subpath):
            # Some house cleaning: delete broken symlinks.
            for i in os.listdir(subpath):
                f = os.path.join(subpath, i)
                # We are lenient with file removals in shared directories
                # because another user might have beat us to it.
                if not os.path.exists(f):
                    try:
                        os.remove(f)
                        logging.info('Deleted broken shared symlink "%s" \
(%s)' % (f, login_name))
                    except Exception as reason:
                        logging.warn(reason)
            for i in fnmatch.filter(os.listdir(subpath), '*-file'):
                rel_path = os.path.relpath(subpath, user_dir)
                src = os.path.join(rel_path, i)
                dst = os.path.join(user_dir, i)
                # Shared entries which are deleted by the user (the
                # sharee) are actually hidden from view by adding them
                # to the 'hidden_gidloc_shared_files' list when the
                # sharee deletes them.
                # Once hidden, local/gid-shared files cannot be recovered
                # unless explicitly re-shared with that user via uid_shares.
                # Note that if the file exists (important if it's a uid
                # point share, which takes precedence) we leave it alone.
                if not os.path.exists(dst) and \
                        i not in \
                        prefs['file_manager']['hidden_gidloc_shared_files']:
                    try:
                        # The target may not exist, but if the link does,
                        # delete it.
                        if os.path.islink(dst + '-id'):
                            os.remove(dst + '-id')
                        os.symlink(src + '-id', dst + '-id')
                    except:
                        logging.error('Cannot link shared id file "%s" \
(%s)' % (src + '-id', login_name))
                    else:
                        if os.path.islink(dst):
                            os.remove(dst)
                        os.symlink(src, dst)
                        logging.debug('Added shared file "%s" -> "%s" \
(%s)' % (src, dst, login_name))
    # Create the listing of the files/symlinks in the user's directory.
    for i in os.listdir(user_dir):
        f = os.path.join(user_dir, i)
        if not os.path.exists(f):
            try:
                os.remove(f)
                logging.info('Deleted broken user symlink "%s" (%s)' % \
                                 (f, login_name))
            except Exception as reason:
                logging.error(reason)
    for i in fnmatch.filter(os.listdir(user_dir), '*-id'):
        f = os.path.join(user_dir, i)
        # This should not normally happen. The magic [:-3] deletes '-id'
        # and leaves the content file name.
        if not os.path.exists(f[:-3]):
            try:
                os.remove(f)
                logging.error('Deleted orphan id file "%s" (%s)' % \
                                  (f, login_name))
            except Exception as reason:
                logging.error(reason)
            continue
        details = manage_users._read_file(f, lock=False)
        # A symlink implies the file is being shared with the user.
        if os.path.islink(f):
            details['shared_with_me'] = True
        else:
            details['shared_with_me'] = False
        for j in ['file_title', 'file_description']:
            if not details[j]: details[j] = info['empty_placeholder_']
        details['file_date'] = \
            time.strftime(info['file_manager_time_format_'], \
                              time.localtime(details['timestamp']))
        # We need the token for the buttons.
        details['token'] = info['token']
        sort_key = details[prefs['file_manager']['sort_criteria']]
        # We pad with zeros to obtain a natural sorting for entries with
        # the same non-string keys e.g. size or timestamp. Padding to 50
        # seems safe, although this is admittedly a magic number
        # (timestamps have less than 20 digits, and files size are smaller
        # than that). Worst case scenario is that the sorting on huge
        # numeric sort keys (larger than 50 digits) will be wrong, but this
        # is unlikely to happen and has no other consequences.
        if not isinstance(sort_key, basestring):
            sort_key = '%r' % sort_key
            sort_key = sort_key.zfill(50)
        # We internally distinguish between identical entry values
        # (say, identical file names), but although lumped together
        # in their proper place within the overall list they are
        # not sub-sorted in any deliberate way.
        if sort_key not in entries:
            count[sort_key] = 1
            entries[sort_key] = details
        else:
            count[sort_key] += 1
            entries[sort_key + ' (%s)' % count[sort_key]] = details
    if prefs['file_manager']['condensed_view']:
        info['template'] = 'condensed_entry_template_'
    else:
        info['template'] = 'entry_template_'
    file_list = ''
    for key in sorted(entries, \
                          reverse=bool(prefs['file_manager']['reverse'])):
        entries[key]['shared_status'] = ''
        entries[key]['file_colour'] = info['my_file_colour_']
        if entries[key]['shared_with_me']:
            # World shares are not symlinked with the individual accounts,
            # and entries which cannot be read any longer are deleted.
            if not entries[key]['local_share'] and \
                    not info['uid'] in entries[key]['uid_shares'] and not \
                    set(gids).intersection(set(entries[key]['gid_shares'])):
                shared_file = \
                    os.path.join(user_dir, entries[key]['file_tag'])
                try:
                    os.remove(shared_file)
                    os.remove(shared_file + '-id')
                except Exception as reason:
                    logging.error(reason)
                continue
            # Don't show the shares made by no-longer-exsting users if
            # 'exusers_cannot_share' is True.
            elif not manage_users._info(entries[key]['owner_uid']) and \
                    info['exusers_cannot_share']:
                continue
            else:
                entries[key]['shared_status'] = """
                 <img src="%s" title="File shared with me by: %s"
                   alt="[File shared with me by: %s]" />
""" % (info['shared_icon_with_me_'], entries[key]['owner'], \
           entries[key]['owner'])
                entries[key]['file_colour'] = info['other_file_colour_']
        if prefs['file_manager']['hide_shared'] and \
                entries[key]['shared_with_me']:
            continue
        # Shared files can be copied internally (it's more efficient than
        # downloading and uploading again).
        entries[key]['copy_file'] = ''
        if entries[key]['shared_with_me']:
            entries[key]['copy_file_icon_'] = info['copy_file_icon_']
            entries[key]['copy_file_form_style_'] = \
                info['copy_file_form_style_']
            entries[key]['copy_file'] = """
             <form %(copy_file_form_style_)s
              action="../file_manager.py/process?action=copy_file"
              method="post">
              <input type="hidden" name="token" value="%(token)s" />
              <input type="hidden" name="file_tag" value="%(file_tag)s" />
              <input title="Make a local copy" type="image"
              alt="Make a local copy" src="%(copy_file_icon_)s" />
             </form>
""" % entries[key]
        # Files that are shared (the user being the sharer) are tagged
        # in various ways to indicate this.
        else:
            if entries[key]['world_share']:
                entries[key]['shared_status'] += """
                 <img src="%s" title="File is shared with the world"
                  alt="[File is shared with the world]" />
""" % info['shared_icon_by_me_to_world_']
            if entries[key]['local_share']:
                entries[key]['shared_status'] += """
                 <img src="%s" title="File is shared with registered users"
                  alt="[File is shared with registered users]" />
""" % info['shared_icon_by_me_locally_']
            if entries[key]['gid_shares']:
                entries[key]['shared_status'] += """
                 <img src="%s" title="File is shared with selected groups"
                  alt="[File is shared with selected groups]" />
""" % info['shared_icon_by_me_to_groups_']
            if entries[key]['uid_shares']:
                entries[key]['shared_status'] += """
                 <img src="%s" title="File is shared with selected users"
                  alt="[File is shared with selected users]" />
""" % info['shared_icon_by_me_selectively_']
        # Convert the size to a string with the appropriate unit suffix e.g.
        # 'MB'.
        entries[key]['file_size_str'] = \
            aux._bytes_string(entries[key]['file_size'])
        entries[key]['toggle_select'] = info['toggle_select']
        # Limit the length of titles and descriptions.
        for i in ['description', 'title']:
            if info['max_chars_in_' + i]:
                max_char = len(entries[key]['file_' + i])
                char_num = min(max_char, info['max_chars_in_' + i])
                entries[key][i + '_blurb'] = \
                    entries[key]['file_' + i][:char_num]
                if max_char > char_num:
                    entries[key][i + '_blurb'] += '...'
            else:
                entries[key][i + '_blurb'] = entries[key]['file_' + i]
        # The keyword filter. This is actually a very important
        # functionality, and should be split into its own function.
        # Furthermore, it is currently very weak, and should be drastically
        # improved. Right now is just filters on words in the file title
        # and description, ignoring punctuation. It is also
        # case-insensitive.
        if prefs['file_manager']['keywords']:
            import re
            # Use '[\W_]+' to eliminate underscores. See also:
            #
            # http://stackoverflow.com/questions/6631870/strip-non-alpha-numeric-characters-from-string-in-python-but-keeping-special-cha
            #
            # The 're.U' works on unicode strings.
            nonalnum = re.compile('[\W]+', re.U)
            keywords = set([i.lower() for i in \
                                prefs['file_manager']['keywords'].split()])
            words = [nonalnum.sub('', i.lower()) for i in \
                         entries[key]['file_title'].split()]
            words += [nonalnum.sub('', i.lower()) for i in \
                          entries[key]['file_description'].split()]
            if keywords <= set(words):
                file_list += aux._fill_str(info[info['template']], \
                                               entries[key])
            continue
        file_list += aux._fill_str(info[info['template']], entries[key])
    if not file_list:
        return info['no_files_found_']
    else:
        return file_list
Esempio n. 20
0
def _update(req, info):
    import logging
    import re
    import time
    import manage_users
    import manage_kbasix
    import aux
    if '*' in info['allowed_internal_logins'] or \
            info['login_name'] in info['allowed_internal_logins']:
        allow_internal = True
    else:
        allow_internal = False
    profile = {}
    account = {}
    account['first_name'] = req.form['first_name'].value
    account['last_name'] = req.form['last_name'].value
    profile['user_email'] = req.form['user_email'].value
    err = 0
    info['details'] = ''
    if not re.match(info['valid_email_'], profile['user_email'], re.I):
        err += 1
        info['details'] += 'Please input a valid email address.<br>'
    if 'user_password' in req.form:
        info['user_password'] = req.form['user_password'].value
    else:
        info['user_password'] = ''
    info['user_ldap_password'] = req.form['user_ldap_password'].value
    info['user_ldap_name'] = req.form['user_ldap_name'].value
    was_external = manage_users._info(info['login_name'])['auth_method']
    if 'use_ldap' in req.form and info['user_ldap_password']:
        if not info['user_ldap_name']:
            err += 1
            info['details'] += 'Please fill out all the LDAP credentials.<br>'
        else:
            from register import _check_ldap
            info['ldap_server'] = req.form['ldap_server'].value
            if info['ldap_server']:
                (err, info['details']) = _check_ldap(info, err)
            else:
                err += 1
                info['details'] += 'Please specify the LDAP server.<br>'
            if not err:
                account['password'] = '******'
                account['auth_method'] = 'ldap'
                account['auth_server'] = info['ldap_server']
                account['user_auth_name'] = info['user_ldap_name']
                if not was_external:
                    info[
                        'details'] += 'Authentication is no longer internal.<br>'
    elif info['user_password'] and allow_internal:
        from register import _check_password
        info['user_password_check'] = req.form['user_password_check'].value
        (err, info['details']) = _check_password(info, err)
        if not err:
            account['auth_method'] = ''
            account['auth_server'] = account['user_auth_name'] = ''
            account['password'] = info['user_password']
            if was_external:
                info['details'] += 'Authentication is now internal.<br>'
    else:
        info['details'] += 'Note: no authentication changes made.<br>'
    if not err:
        try:
            manage_kbasix._account_mod(info['login_name'], 'profile', profile)
            manage_users._mod(info['login_name'], account)
        except Exception as reason:
            raise UpdateError(reason)
    if err:
        info['class'] = 'fail'
        info['status_button_1'] = aux._go_back_button(req, info['token'])
        info['status_button_2'] = ''
        logging.debug('Failed profile update because "%s" (%s)' % \
                          (info['details'].replace('<br>',' '), info['login_name']))
    else:
        info['class'] = 'success'
        info['details'] += aux._fill_str(info['successful_update_blurb'], info)
        if info['access'] == 'profile.py':
            info['status_button_1'] = """
          <form action="../login.py/process?start" method="post">
            <input type="submit" value="Login" />
          </form><br>
""" % info
        else:
            info['status_button_1'] = ''
        info['status_button_2'] = ''
        logging.debug('Successful profile update (%s)' % info['login_name'])
    info['title'] = 'User Profile'
    return aux._fill_page(info['status_page_'], info)
Esempio n. 21
0
def _get_file_list(info):
    """Get the file listing.

       file_list = _get_file_list(info)

    Returns a string.
    """
    # This function is way too big, it needs to be sensibly split up.
    import logging
    import os
    import fnmatch
    import time
    import aux
    import manage_users
    import manage_kbasix
    login_name = info['login_name']
    logging.debug('Creating a file list (%s)' % login_name)
    prefs = manage_kbasix._account_info(login_name, 'prefs')
    user_dir = os.path.join(info['users_root_dir_'], str(info['uid']))
    entries = {}
    count = {}
    gids = manage_users._info(login_name)['gids']
    # Check for local- and GID-shared files.
    for subdir in ['local'] + [str(i) for i in gids]:
        subpath = os.path.join(info['shared_dir_'], subdir)
        if os.path.exists(subpath):
            # Some house cleaning: delete broken symlinks.
            for i in os.listdir(subpath):
                f = os.path.join(subpath, i)
                # We are lenient with file removals in shared directories
                # because another user might have beat us to it.
                if not os.path.exists(f):
                    try:
                        os.remove(f)
                        logging.info('Deleted broken shared symlink "%s" \
(%s)' % (f, login_name))
                    except Exception as reason:
                        logging.warn(reason)
            for i in fnmatch.filter(os.listdir(subpath), '*-file'):
                rel_path = os.path.relpath(subpath, user_dir)
                src = os.path.join(rel_path, i)
                dst = os.path.join(user_dir, i)
                # Shared entries which are deleted by the user (the
                # sharee) are actually hidden from view by adding them
                # to the 'hidden_gidloc_shared_files' list when the
                # sharee deletes them.
                # Once hidden, local/gid-shared files cannot be recovered
                # unless explicitly re-shared with that user via uid_shares.
                # Note that if the file exists (important if it's a uid
                # point share, which takes precedence) we leave it alone.
                if not os.path.exists(dst) and \
                        i not in \
                        prefs['file_manager']['hidden_gidloc_shared_files']:
                    try:
                        # The target may not exist, but if the link does,
                        # delete it.
                        if os.path.islink(dst + '-id'):
                            os.remove(dst + '-id')
                        os.symlink(src + '-id', dst + '-id')
                    except:
                        logging.error('Cannot link shared id file "%s" \
(%s)' % (src + '-id', login_name))
                    else:
                        if os.path.islink(dst):
                            os.remove(dst)
                        os.symlink(src, dst)
                        logging.debug('Added shared file "%s" -> "%s" \
(%s)' % (src, dst, login_name))
    # Create the listing of the files/symlinks in the user's directory.
    for i in os.listdir(user_dir):
        f = os.path.join(user_dir, i)
        if not os.path.exists(f):
            try:
                os.remove(f)
                logging.info('Deleted broken user symlink "%s" (%s)' % \
                                 (f, login_name))
            except Exception as reason:
                logging.error(reason)
    for i in fnmatch.filter(os.listdir(user_dir), '*-id'):
        f = os.path.join(user_dir, i)
        # This should not normally happen. The magic [:-3] deletes '-id'
        # and leaves the content file name.
        if not os.path.exists(f[:-3]):
            try:
                os.remove(f)
                logging.error('Deleted orphan id file "%s" (%s)' % \
                                  (f, login_name))
            except Exception as reason:
                logging.error(reason)
            continue
        details = manage_users._read_file(f, lock=False)
        # A symlink implies the file is being shared with the user.
        if os.path.islink(f):
            details['shared_with_me'] = True
        else:
            details['shared_with_me'] = False
        for j in ['file_title', 'file_description']:
            if not details[j]: details[j] = info['empty_placeholder_']
        details['file_date'] = \
            time.strftime(info['file_manager_time_format_'], \
                              time.localtime(details['timestamp']))
        # We need the token for the buttons.
        details['token'] = info['token']
        sort_key = details[prefs['file_manager']['sort_criteria']]
        # We pad with zeros to obtain a natural sorting for entries with
        # the same non-string keys e.g. size or timestamp. Padding to 50
        # seems safe, although this is admittedly a magic number
        # (timestamps have less than 20 digits, and files size are smaller
        # than that). Worst case scenario is that the sorting on huge
        # numeric sort keys (larger than 50 digits) will be wrong, but this
        # is unlikely to happen and has no other consequences.
        if not isinstance(sort_key, basestring):
            sort_key = '%r' % sort_key
            sort_key = sort_key.zfill(50)
        # We internally distinguish between identical entry values
        # (say, identical file names), but although lumped together
        # in their proper place within the overall list they are
        # not sub-sorted in any deliberate way.
        if sort_key not in entries:
            count[sort_key] = 1
            entries[sort_key] = details
        else:
            count[sort_key] += 1
            entries[sort_key + ' (%s)' % count[sort_key]] = details
    if prefs['file_manager']['condensed_view']:
        info['template'] = 'condensed_entry_template_'
    else:
        info['template'] = 'entry_template_'
    file_list = ''
    for key in sorted(entries, \
                          reverse=bool(prefs['file_manager']['reverse'])):
        entries[key]['shared_status'] = ''
        entries[key]['file_colour'] = info['my_file_colour_']
        if entries[key]['shared_with_me']:
            # World shares are not symlinked with the individual accounts,
            # and entries which cannot be read any longer are deleted.
            if not entries[key]['local_share'] and \
                    not info['uid'] in entries[key]['uid_shares'] and not \
                    set(gids).intersection(set(entries[key]['gid_shares'])):
                shared_file = \
                    os.path.join(user_dir, entries[key]['file_tag'])
                try:
                    os.remove(shared_file)
                    os.remove(shared_file + '-id')
                except Exception as reason:
                    logging.error(reason)
                continue
            # Don't show the shares made by no-longer-exsting users if
            # 'exusers_cannot_share' is True.
            elif not manage_users._info(entries[key]['owner_uid']) and \
                    info['exusers_cannot_share']:
                continue
            else:
                entries[key]['shared_status'] = """
                 <img src="%s" title="File shared with me by: %s"
                   alt="[File shared with me by: %s]" />
""" % (info['shared_icon_with_me_'], entries[key]['owner'], \
           entries[key]['owner'])
                entries[key]['file_colour'] = info['other_file_colour_']
        if prefs['file_manager']['hide_shared'] and \
                entries[key]['shared_with_me']:
            continue
        # Shared files can be copied internally (it's more efficient than
        # downloading and uploading again).
        entries[key]['copy_file'] = ''
        if entries[key]['shared_with_me']:
            entries[key]['copy_file_icon_'] = info['copy_file_icon_']
            entries[key]['copy_file_form_style_'] = \
                info['copy_file_form_style_'] 
            entries[key]['copy_file'] = """
             <form %(copy_file_form_style_)s
              action="../file_manager.py/process?action=copy_file"
              method="post">
              <input type="hidden" name="token" value="%(token)s" />
              <input type="hidden" name="file_tag" value="%(file_tag)s" />
              <input title="Make a local copy" type="image"
              alt="Make a local copy" src="%(copy_file_icon_)s" />
             </form>
""" % entries[key]
        # Files that are shared (the user being the sharer) are tagged
        # in various ways to indicate this.
        else:
            if entries[key]['world_share']:
                entries[key]['shared_status'] += """
                 <img src="%s" title="File is shared with the world"
                  alt="[File is shared with the world]" />
""" % info['shared_icon_by_me_to_world_']
            if entries[key]['local_share']:
                entries[key]['shared_status'] += """
                 <img src="%s" title="File is shared with registered users"
                  alt="[File is shared with registered users]" />
""" % info['shared_icon_by_me_locally_']
            if entries[key]['gid_shares']:
                entries[key]['shared_status'] += """
                 <img src="%s" title="File is shared with selected groups"
                  alt="[File is shared with selected groups]" />
""" % info['shared_icon_by_me_to_groups_']
            if entries[key]['uid_shares']:
                entries[key]['shared_status'] += """
                 <img src="%s" title="File is shared with selected users"
                  alt="[File is shared with selected users]" />
""" % info['shared_icon_by_me_selectively_']
        # Convert the size to a string with the appropriate unit suffix e.g.
        # 'MB'.
        entries[key]['file_size_str'] = \
            aux._bytes_string(entries[key]['file_size'])
        entries[key]['toggle_select'] = info['toggle_select']
        # Limit the length of titles and descriptions.
        for i in ['description', 'title']:
            if info['max_chars_in_' + i]:
                max_char = len(entries[key]['file_' + i])
                char_num = min(max_char, info['max_chars_in_' + i])
                entries[key][i + '_blurb'] = \
                    entries[key]['file_' + i][:char_num]
                if max_char > char_num:
                    entries[key][i + '_blurb'] += '...'
            else:
                entries[key][i + '_blurb'] = entries[key]['file_' + i]
        # The keyword filter. This is actually a very important
        # functionality, and should be split into its own function.
        # Furthermore, it is currently very weak, and should be drastically
        # improved. Right now is just filters on words in the file title
        # and description, ignoring punctuation. It is also
        # case-insensitive.
        if prefs['file_manager']['keywords']:
            import re
            # Use '[\W_]+' to eliminate underscores. See also:
            #
            # http://stackoverflow.com/questions/6631870/strip-non-alpha-numeric-characters-from-string-in-python-but-keeping-special-cha
            #
            # The 're.U' works on unicode strings.
            nonalnum = re.compile('[\W]+', re.U)
            keywords = set([i.lower() for i in \
                                prefs['file_manager']['keywords'].split()])
            words = [nonalnum.sub('', i.lower()) for i in \
                         entries[key]['file_title'].split()]
            words += [nonalnum.sub('', i.lower()) for i in \
                          entries[key]['file_description'].split()]
            if keywords <= set(words):
                file_list += aux._fill_str(info[info['template']], \
                                               entries[key])
            continue
        file_list += aux._fill_str(info[info['template']], entries[key])
    if not file_list:
        return info['no_files_found_']
    else:
        return file_list
Esempio n. 22
0
def _get_shares(file_info, info):
    """Convert UIDs/GIDs/boolean shares as stored in the file metadata
    into actual user names/group names/HTML 'checked' (or not).

       (users, groups, local, world) = _get_shares(file_info, info)

    Returns a (list, list, str, str) tuple.
    """
    import logging
    import os
    import manage_kbasix
    import manage_users
    id_file = file_info['file_tag'] + '-id'
    users = []
    for i in file_info['uid_shares']:
        user_info = manage_users._info(i)
        # If 'remove_exusers_shares' is False we'll try to keep
        # the shares of non-existent users (so that if they return
        # they can recover said shares). However, this doesn't work
        # well in practice and the metadata editor might wipe it out
        # during an update. Furthermore, the user might be confused
        # when seeing '*.7' in the list. Same for 'remove_exgroups_shares'.
        if not user_info:
            if not info['remove_exusers_shares']:
                users.append('*.%s' % i)
        else:
            # Users who have deleted the file being shared with them
            # show up with a '-' prefix in the shared user list.
            user_name = \
                manage_kbasix._account_info(user_info['login_name'], \
                                                'profile')['user_name']
            if not os.path.exists(os.path.join(info['users_root_dir_'], \
                                                   str(user_info['uid']), \
                                                   id_file)):
                users.append('-' + user_name)
            else:
                users.append(user_name)
    if users:
        users = ', '.join(users)
    else:
        users = ''
    groups = []
    for i in file_info['gid_shares']:
        group_info = manage_users._info(i, is_type='group')
        if not group_info:
            if not info['remove_exgroups_shares']:
                groups.append('*.%s' % i)
        else:
            groups.append(group_info['group_name'])
    if groups:
        groups = ', '.join(groups)
    else:
        groups = ''
    # The booleans become 'checked' if True, '' otherwise.
    if file_info['local_share']:
        local = 'checked'
    else:
        local = ''
    if file_info['world_share']:
        world = 'checked'
    else:
        world = ''
    return (users, groups, local, world)
Esempio n. 23
0
def _account_add(login_name, settings):
    """Add a KBasix user account.

       _account_add(login_name, settings)

    The 'settings' dictionary is stored in the user's profile. Its
    only requirement is to define the keys 'user_name' and 'login_name'
    (such that login_name = user_name.lower(), where login_name is
    the user login name handled by the KBasix user management module.
    Returns nothing.
    """
    (OK, status) = _check_args(locals())
    if not OK:
        raise AccountAddError(status)
    if 'user_name' not in settings:
        raise AccountAddError('Account settings must contain "user_name"')
    user_name = settings['user_name']
    if login_name != settings['login_name']:
        raise AccountAddError('Inconsistent "login_name" ("%s" and "%s")', \
                                  (login_name, settings['login_name']))
    # This 'user_name' is the only user input saved directly on disk and
    # hence the extra security checks (which should never be triggered).
    # Although the length of the variable is set by 'login_name_max_length',
    # it's very hard to believe a user name will be over 100 characters
    # long, and furthermore such long names can cause issues with the
    # filename length limit on the file system (system-dependent, but
    # usually 255 characters).
    if not user_name.isalnum() or len(user_name) > 100:
        raise AccountAddError('Invalid "user_name": %s' % user_name)
    uid = str(_info(login_name)['uid'])
    # The 'world' subdirectory is created, and is in turned symlinked
    # from the overall 'www_dir_' which contains the world-exposed
    # user files. Note that the directory symlink name under 'www_dir_'
    # is the user's user name. Note that the user directories are
    # identified by uid.
    user_dir = os.path.join(users_root_dir_, uid)
    # This 'world_dir' (note the lack of underscore) is the physical
    # 'world' dir which lives under the user's directory within
    # 'users_root_dir_'. 'world_dir_' is the top-level http-accessible
    # directory (which lives within DocumentRoot) and within it the
    # the symlink 'world_link' is created (which uses user name as
    # as opposed to uid -- it is deleted when an account is deleted).
    world_dir = os.path.join(user_dir, 'world')
    world_link = os.path.join(www_dir_, user_name)
    rel_path = os.path.relpath(world_dir, www_dir_)
    profile = os.path.join(user_dir, uid + '.profile')
    preferences = os.path.join(user_dir, uid + '.prefs')
    try:
        if not os.path.isdir(user_dir):
            os.makedirs(user_dir)
            os.chmod(user_dir, 0700)
        if not os.path.isdir(world_dir):
            os.makedirs(world_dir)
            os.chmod(world_dir, 0700)
        if not os.path.islink(world_link):
            os.symlink(rel_path, world_link)
    except Exception as reason:
        raise AccountAddError(reason)
    try:
        _save_file({}, preferences, unlock=False)
        _save_file(settings, profile, unlock=False)
    except Exception as reason:
        raise AccountAddError(reason)
    return
Esempio n. 24
0
def _is_session(req, required, holdover=False):
    """Checks the validity of a session.

       session = _is_session(req, required, holdover=False)

    If 'first_time' is True the token's timestamp is checked
    against 'account_confirmation_timeout'. The 'required' boolean sets
    whether a login is needed to access the page, and if so performs a
    redirect to the login module 'login.py'. A bad token results
    in an empty dictionary returning, otherwise the following dictionary
    is returned:

    {'token': the token itself,
     'uid': the numeric uid (int),
     'start': the time when the token was created (float),
     'session': the unique session id,
     'client_ip': the client IP (or '0.0.0.256' if not set),
     'access': name of the accessible module or 'all'}
    """
    (OK, status) = _check_args(locals())
    if not OK:
        raise IsSessionError(status)
    from mod_python import apache, util
    # Using None actually puts 'None' as the token string on the page,
    # which is undesirable as it checks out True.
    no_session = {'token': '', 'login_name': '', 'uid': '', \
                      'start': '', 'session': '', 'client_ip': '', \
                      'access': ''}
    # We do nothing over unencrypted connections
    if not req.is_https():
        return no_session
    referrer = os.path.basename(req.canonical_filename)
    # A login redirect will not have 'file_tag', so if the user
    # is logged out while in the metadata editor and the logs in
    # the redirect will be to the file manager instead.
    if referrer == 'metaeditor.py':
        referrer = 'file_manager.py'
    if 'token' not in req.form:
        token = ''
    else:
        token = req.form['token']
    session = _check_token(token)
    if session:
        # The following exception may be triggered is a user is deleted
        # mid-session.
        try:
            session['login_name'] = _info(session['uid'])['login_name']
        except:
            session = {}
    # Terminate session if the client IP changes (and ip_set is True)
    if session and per_client_ip_token:
        if session['client_ip'] == '0.0.0.256':
            pass
        elif session['client_ip'] != \
                req.get_remote_host(apache.REMOTE_NOLOOKUP):
            session = {}
    if session and session['access'] not in ['all', referrer]:
        session = {}
    if session and per_request_token:
        # For extra security we create a token per request, except if we
        # explicitly require a holdover (needed when client-based windows
        # are generated e.g. a download query window).
        if holdover:
            return session
        else:
            token = _create_token(req, session['uid'])
            session = _check_token(token)
            if session:
                session['login_name'] = _info(session['uid'])['login_name']
    if not session and required:
        util.redirect(req, '../login.py/process?start&referrer=%s' % \
                          referrer)
    if session:
        return session
    else:
        return no_session
Esempio n. 25
0
def _account_add(login_name, settings):
    """Add a KBasix user account.

       _account_add(login_name, settings)

    The 'settings' dictionary is stored in the user's profile. Its
    only requirement is to define the keys 'user_name' and 'login_name'
    (such that login_name = user_name.lower(), where login_name is
    the user login name handled by the KBasix user management module.
    Returns nothing.
    """
    (OK, status) = _check_args(locals())
    if not OK:
        raise AccountAddError(status)
    if 'user_name' not in settings:
        raise AccountAddError('Account settings must contain "user_name"')
    user_name = settings['user_name']
    if login_name != settings['login_name']:
        raise AccountAddError('Inconsistent "login_name" ("%s" and "%s")', \
                                  (login_name, settings['login_name']))
    # This 'user_name' is the only user input saved directly on disk and
    # hence the extra security checks (which should never be triggered).
    # Although the length of the variable is set by 'login_name_max_length',
    # it's very hard to believe a user name will be over 100 characters
    # long, and furthermore such long names can cause issues with the
    # filename length limit on the file system (system-dependent, but
    # usually 255 characters).
    if not user_name.isalnum() or len(user_name) > 100:
        raise AccountAddError('Invalid "user_name": %s' % user_name)
    uid = str(_info(login_name)['uid'])
    # The 'world' subdirectory is created, and is in turned symlinked
    # from the overall 'www_dir_' which contains the world-exposed
    # user files. Note that the directory symlink name under 'www_dir_'
    # is the user's user name. Note that the user directories are
    # identified by uid.
    user_dir = os.path.join(users_root_dir_, uid)
    # This 'world_dir' (note the lack of underscore) is the physical
    # 'world' dir which lives under the user's directory within
    # 'users_root_dir_'. 'world_dir_' is the top-level http-accessible
    # directory (which lives within DocumentRoot) and within it the
    # the symlink 'world_link' is created (which uses user name as
    # as opposed to uid -- it is deleted when an account is deleted).
    world_dir = os.path.join(user_dir, 'world')
    world_link = os.path.join(www_dir_, user_name)
    rel_path = os.path.relpath(world_dir, www_dir_)
    profile = os.path.join(user_dir, uid + '.profile')
    preferences = os.path.join(user_dir, uid + '.prefs')
    try:
        if not os.path.isdir(user_dir):
            os.makedirs(user_dir)
            os.chmod(user_dir, 0700)
        if not os.path.isdir(world_dir):
            os.makedirs(world_dir)
            os.chmod(world_dir, 0700)
        if not os.path.islink(world_link):
            os.symlink(rel_path, world_link)
    except Exception as reason:
        raise AccountAddError(reason)
    try:
        _save_file({}, preferences, unlock=False)
        _save_file(settings, profile, unlock=False)
    except Exception as reason:
        raise AccountAddError(reason)
    return
Esempio n. 26
0
def _update(req, info):
    import logging
    import re
    import time
    import manage_users
    import manage_kbasix
    import aux
    if '*' in info['allowed_internal_logins'] or \
            info['login_name'] in info['allowed_internal_logins']:
        allow_internal = True
    else:
        allow_internal = False
    profile = {}
    account = {}
    account['first_name'] = req.form['first_name'].value
    account['last_name'] = req.form['last_name'].value
    profile['user_email'] = req.form['user_email'].value
    err = 0
    info['details'] = ''
    if not re.match(info['valid_email_'], profile['user_email'], re.I):
        err += 1
        info['details'] += 'Please input a valid email address.<br>'
    if 'user_password' in req.form:
        info['user_password'] = req.form['user_password'].value
    else:
        info['user_password'] = ''
    info['user_ldap_password'] = req.form['user_ldap_password'].value
    info['user_ldap_name'] = req.form['user_ldap_name'].value
    was_external = manage_users._info(info['login_name'])['auth_method']
    if 'use_ldap' in req.form and info['user_ldap_password']:
        if not info['user_ldap_name']:
            err += 1
            info['details'] += 'Please fill out all the LDAP credentials.<br>'
        else:
                from register import _check_ldap
                info['ldap_server'] = req.form['ldap_server'].value
                if info['ldap_server']:
                    (err, info['details']) = _check_ldap(info, err)
                else:
                    err += 1
                    info['details'] += 'Please specify the LDAP server.<br>'
                if not err:
                    account['password'] = '******'
                    account['auth_method'] = 'ldap'
                    account['auth_server'] = info['ldap_server']
                    account['user_auth_name'] = info['user_ldap_name']
                    if not was_external:
                        info['details'] += 'Authentication is no longer internal.<br>'
    elif info['user_password'] and allow_internal:
        from register import _check_password
        info['user_password_check'] = req.form['user_password_check'].value
        (err, info['details']) = _check_password(info, err)
        if not err:
            account['auth_method'] = ''
            account['auth_server'] = account['user_auth_name'] = ''
            account['password'] = info['user_password']
            if was_external:
                info['details'] += 'Authentication is now internal.<br>'
    else:
        info['details'] += 'Note: no authentication changes made.<br>'
    if not err:
        try:
            manage_kbasix._account_mod(info['login_name'], 'profile', profile)
            manage_users._mod(info['login_name'], account)
        except Exception as reason:
            raise UpdateError(reason)
    if err:
        info['class'] = 'fail'
        info['status_button_1'] = aux._go_back_button(req, info['token'])
        info['status_button_2'] = ''
        logging.debug('Failed profile update because "%s" (%s)' % \
                          (info['details'].replace('<br>',' '), info['login_name']))
    else:
        info['class'] = 'success'
        info['details'] += aux._fill_str(info['successful_update_blurb'], info)
        if info['access'] == 'profile.py':
            info['status_button_1'] = """
          <form action="../login.py/process?start" method="post">
            <input type="submit" value="Login" />
          </form><br>
""" % info
        else:
            info['status_button_1'] = ''
        info['status_button_2'] = ''
        logging.debug('Successful profile update (%s)' % info['login_name'])
    info['title'] = 'User Profile'
    return aux._fill_page(info['status_page_'], info)
Esempio n. 27
0
def _is_session(req, required, holdover=False):
    """Checks the validity of a session.

       session = _is_session(req, required, holdover=False)

    If 'first_time' is True the token's timestamp is checked
    against 'account_confirmation_timeout'. The 'required' boolean sets
    whether a login is needed to access the page, and if so performs a
    redirect to the login module 'login.py'. A bad token results
    in an empty dictionary returning, otherwise the following dictionary
    is returned:

    {'token': the token itself,
     'uid': the numeric uid (int),
     'start': the time when the token was created (float),
     'session': the unique session id,
     'client_ip': the client IP (or '0.0.0.256' if not set),
     'access': name of the accessible module or 'all'}
    """
    (OK, status) = _check_args(locals())
    if not OK:
        raise IsSessionError(status)
    from mod_python import apache, util
    # Using None actually puts 'None' as the token string on the page,
    # which is undesirable as it checks out True.
    no_session = {'token': '', 'login_name': '', 'uid': '', \
                      'start': '', 'session': '', 'client_ip': '', \
                      'access': ''}
    # We do nothing over unencrypted connections
    if not req.is_https():
        return no_session
    referrer = os.path.basename(req.canonical_filename)
    # A login redirect will not have 'file_tag', so if the user
    # is logged out while in the metadata editor and the logs in
    # the redirect will be to the file manager instead.
    if referrer == 'metaeditor.py':
        referrer = 'file_manager.py'
    if 'token' not in req.form:
        token = ''
    else:
        token = req.form['token']
    session = _check_token(token)
    if session:
        # The following exception may be triggered is a user is deleted
        # mid-session.
        try:
            session['login_name'] = _info(session['uid'])['login_name']
        except:
            session = {}
    # Terminate session if the client IP changes (and ip_set is True)
    if session and per_client_ip_token:
        if session['client_ip'] == '0.0.0.256':
            pass
        elif session['client_ip'] != \
                req.get_remote_host(apache.REMOTE_NOLOOKUP):
            session = {}
    if session and session['access'] not in ['all', referrer]:
        session = {}
    if session and per_request_token:
        # For extra security we create a token per request, except if we
        # explicitly require a holdover (needed when client-based windows
        # are generated e.g. a download query window).
        if holdover:
            return session
        else:
            token = _create_token(req, session['uid'])
            session = _check_token(token)
            if session:
                session['login_name'] = _info(session['uid'])['login_name']
    if not session and required:
        util.redirect(req, '../login.py/process?start&referrer=%s' % \
                          referrer)
    if session:
        return session
    else:
        return no_session