Exemple #1
0
def refresh_share_creds(configuration, protocol, username,
                        share_modes=['read-write']):
    """Reload sharelink credentials for username (SHARE_ID) if they changed on
    disk. That is, add user entries in configuration.daemon_conf['shares'] for
    any corresponding active sharelinks.
    Removes all sharelink login entries if the sharelink is no longer active,
    too. The protocol argument specifies which auth files to use.
    Returns a tuple with the updated daemon_conf and the list of changed share
    IDs.
    NOTE: we limit share_modes to read-write sharelinks for now since we don't
    have guards in place to support read-only or write-only mode in daemons.
    NOTE: we further limit to directory sharelinks for chroot'ing.
    """
    # Must end in sep
    base_dir = configuration.user_home.rstrip(os.sep) + os.sep
    changed_shares = []
    conf = configuration.daemon_conf
    last_update = conf['time_stamp']
    logger = conf.get("logger", logging.getLogger())
    creds_lock = conf.get('creds_lock', None)
    if not protocol in ('sftp', 'davs', 'ftps', ):
        logger.error("invalid protocol: %s" % protocol)
        return (conf, changed_shares)
    if [kind for kind in share_modes if kind != 'read-write']:
        logger.error("invalid share_modes: %s" % share_modes)
        return (conf, changed_shares)
    if not possible_sharelink_id(configuration, username):
        # logger.debug("ruled out %s as a possible sharelink ID" % username)
        return (conf, changed_shares)

    try:
        (mode, _) = extract_mode_id(configuration, username)
    except ValueError, err:
        logger.error('refresh share creds called with invalid username %s: %s'
                     % (username, err))
        mode = 'INVALID-SHARELINK'
Exemple #2
0
def main(client_id, user_arguments_dict, environ=None):
    """Main function used by front end"""

    if environ is None:
        environ = os.environ

    (configuration, logger, output_objects, op_name) = \
        initialize_main_variables(client_id, op_header=False)
    logger.info('Extracting input in %s' % op_name)
    status = returnvalues.OK
    defaults = signature()[1]

    logger.info('Extracted input in %s: %s' %
                (op_name, user_arguments_dict.keys()))

    # All non-file fields must be validated
    validate_args = dict([(key, user_arguments_dict.get(key, val))
                          for (key, val) in user_arguments_dict.items()
                          if not key in manual_validation])
    # IMPORTANT: we must explicitly inlude CSRF token
    validate_args[csrf_field] = user_arguments_dict.get(csrf_field, [''])
    (validate_status, accepted) = validate_input(
        validate_args,
        defaults,
        output_objects,
        allow_rejects=False,
    )
    if not validate_status:
        logger.error('%s validation failed: %s (%s)' %
                     (op_name, validate_status, accepted))
        return (accepted, returnvalues.CLIENT_ERROR)

    logger.info('validated input in %s: %s' % (op_name, validate_args.keys()))

    action = accepted['action'][-1]
    current_dir = os.path.normpath(accepted['current_dir'][-1].lstrip(os.sep))
    flags = ''.join(accepted['flags'])
    share_id = accepted['share_id'][-1]
    output_format = accepted['output_format'][-1]

    if action != "status":
        if not safe_handler(configuration, 'post', op_name, client_id,
                            get_csrf_limit(configuration), accepted):
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                '''Only accepting
                CSRF-filtered POST requests to prevent unintended updates'''
            })
            return (output_objects, returnvalues.CLIENT_ERROR)

    reject_write = False
    uploaded = []
    header_item = {'object_type': 'header', 'text': ''}
    # Always include a files reply even if empty
    output_objects.append(header_item)
    output_objects.append({'object_type': 'uploadfiles', 'files': uploaded})

    # Either authenticated user client_id set or sharelink ID
    if client_id:
        user_id = client_id
        target_dir = client_id_dir(client_id)
        base_dir = configuration.user_home
        redirect_name = configuration.site_user_redirect
        redirect_path = redirect_name
        id_args = ''
        page_title = 'Upload to User Directory: %s' % action
        userstyle = True
        widgets = True
    elif share_id:
        try:
            (share_mode, _) = extract_mode_id(configuration, share_id)
        except ValueError, err:
            logger.error('%s called with invalid share_id %s: %s' %
                         (op_name, share_id, err))
            output_objects.append({
                'object_type': 'error_text',
                'text': 'Invalid sharelink ID: %s' % share_id
            })
            return (output_objects, returnvalues.CLIENT_ERROR)
        # TODO: load and check sharelink pickle (currently requires client_id)
        user_id = 'anonymous user through share ID %s' % share_id
        # NOTE: we must return uploaded reply so we delay read-only failure
        if share_mode == 'read-only':
            logger.error('%s called without write access: %s' %
                         (op_name, accepted))
            reject_write = True
        target_dir = os.path.join(share_mode, share_id)
        base_dir = configuration.sharelink_home
        redirect_name = 'share_redirect'
        redirect_path = os.path.join(redirect_name, share_id)
        id_args = 'share_id=%s;' % share_id
        page_title = 'Upload to Shared Directory: %s' % action
        userstyle = False
        widgets = False
Exemple #3
0
def main(client_id, user_arguments_dict):
    """Main function used by front end"""

    (configuration, logger, output_objects, op_name) = \
        initialize_main_variables(client_id, op_header=False,
                                  op_menu=client_id)
    client_dir = client_id_dir(client_id)
    status = returnvalues.OK
    defaults = signature()[1]
    (validate_status, accepted) = validate_input(
        user_arguments_dict,
        defaults,
        output_objects,
        allow_rejects=False,
        )
    if not validate_status:
        return (accepted, returnvalues.CLIENT_ERROR)

    flags = ''.join(accepted['flags'])
    patterns = accepted['path']
    current_dir = accepted['current_dir']
    share_id = accepted['share_id'][-1]

    if not safe_handler(configuration, 'post', op_name, client_id,
                        get_csrf_limit(configuration), accepted):
        output_objects.append(
            {'object_type': 'error_text', 'text': '''Only accepting
CSRF-filtered POST requests to prevent unintended updates'''
             })
        return (output_objects, returnvalues.CLIENT_ERROR)

    # Either authenticated user client_id set or sharelink ID
    if client_id:
        user_id = client_id
        target_dir = client_id_dir(client_id)
        base_dir = configuration.user_home
        id_query = ''
        page_title = 'Remove User Directory'
        userstyle = True
        widgets = True
    elif share_id:
        try:
            (share_mode, _) = extract_mode_id(configuration, share_id)
        except ValueError, err:
            logger.error('%s called with invalid share_id %s: %s' % \
                         (op_name, share_id, err))
            output_objects.append({'object_type': 'error_text', 'text'
                                   : 'Invalid sharelink ID: %s' % share_id})
            return (output_objects, returnvalues.CLIENT_ERROR)
        # TODO: load and check sharelink pickle (currently requires client_id)
        user_id = 'anonymous user through share ID %s' % share_id
        if share_mode == 'read-only':
            logger.error('%s called without write access: %s' % \
                         (op_name, accepted))
            output_objects.append({'object_type': 'error_text', 'text'
                                   : 'No write access!'})
            return (output_objects, returnvalues.CLIENT_ERROR)
        target_dir = os.path.join(share_mode, share_id)
        base_dir = configuration.sharelink_home
        id_query = '?share_id=%s' % share_id
        page_title = 'Remove Shared Directory'
        userstyle = False
        widgets = False
Exemple #4
0
def main(client_id, user_arguments_dict, environ=None):
    """Main function used by front end"""

    if environ is None:
        environ = os.environ

    (configuration, logger, output_objects, op_name) = \
        initialize_main_variables(client_id)
    client_dir = client_id_dir(client_id)
    defaults = signature()[1]
    (validate_status, accepted) = validate_input_and_cert(
        user_arguments_dict,
        defaults,
        output_objects,
        client_id,
        configuration,
        allow_rejects=False,
    )
    if not validate_status:
        return (accepted, returnvalues.CLIENT_ERROR)

    flags = ''.join(accepted['flags'])
    src_list = accepted['src']
    dst = accepted['dst'][-1]
    iosessionid = accepted['iosessionid'][-1]
    share_id = accepted['share_id'][-1]
    freeze_id = accepted['freeze_id'][-1]

    if not safe_handler(configuration, 'post', op_name, client_id,
                        get_csrf_limit(configuration), accepted):
        output_objects.append(
            {'object_type': 'error_text', 'text': '''Only accepting
CSRF-filtered POST requests to prevent unintended updates'''
             })
        return (output_objects, returnvalues.CLIENT_ERROR)

    # Please note that base_dir must end in slash to avoid access to other
    # user dirs when own name is a prefix of another user name

    base_dir = os.path.abspath(os.path.join(configuration.user_home,
                                            client_dir)) + os.sep

    # Special handling if used from a job (no client_id but iosessionid)
    if not client_id and iosessionid:
        base_dir = os.path.realpath(configuration.webserver_home
                                    + os.sep + iosessionid) + os.sep

    # Use selected base as source and destination dir by default
    src_base = dst_base = base_dir

    # Sharelink import if share_id is given - change to sharelink as src base
    if share_id:
        try:
            (share_mode, _) = extract_mode_id(configuration, share_id)
        except ValueError, err:
            logger.error('%s called with invalid share_id %s: %s' %
                         (op_name, share_id, err))
            output_objects.append(
                {'object_type': 'error_text', 'text':
                 'Invalid sharelink ID: %s' % share_id})
            return (output_objects, returnvalues.CLIENT_ERROR)
        # TODO: load and check sharelink pickle (currently requires client_id)
        if share_mode == 'write-only':
            logger.error('%s called import from write-only sharelink: %s'
                         % (op_name, accepted))
            output_objects.append(
                {'object_type': 'error_text', 'text':
                 'Sharelink %s is write-only!' % share_id})
            return (output_objects, returnvalues.CLIENT_ERROR)
        target_dir = os.path.join(share_mode, share_id)
        src_base = os.path.abspath(os.path.join(configuration.sharelink_home,
                                                target_dir)) + os.sep
        if os.path.isfile(os.path.realpath(src_base)):
            logger.error('%s called import on single file sharelink: %s'
                         % (op_name, share_id))
            output_objects.append(
                {'object_type': 'error_text', 'text': """Import is only
supported for directory sharelinks!"""})
            return (output_objects, returnvalues.CLIENT_ERROR)
        elif not os.path.isdir(src_base):
            logger.error('%s called import with non-existant sharelink: %s'
                         % (client_id, share_id))
            output_objects.append(
                {'object_type': 'error_text', 'text': 'No such sharelink: %s'
                 % share_id})
            return (output_objects, returnvalues.CLIENT_ERROR)
Exemple #5
0
def main(client_id, user_arguments_dict):
    """Main function used by front end"""

    (configuration, logger, output_objects, op_name) = \
        initialize_main_variables(client_id, op_header=False,
                                  op_menu=client_id)
    defaults = signature()[1]
    (validate_status, accepted) = validate_input(
        user_arguments_dict,
        defaults,
        output_objects,
        allow_rejects=False,
    )
    if not validate_status:
        return (accepted, returnvalues.CLIENT_ERROR)

    flags = ''.join(accepted['flags'])
    pattern_list = accepted['path']
    current_dir = accepted['current_dir'][-1].lstrip('/')
    share_id = accepted['share_id'][-1]
    show_dest = accepted['with_dest'][0].lower() == 'true'

    status = returnvalues.OK

    # NOTE: in contrast to 'ls' we never include write operations here
    read_mode, write_mode = True, False
    visibility_mods = '''
            .%(main_class)s .enable_write { display: none; }
            .%(main_class)s .disable_read { display: none; }
            .%(main_class)s .if_full { display: none; }
    '''
    # Either authenticated user client_id set or sharelink ID
    if client_id:
        user_id = client_id
        target_dir = client_id_dir(client_id)
        base_dir = configuration.user_home
        redirect_name = configuration.site_user_redirect
        redirect_path = redirect_name
        id_args = ''
        root_link_name = 'USER HOME'
        main_class = "user_expand"
        page_title = 'User Files - Path Expansion'
        userstyle = True
        widgets = True
    elif share_id:
        try:
            (share_mode, _) = extract_mode_id(configuration, share_id)
        except ValueError, err:
            logger.error('%s called with invalid share_id %s: %s' %
                         (op_name, share_id, err))
            output_objects.append({
                'object_type': 'error_text',
                'text': 'Invalid sharelink ID: %s' % share_id
            })
            return (output_objects, returnvalues.CLIENT_ERROR)
        # TODO: load and check sharelink pickle (currently requires client_id)
        # then include shared by %(owner)s on page header
        user_id = 'anonymous user through share ID %s' % share_id
        target_dir = os.path.join(share_mode, share_id)
        base_dir = configuration.sharelink_home
        redirect_name = 'share_redirect'
        redirect_path = os.path.join(redirect_name, share_id)
        id_args = 'share_id=%s;' % share_id
        root_link_name = '%s' % share_id
        main_class = "sharelink_expand"
        page_title = 'Shared Files - Path Expansion'
        userstyle = False
        widgets = False
Exemple #6
0
            logger.debug("found sid dir: %s" % sid_name)
            # Save full prefix of link path
            full_prefix = os.path.join(root, sid_name)
            # Make sure absolute/normalized but unexpanded path is inside base.
            # Only prevents path itself outside base - not illegal linking
            # outside base, which is checked later.
            path = os.path.abspath(path)
            if not path.startswith(full_prefix):
                logger.error("got path from %s outside sid base: %s" %
                             (client_ip, path))
                print INVALID_MARKER
                continue
            if is_sharelink:
                # Share links use Alias to map directly into sharelink_home
                # and with first char mapping into access mode sub-dir there.
                (access_dir, _) = extract_mode_id(configuration, sid_name)
                real_root = os.path.join(configuration.sharelink_home,
                                         access_dir) + os.sep
            else:
                # Session links are directly in webserver_home and they map
                # either into mig_system_files for empty jobs or into specific
                # user_home for real job input/output.
                real_root = configuration.webserver_home.rstrip(os.sep) + \
                    os.sep

            # NOTE: we cannot completely trust linked path to be safe,
            # so we first check full prefix on normalized path above to avoid
            # user escaping link base with e.g. SHAREID/../bla . Next we
            # carefully expand only the SID link part and update base safely.

            # We expand with readlink to only follow initial link and extract
Exemple #7
0
def refresh_shares(configuration, protocol, share_modes=['read-write']):
    """Refresh shares keys based on the sharelink state.
    Add user entries for all active sharelinks.
    Removes all the user entries for sharelinks no longer active.
    Returns a tuple with the daemon_conf and the list of changed sharelink IDs.
    NOTE: we limit share_modes to read-write sharelinks for now since we don't
    have guards in place to support read-only or write-only mode in daemons.
    NOTE: we further limit to directory sharelinks for chroot'ing.
    NOTE: Deprecated due to severe system load, use refresh_share_creds instead
    """
    # Must end in sep
    base_dir = configuration.user_home.rstrip(os.sep) + os.sep
    changed_shares = []
    conf = configuration.daemon_conf
    logger = conf.get("logger", logging.getLogger())
    creds_lock = conf.get('creds_lock', None)
    if creds_lock:
        creds_lock.acquire()
    old_usernames = [i.username for i in conf['shares']]
    if creds_lock:
        creds_lock.release()
    cur_usernames = []
    if not protocol in ('sftp', 'davs', 'ftps', ):
        logger.error("invalid protocol: %s" % protocol)
        return (conf, changed_shares)
    if [kind for kind in share_modes if kind != 'read-write']:
        logger.error("invalid share_modes: %s" % share_modes)
        return (conf, changed_shares)

    for link_name in os.listdir(configuration.sharelink_home):
        link_path = os.path.join(configuration.sharelink_home, link_name)
        try:
            link_dest = os.readlink(link_path)
            if not link_dest.startswith(base_dir):
                raise ValueError("Invalid base for share %s" % link_name)
        except Exception, err:
            logger.error("invalid share %s: %s" % (link_name, err))
            continue
        (mode, _) = extract_mode_id(configuration, link_name)
        if not mode in share_modes:
            logger.info("invalid share mode %s for %s" % (mode, link_name))
            continue
        share_dict = None
        if os.path.islink(link_path) and os.path.isdir(link_dest):
            share_id = link_name
            # NOTE: share link points inside user home of owner so extract here
            share_root = link_dest.replace(base_dir, '').lstrip(os.sep)
            # NOTE: just use share_id as password/digest for now
            share_pw_hash = generate_password_hash(configuration, share_id)
            share_pw_digest = generate_password_digest(
                configuration, dav_domain, share_id, share_id,
                configuration.site_digest_salt)
            # TODO: load pickle from user_settings of owner (from link_dest)?
            share_dict = {'share_id': share_id, 'share_root': share_root,
                          'share_pw_hash': share_pw_hash,
                          'share_pw_digest': share_pw_digest}

        # We only allow access to active shares
        if share_dict is not None and isinstance(share_dict, dict) and \
            share_dict.has_key('share_id') and \
                share_dict.has_key('share_root') and \
                share_dict.has_key('share_pw_hash') and \
                share_dict.has_key('share_pw_digest'):
            user_alias = share_id
            user_dir = share_dict['share_root']
            user_password = share_dict['share_pw_hash']
            user_digest = share_dict['share_pw_digest']
            logger.info("Adding login for share %s" % user_alias)
            add_share_object(configuration, user_alias, user_dir,
                             password=user_password,
                             digest=user_digest)
            cur_usernames.append(user_alias)
            changed_shares.append(user_alias)