Example #1
0
def _parse_form_xfer(xfer, user_args, client_id, configuration):
    """Parse xfer request (i.e. copy, move or upload) file/dir entries from
    user_args.
    """
    _logger = configuration.logger
    files, rejected = [], []
    i = 0
    client_dir = client_id_dir(client_id)
    base_dir = os.path.abspath(os.path.join(configuration.user_home,
                                            client_dir)) + os.sep
    xfer_pattern = 'freeze_%s_%%d' % xfer
    for i in xrange(max_freeze_files):
        if user_args.has_key(xfer_pattern % i):
            source_path = user_args[xfer_pattern % i][-1].strip()
            source_path = os.path.normpath(source_path).lstrip(os.sep)
            _logger.debug('found %s entry: %s' % (xfer, source_path))
            if not source_path:
                continue
            try:
                valid_path(source_path)
            except Exception, exc:
                rejected.append('invalid path: %s (%s)' % (source_path,
                                                           exc))
                continue
            # IMPORTANT: path must be expanded to abs for proper chrooting
            abs_path = os.path.abspath(
                os.path.join(base_dir, source_path))
            # Prevent out-of-bounds, and restrict some greedy targets
            if not valid_user_path(configuration, abs_path, base_dir, True):
                _logger.error('found illegal directory traversal %s entry: %s'
                              % (xfer, source_path))
                rejected.append('invalid path: %s (%s)' %
                                (source_path, 'illegal path!'))
                continue
            elif os.path.exists(abs_path) and os.path.samefile(abs_path,
                                                               base_dir):
                _logger.warning('refusing archival of entire user home %s: %s'
                                % (xfer, source_path))
                rejected.append('invalid path: %s (%s)' %
                                (source_path, 'entire home not allowed!'))
                continue
            elif in_vgrid_share(configuration, abs_path) == source_path:
                _logger.warning(
                    'refusing archival of entire vgrid shared folder %s: %s' %
                    (xfer, source_path))
                rejected.append('invalid path: %s (%s)' %
                                (source_path, 'entire %s share not allowed!'
                                 % configuration.site_vgrid_label))
                continue

            # expand any dirs recursively
            if os.path.isdir(abs_path):
                for (root, dirnames, filenames) in os.walk(abs_path):
                    for subname in filenames:
                        abs_sub = os.path.join(root, subname)
                        sub_base = root.replace(abs_path, source_path)
                        sub_path = os.path.join(sub_base, subname)
                        files.append((abs_sub, sub_path.lstrip(os.sep)))
            else:
                files.append((abs_path, source_path.lstrip(os.sep)))
Example #2
0
            if verbose(flags):
                output_objects.append(
                    {'object_type': 'file', 'name': relative_path})

            # Prevent vgrid share copy which would create read-only dot dirs

            # Generally refuse handling symlinks including root vgrid shares
            if os.path.islink(abs_path):
                output_objects.append(
                    {'object_type': 'warning', 'text': """You're not allowed to
copy entire special folders like %s shared folders!"""
                     % configuration.site_vgrid_label})
                status = returnvalues.CLIENT_ERROR
                continue
            # Additionally refuse operations on inherited subvgrid share roots
            elif in_vgrid_share(configuration, abs_path) == relative_path:
                output_objects.append(
                    {'object_type': 'warning', 'text': """You're not allowed to
copy entire %s shared folders!""" % configuration.site_vgrid_label})
                status = returnvalues.CLIENT_ERROR
                continue
            elif os.path.realpath(abs_path) == os.path.realpath(base_dir):
                logger.error("%s: refusing copy home dir: %s" % (op_name,
                                                                 abs_path))
                output_objects.append(
                    {'object_type': 'warning', 'text':
                     "You're not allowed to copy your entire home directory!"
                     })
                status = returnvalues.CLIENT_ERROR
                continue
Example #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)
    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:
        WARNING_MSG = str(accepted)
        output_objects.append({'object_type': 'warning', 'text': WARNING_MSG})
        return (accepted, returnvalues.CLIENT_ERROR)

    # Convert accpeted values to string and filter out NON-set values

    accepted_joined_values = {
        key: ''.join(value)
        for (key, value) in accepted.iteritems() if len(value) > 0
    }

    action = accepted_joined_values['action']
    flags = accepted_joined_values['flags']
    path = accepted_joined_values['path']
    extension = accepted['extension'][-1].strip()

    logger.debug('%s from %s: %s' % (op_name, client_id, accepted))

    if not action in valid_actions:
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            'Invalid action "%s" (supported: %s)' %
            (action, ', '.join(valid_actions))
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    if action in post_actions:
        if not safe_handler(
                configuration,
                'post',
                op_name,
                client_id,
                get_csrf_limit(configuration),
                accepted,
        ):
            logger.info('checkpoint2')
            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
    abs_path = os.path.join(base_dir, path)

    title_entry = find_entry(output_objects, 'title')
    title_entry['text'] = 'IMAGEPREVIEW Management'
    output_objects.append({
        'object_type': 'header',
        'text': 'IMAGEPREVIEW Management'
    })
    status = returnvalues.ERROR

    vgrid_name = in_vgrid_share(configuration, abs_path)
    vgrid_owner = vgrid_is_owner(vgrid_name, client_id, configuration)

    status = returnvalues.OK
    if vgrid_name is None:
        status = returnvalues.ERROR
        ERROR_MSG = "No vgrid found for path: '%s'" % path
        output_objects.append({'object_type': 'error_text', 'text': ERROR_MSG})

    if status == returnvalues.OK:
        if action == 'list_settings':
            status = list_settings(configuration, abs_path, path,
                                   output_objects)
            logger.debug('list exit status: %s' % str(status))
        elif action == 'remove_setting':
            if vgrid_owner == False:
                status = returnvalues.ERROR
                ERROR_MSG = \
                    "Ownership of vgrid: '%s' required to change imagepreview settings" \
                    % vgrid_name
                output_objects.append({
                    'object_type': 'error_text',
                    'text': ERROR_MSG
                })
            else:
                status = remove_setting(configuration, abs_path, path,
                                        extension, output_objects)
            logger.debug('remove_setting exit status: %s' % str(status))
        elif action == 'get_setting':
            status = get_setting(configuration, abs_path, path, extension,
                                 output_objects)
            logger.debug('get_setting exit status: %s' % str(status))
        elif action == 'update_setting':
            if vgrid_owner == False:
                status = returnvalues.ERROR
                ERROR_MSG = \
                    "Ownership of vgrid: '%s' required to change imagepreview settings" \
                    % vgrid_name
                output_objects.append({
                    'object_type': 'error_text',
                    'text': ERROR_MSG
                })
            else:
                status = update_setting(
                    configuration,
                    base_dir,
                    abs_path,
                    path,
                    extension,
                    accepted_joined_values,
                    output_objects,
                )
                logger.debug('update_setting exit status: %s' % str(status))
        elif action == 'create_setting':

            if vgrid_owner == False:
                status = returnvalues.ERROR
                ERROR_MSG = \
                    "Ownership of vgrid: '%s' required to change imagepreview settings" \
                    % vgrid_name
                output_objects.append({
                    'object_type': 'error_text',
                    'text': ERROR_MSG
                })
            else:
                status = create_setting(
                    configuration,
                    client_id,
                    base_dir,
                    abs_path,
                    path,
                    extension,
                    accepted_joined_values,
                    output_objects,
                )
                status = returnvalues.OK
            logger.debug('create_setting exit status: %s' % str(status))
        elif action == 'reset_setting':
            if vgrid_owner == False:
                status = returnvalues.ERROR
                ERROR_MSG = \
                    "Ownership of vgrid: '%s' required to change imagepreview settings" \
                    % vgrid_name
                output_objects.append({
                    'object_type': 'error_text',
                    'text': ERROR_MSG
                })
            else:
                status = reset_settings(configuration, abs_path, path,
                                        output_objects, extension)
            logger.debug('reset exit status: %s' % str(status))
        elif action == 'get':

            status = get(configuration, base_dir, path, output_objects)
            logger.debug('get exit status: %s' % str(status))
        elif action == 'remove':
            if vgrid_owner == False:
                status = returnvalues.ERROR
                ERROR_MSG = \
                    "Ownership of vgrid: '%s' required to change imagepreview settings" \
                    % vgrid_name
                output_objects.append({
                    'object_type': 'error_text',
                    'text': ERROR_MSG
                })
            else:
                status = remove(configuration, base_dir, abs_path, path,
                                output_objects)
                logger.debug('remove exit status: %s' % str(status))
        elif action == 'clean':
            if vgrid_owner == False:
                status = returnvalues.ERROR
                ERROR_MSG = \
                    "Ownership of vgrid: '%s' required to change imagepreview settings" \
                    % vgrid_name
                output_objects.append({
                    'object_type': 'error_text',
                    'text': ERROR_MSG
                })
            else:
                status = clean(configuration, base_dir, abs_path, path,
                               output_objects)
                logger.debug('clean exit status: %s' % str(status))
        elif action == 'cleanrecursive':

            if vgrid_owner == False:
                status = returnvalues.ERROR
                ERROR_MSG = \
                    "Ownership of vgrid: '%s' required to change imagepreview settings" \
                    % vgrid_name
                output_objects.append({
                    'object_type': 'error_text',
                    'text': ERROR_MSG
                })
            else:
                status = clean(
                    configuration,
                    base_dir,
                    abs_path,
                    path,
                    output_objects,
                    recursive=True,
                )
                logger.debug('cleanrecursive exit status: %s' % str(status))
        elif action == 'refresh':

            if vgrid_owner == False:
                status = returnvalues.ERROR
                ERROR_MSG = \
                    "Ownership of vgrid: '%s' required to change imagepreview settings" \
                    % vgrid_name
                output_objects.append({
                    'object_type': 'error_text',
                    'text': ERROR_MSG
                })
            else:
                status = refresh(
                    configuration,
                    client_id,
                    base_dir,
                    abs_path,
                    path,
                    output_objects,
                )
                logger.debug('refresh exit status: %s' % str(status))
        else:
            ERROR_MSG = "action: '%s' _NOT_ implemented yet" \
                % str(action)
            output_objects.append({
                'object_type': 'error_text',
                'text': ERROR_MSG
            })

    logger.debug('output_objects: %s' % str(output_objects))
    logger.debug('status: %s' % str(status))
    return (output_objects, status)
Example #4
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)
    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'])
    dst = accepted['dst'][-1].lstrip(os.sep)
    pattern_list = accepted['src']
    current_dir = accepted['current_dir'][-1].lstrip(os.sep)

    # All paths are relative to current_dir

    pattern_list = [os.path.join(current_dir, i) for i in pattern_list]
    dst = os.path.join(current_dir, dst)

    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

    title_entry = find_entry(output_objects, 'title')
    title_entry['text'] = 'ZIP/TAR archiver'
    output_objects.append(
        {'object_type': 'header', 'text': 'ZIP/TAR archiver'})

    if verbose(flags):
        for flag in flags:
            output_objects.append({'object_type': 'text', 'text':
                                   '%s using flag: %s' % (op_name, flag)})

    if 'h' in flags:
        usage(output_objects)

    # IMPORTANT: path must be expanded to abs for proper chrooting
    abs_dir = os.path.abspath(os.path.join(base_dir,
                                           current_dir.lstrip(os.sep)))
    if not valid_user_path(configuration, abs_dir, base_dir, True):

        # out of bounds

        output_objects.append({'object_type': 'error_text', 'text':
                               "You're not allowed to work in %s!"
                               % current_dir})
        logger.warning('%s tried to %s restricted path %s ! (%s)'
                       % (client_id, op_name, abs_dir, current_dir))
        return (output_objects, returnvalues.CLIENT_ERROR)

    output_objects.append(
        {'object_type': 'text', 'text': "working in %s" % current_dir})

    # NOTE: dst already incorporates current_dir prefix here
    # IMPORTANT: path must be expanded to abs for proper chrooting
    abs_dest = os.path.abspath(os.path.join(base_dir, dst))
    logger.info('pack in %s' % abs_dest)

    # Don't use abs_path in output as it may expose underlying
    # fs layout.

    relative_dest = abs_dest.replace(base_dir, '')
    if not valid_user_path(configuration, abs_dest, base_dir, True):

        # out of bounds

        output_objects.append(
            {'object_type': 'error_text', 'text':
             "Invalid path! (%s expands to an illegal path)" % dst})
        logger.warning('%s tried to %s restricted path %s !(%s)'
                       % (client_id, op_name, abs_dest, dst))
        return (output_objects, returnvalues.CLIENT_ERROR)

    if not os.path.isdir(os.path.dirname(abs_dest)):
        output_objects.append({'object_type': 'error_text', 'text':
                               "No such destination directory: %s"
                               % os.path.dirname(relative_dest)})
        return (output_objects, returnvalues.CLIENT_ERROR)

    if not check_write_access(abs_dest, parent_dir=True):
        logger.warning('%s called without write access: %s' %
                       (op_name, abs_dest))
        output_objects.append(
            {'object_type': 'error_text', 'text':
             'cannot pack to "%s": inside a read-only location!' %
             relative_dest})
        return (output_objects, returnvalues.CLIENT_ERROR)

    status = returnvalues.OK

    for pattern in pattern_list:

        # Check directory traversal attempts before actual handling to avoid
        # leaking information about file system layout while allowing
        # consistent error messages

        unfiltered_match = glob.glob(base_dir + pattern)
        match = []
        for server_path in unfiltered_match:
            logger.debug("%s: inspecting: %s" % (op_name, server_path))
            # IMPORTANT: path must be expanded to abs for proper chrooting
            abs_path = os.path.abspath(server_path)
            if not valid_user_path(configuration, abs_path, base_dir, True):

                # out of bounds - save user warning for later to allow
                # partial match:
                # ../*/* is technically allowed to match own files.

                logger.warning('%s tried to %s restricted path %s ! (%s)'
                               % (client_id, op_name, abs_path, pattern))
                continue
            else:
                match.append(abs_path)

        # Now actually treat list of allowed matchings and notify if no
        # (allowed) match

        if not match:
            output_objects.append({'object_type': 'error_text', 'text':
                                   "%s: cannot pack '%s': no valid src paths"
                                   % (op_name, pattern)})
            status = returnvalues.CLIENT_ERROR
            continue

        for abs_path in match:
            relative_path = abs_path.replace(base_dir, '')
            # Generally refuse handling symlinks including root vgrid shares
            if os.path.islink(abs_path):
                output_objects.append(
                    {'object_type': 'warning', 'text': """You're not allowed to
pack entire special folders like %s shared folders!""" %
                     configuration.site_vgrid_label})
                status = returnvalues.CLIENT_ERROR
                continue
            # Additionally refuse operations on inherited subvgrid share roots
            elif in_vgrid_share(configuration, abs_path) == relative_path:
                output_objects.append(
                    {'object_type': 'warning', 'text': """You're not allowed to
pack entire %s shared folders!""" % configuration.site_vgrid_label})
                status = returnvalues.CLIENT_ERROR
                continue
            elif os.path.realpath(abs_path) == os.path.realpath(base_dir):
                logger.error("%s: refusing pack home dir: %s" % (op_name,
                                                                 abs_path))
                output_objects.append(
                    {'object_type': 'warning', 'text':
                     "You're not allowed to pack your entire home directory!"
                     })
                status = returnvalues.CLIENT_ERROR
                continue
            elif os.path.realpath(abs_dest) == os.path.realpath(abs_path):
                output_objects.append({'object_type': 'warning', 'text':
                                       'overlap in source and destination %s'
                                       % relative_dest})
                status = returnvalues.CLIENT_ERROR
                continue
            if verbose(flags):
                output_objects.append(
                    {'object_type': 'file', 'name': relative_path})

            (pack_status, msg) = pack_archive(configuration, client_id,
                                              relative_path, relative_dest)
            if not pack_status:
                output_objects.append({'object_type': 'error_text',
                                       'text': 'Error: %s' % msg})
                status = returnvalues.CLIENT_ERROR
                continue
            output_objects.append({'object_type': 'text', 'text':
                                   'Added %s to %s' % (relative_path,
                                                       relative_dest)})

    output_objects.append({'object_type': 'text', 'text':
                           'Packed archive of %s is now available in %s'
                           % (', '.join(pattern_list), relative_dest)})
    output_objects.append({'object_type': 'link', 'text': 'Download archive',
                           'destination': os.path.join('..', client_dir,
                                                       relative_dest)})

    return (output_objects, status)
Example #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)
    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)

    action = accepted['action'][-1]
    share_id = accepted['share_id'][-1]
    path = accepted['path'][-1]
    read_access = accepted['read_access'][-1].lower() in enabled_strings
    write_access = accepted['write_access'][-1].lower() in enabled_strings
    expire = accepted['expire'][-1]
    # Merge and split invite to make sure 'a@b, c@d' entries are handled
    invite_list = ','.join(accepted['invite']).split(',')
    invite_list = [i for i in invite_list if i]
    invite_msg = accepted['msg']

    title_entry = find_entry(output_objects, 'title')
    title_entry['text'] = 'Share Link'

    # jquery support for tablesorter and confirmation on delete/redo:
    # table initially sorted by 5, 4 reversed (active first and in growing age)

    table_spec = {'table_id': 'sharelinkstable', 'sort_order': '[[5,1],[4,1]]'}
    (add_import, add_init, add_ready) = man_base_js(configuration,
                                                    [table_spec],
                                                    {'width': 600})
    title_entry['script']['advanced'] += add_import
    title_entry['script']['init'] += add_init
    title_entry['script']['ready'] += add_ready
    output_objects.append({
        'object_type': 'html_form',
        'text': man_base_html(configuration)
    })

    header_entry = {'object_type': 'header', 'text': 'Manage share links'}
    output_objects.append(header_entry)

    if not configuration.site_enable_sharelinks:
        output_objects.append({
            'object_type':
            'text',
            'text':
            '''
Share links are disabled on this site.
Please contact the site admins %s if you think they should be enabled.
''' % configuration.admin_email
        })
        return (output_objects, returnvalues.OK)

    logger.info('sharelink %s from %s' % (action, client_id))
    logger.debug('sharelink from %s: %s' % (client_id, accepted))

    if not action in valid_actions:
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            'Invalid action "%s" (supported: %s)' %
            (action, ', '.join(valid_actions))
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    if action in post_actions:
        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)

    (load_status, share_map) = load_share_links(configuration, client_id)
    if not load_status:
        share_map = {}

    form_method = 'post'
    csrf_limit = get_csrf_limit(configuration)
    target_op = 'sharelink'
    csrf_token = make_csrf_token(configuration, form_method, target_op,
                                 client_id, csrf_limit)
    if action in get_actions:
        if action == "show":
            # Table columns to skip
            skip_list = ['owner', 'single_file', 'expire']
            sharelinks = []
            for (saved_id, share_dict) in share_map.items():
                share_item = build_sharelinkitem_object(
                    configuration, share_dict)
                js_name = 'delete%s' % hexlify(saved_id)
                helper = html_post_helper(
                    js_name, '%s.py' % target_op, {
                        'share_id': saved_id,
                        'action': 'delete',
                        csrf_field: csrf_token
                    })
                output_objects.append({
                    'object_type': 'html_form',
                    'text': helper
                })
                share_item['delsharelink'] = {
                    'object_type':
                    'link',
                    'destination':
                    "javascript: confirmDialog(%s, '%s');" %
                    (js_name, 'Really remove %s?' % saved_id),
                    'class':
                    'removelink iconspace',
                    'title':
                    'Remove share link %s' % saved_id,
                    'text':
                    ''
                }
                sharelinks.append(share_item)

            # Display share links and form to add new ones

            output_objects.append({
                'object_type': 'sectionheader',
                'text': 'Share Links'
            })
            output_objects.append({
                'object_type': 'table_pager',
                'entry_name': 'share links',
                'default_entries': default_pager_entries
            })
            output_objects.append({
                'object_type': 'sharelinks',
                'sharelinks': sharelinks,
                'skip_list': skip_list
            })

            output_objects.append({
                'object_type': 'html_form',
                'text': '<br/>'
            })
            output_objects.append({
                'object_type': 'sectionheader',
                'text': 'Create Share Link'
            })
            submit_button = '''<span>
    <input type=submit value="Create share link" />
    </span>'''
            sharelink_html = create_share_link_form(configuration, client_id,
                                                    'html', submit_button,
                                                    csrf_token)
            output_objects.append({
                'object_type': 'html_form',
                'text': sharelink_html
            })
        elif action == "edit":
            header_entry['text'] = 'Edit Share Link'
            share_dict = share_map.get(share_id, {})
            if not share_dict:
                output_objects.append({
                    'object_type':
                    'error_text',
                    'text':
                    'existing share link is required for edit'
                })
                return (output_objects, returnvalues.CLIENT_ERROR)

            output_objects.append({
                'object_type':
                'html_form',
                'text':
                '''
<p>
Here you can send invitations for your share link %(share_id)s to one or more
comma-separated recipients.
</p>
                                   ''' % share_dict
            })
            sharelinks = []
            share_item = build_sharelinkitem_object(configuration, share_dict)
            saved_id = share_item['share_id']
            js_name = 'delete%s' % hexlify(saved_id)
            helper = html_post_helper(js_name, '%s.py' % target_op, {
                'share_id': saved_id,
                'action': 'delete',
                csrf_field: csrf_token
            })
            output_objects.append({'object_type': 'html_form', 'text': helper})
            # Hide link to self
            del share_item['editsharelink']
            share_item['delsharelink'] = {
                'object_type':
                'link',
                'destination':
                "javascript: confirmDialog(%s, '%s');" %
                (js_name, 'Really remove %s?' % saved_id),
                'class':
                'removelink iconspace',
                'title':
                'Remove share link %s' % saved_id,
                'text':
                ''
            }
            sharelinks.append(share_item)
            output_objects.append({
                'object_type': 'sharelinks',
                'sharelinks': sharelinks
            })
            submit_button = '''<span>
    <input type=submit value="Send invitation(s)" />
    </span>'''
            sharelink_html = invite_share_link_form(configuration, client_id,
                                                    share_dict, 'html',
                                                    submit_button, csrf_token)
            output_objects.append({
                'object_type': 'html_form',
                'text': sharelink_html
            })
            output_objects.append({
                'object_type': 'link',
                'destination': 'sharelink.py',
                'text': 'Return to share link overview'
            })

        return (output_objects, returnvalues.OK)
    elif action in post_actions:
        share_dict = share_map.get(share_id, {})
        if not share_dict and action != 'create':
            logger.warning('%s tried to %s missing or not owned link %s!' %
                           (client_id, action, share_id))
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                '%s requires existing share link' % action
            })
            return (output_objects, returnvalues.CLIENT_ERROR)

        share_path = share_dict.get('path', path)

        # 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

        rel_share_path = share_path.lstrip(os.sep)
        # IMPORTANT: path must be expanded to abs for proper chrooting
        abs_path = os.path.abspath(os.path.join(base_dir, rel_share_path))
        relative_path = abs_path.replace(base_dir, '')
        real_path = os.path.realpath(abs_path)
        single_file = os.path.isfile(real_path)
        vgrid_name = in_vgrid_share(configuration, abs_path)

        if action == 'delete':
            header_entry['text'] = 'Delete Share Link'
            (save_status, _) = delete_share_link(share_id, client_id,
                                                 configuration, share_map)
            if save_status and vgrid_name:
                logger.debug("del vgrid sharelink pointer %s" % share_id)
                (del_status,
                 del_msg) = vgrid_remove_sharelinks(configuration, vgrid_name,
                                                    [share_id], 'share_id')
                if not del_status:
                    logger.error("del vgrid sharelink pointer %s failed: %s" %
                                 (share_id, del_msg))
                    return (False, share_map)
            desc = "delete"
        elif action == "update":
            header_entry['text'] = 'Update Share Link'
            # Try to point replies to client_id email
            client_email = extract_field(client_id, 'email')
            if invite_list:
                invites = share_dict.get('invites', []) + invite_list
                invites_uniq = list(set([i for i in invites if i]))
                invites_uniq.sort()
                share_dict['invites'] = invites_uniq
                auto_msg = invite_share_link_message(configuration, client_id,
                                                     share_dict, 'html')
                msg = '\n'.join(invite_msg)
                # Now send request to all targets in turn
                threads = []
                for target in invite_list:
                    job_dict = {
                        'NOTIFY': [target.strip()],
                        'JOB_ID': 'NOJOBID',
                        'USER_CERT': client_id,
                        'EMAIL_SENDER': client_email
                    }

                    logger.debug('invite %s to %s' % (target, share_id))
                    threads.append(
                        notify_user_thread(
                            job_dict,
                            [auto_msg, msg],
                            'INVITESHARE',
                            logger,
                            '',
                            configuration,
                        ))

                # Try finishing delivery but do not block forever on one message
                notify_done = [False for _ in threads]
                for _ in range(3):
                    for i in range(len(invite_list)):
                        if not notify_done[i]:
                            logger.debug('check done %s' % invite_list[i])
                            notify = threads[i]
                            notify.join(3)
                            notify_done[i] = not notify.isAlive()
                notify_sent, notify_failed = [], []
                for i in range(len(invite_list)):
                    if notify_done[i]:
                        notify_sent.append(invite_list[i])
                    else:
                        notify_failed.append(invite_list[i])
                logger.debug('notify sent %s, failed %s' %
                             (notify_sent, notify_failed))
                if notify_failed:
                    output_objects.append({
                        'object_type':
                        'html_form',
                        'text':
                        '''
<p>Failed to send invitation to %s</p>''' % ', '.join(notify_failed)
                    })
                if notify_sent:
                    output_objects.append({
                        'object_type':
                        'html_form',
                        'text':
                        '''<p>Invitation sent to %s</p>
<textarea class="fillwidth padspace" rows="%d" readonly="readonly">
%s
%s
</textarea>
                                            ''' %
                        (', '.join(notify_sent),
                         (auto_msg + msg).count('\n') + 3, auto_msg, msg)
                    })
            if expire:
                share_dict['expire'] = expire
            (save_status, _) = update_share_link(share_dict, client_id,
                                                 configuration, share_map)
            desc = "update"
        elif action == "create":
            header_entry['text'] = 'Create Share Link'
            if not read_access and not write_access:
                output_objects.append({
                    'object_type':
                    'error_text',
                    'text':
                    'No access set - please select read, write or both'
                })
                return (output_objects, returnvalues.CLIENT_ERROR)
            # NOTE: check path here as relative_path is empty for path='/'
            if not path:
                output_objects.append({
                    'object_type': 'error_text',
                    'text': 'No path provided!'
                })
                return (output_objects, returnvalues.CLIENT_ERROR)
            # We refuse sharing of entire home for security reasons
            elif not valid_user_path(
                    configuration, abs_path, base_dir, allow_equal=False):
                logger.warning('%s tried to %s restricted path %s ! (%s)' %
                               (client_id, action, abs_path, path))
                output_objects.append({
                    'object_type':
                    'error_text',
                    'text':
                    '''Illegal path "%s":
you can only share your own data, and not your entire home direcory.''' % path
                })
                return (output_objects, returnvalues.CLIENT_ERROR)
            elif not os.path.exists(abs_path):
                output_objects.append({
                    'object_type':
                    'error_text',
                    'text':
                    'Provided path "%s" does not exist!' % path
                })
                return (output_objects, returnvalues.CLIENT_ERROR)
            # Refuse sharing of (mainly auth) dot dirs in root of user home
            elif real_path.startswith(os.path.join(base_dir, '.')):
                output_objects.append({
                    'object_type':
                    'error_text',
                    'text':
                    'Provided path "%s" cannot be shared for security reasons'
                    % path
                })
                return (output_objects, returnvalues.CLIENT_ERROR)
            elif single_file and write_access:
                output_objects.append({
                    'object_type':
                    'error_text',
                    'text':
                    '''Individual files
cannot be shared with write access - please share a directory with the file in
it or only share with read access.
                     '''
                })
                return (output_objects, returnvalues.CLIENT_ERROR)

            # We check if abs_path is in vgrid share, but do not worry about
            # private_base or public_base since they are only available to
            # owners, who can always share anyway.

            if vgrid_name is not None and \
                    not vgrid_is_owner(vgrid_name, client_id, configuration):
                # share is inside vgrid share so we must check that user is
                # permitted to create sharelinks there.
                (load_status, settings_dict) = vgrid_settings(vgrid_name,
                                                              configuration,
                                                              recursive=True,
                                                              as_dict=True)
                if not load_status:
                    # Probably owners just never saved settings, use defaults
                    settings_dict = {'vgrid_name': vgrid_name}
                allowed = settings_dict.get('create_sharelink', keyword_owners)
                if allowed != keyword_members:
                    output_objects.append({
                        'object_type': 'error_text',
                        'text': '''The settings
for the %(vgrid_name)s %(vgrid_label)s do not permit you to re-share
%(vgrid_label)s shared folders. Please contact the %(vgrid_name)s owners if you
think you should be allowed to do that.
''' % {
                            'vgrid_name': vgrid_name,
                            'vgrid_label': configuration.site_vgrid_label
                        }
                    })
                    return (output_objects, returnvalues.CLIENT_ERROR)

            access_list = []
            if read_access:
                access_list.append('read')
            if write_access:
                access_list.append('write')

            share_mode = '-'.join((access_list + ['only'])[:2])

            # TODO: more validity checks here

            if share_dict:
                desc = "update"
            else:
                desc = "create"

            # IMPORTANT: always use expanded path
            share_dict.update({
                'path': relative_path,
                'access': access_list,
                'expire': expire,
                'invites': invite_list,
                'single_file': single_file
            })
            attempts = 1
            generate_share_id = False
            if not share_id:
                attempts = 3
                generate_share_id = True
            for i in range(attempts):
                if generate_share_id:
                    share_id = generate_sharelink_id(configuration, share_mode)
                share_dict['share_id'] = share_id
                (save_status,
                 save_msg) = create_share_link(share_dict, client_id,
                                               configuration, share_map)
                if save_status:
                    logger.info('created sharelink: %s' % share_dict)
                    break
                else:
                    # ID Collision?
                    logger.warning('could not create sharelink: %s' % save_msg)
            if save_status and vgrid_name:
                logger.debug("add vgrid sharelink pointer %s" % share_id)
                (add_status,
                 add_msg) = vgrid_add_sharelinks(configuration, vgrid_name,
                                                 [share_dict])
                if not add_status:
                    logger.error(
                        "save vgrid sharelink pointer %s failed: %s " %
                        (share_id, add_msg))
                    return (False, share_map)
        else:
            output_objects.append({
                'object_type': 'error_text',
                'text': 'No such action %s' % action
            })
            return (output_objects, returnvalues.CLIENT_ERROR)

        if not save_status:
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                'Error in %s share link %s: ' % (desc, share_id) +
                'save updated share links failed!'
            })
            return (output_objects, returnvalues.CLIENT_ERROR)

        output_objects.append({
            'object_type':
            'text',
            'text':
            '%sd share link %s on %s .' %
            (desc.title(), share_id, relative_path)
        })
        if action in ['create', 'update']:
            sharelinks = []
            share_item = build_sharelinkitem_object(configuration, share_dict)
            saved_id = share_item['share_id']
            js_name = 'delete%s' % hexlify(saved_id)
            helper = html_post_helper(js_name, '%s.py' % target_op, {
                'share_id': saved_id,
                'action': 'delete',
                csrf_field: csrf_token
            })
            output_objects.append({'object_type': 'html_form', 'text': helper})
            share_item['delsharelink'] = {
                'object_type':
                'link',
                'destination':
                "javascript: confirmDialog(%s, '%s');" %
                (js_name, 'Really remove %s?' % saved_id),
                'class':
                'removelink iconspace',
                'title':
                'Remove share link %s' % saved_id,
                'text':
                ''
            }
            sharelinks.append(share_item)
            output_objects.append({
                'object_type': 'sharelinks',
                'sharelinks': sharelinks
            })
            if action == 'create':
                # NOTE: Leave editsharelink here for use in fileman overlay
                #del share_item['editsharelink']
                output_objects.append({
                    'object_type': 'html_form',
                    'text': '<br />'
                })
                submit_button = '''<span>
            <input type=submit value="Send invitation(s)" />
            </span>'''
                invite_html = invite_share_link_form(configuration, client_id,
                                                     share_dict, 'html',
                                                     submit_button, csrf_token)
                output_objects.append({
                    'object_type': 'html_form',
                    'text': invite_html
                })
    else:
        output_objects.append({
            'object_type': 'error_text',
            'text': 'Invalid share link action: %s' % action
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    output_objects.append({'object_type': 'html_form', 'text': '<br />'})
    output_objects.append({
        'object_type': 'link',
        'destination': 'sharelink.py',
        'text': 'Return to share link overview'
    })

    return (output_objects, returnvalues.OK)
Example #6
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)
    status = returnvalues.OK
    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]

    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

    status = returnvalues.OK

    abs_dest = base_dir + dst
    dst_list = glob.glob(abs_dest)
    if not dst_list:

        # New destination?

        if not glob.glob(os.path.dirname(abs_dest)):
            output_objects.append({
                'object_type': 'error_text',
                'text': 'Illegal dst path provided!'
            })
            return (output_objects, returnvalues.CLIENT_ERROR)
        else:
            dst_list = [abs_dest]

    # Use last match in case of multiple matches

    dest = dst_list[-1]
    if len(dst_list) > 1:
        output_objects.append({
            'object_type':
            'warning',
            'text':
            'dst (%s) matches multiple targets - using last: %s' % (dst, dest)
        })

    # IMPORTANT: path must be expanded to abs for proper chrooting
    abs_dest = os.path.abspath(dest)

    # Don't use abs_path in output as it may expose underlying
    # fs layout.

    relative_dest = abs_dest.replace(base_dir, '')
    if not valid_user_path(configuration, abs_dest, base_dir, True):
        logger.warning('%s tried to %s to restricted path %s ! (%s)' %
                       (client_id, op_name, abs_dest, dst))
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            "Invalid path! (%s expands to an illegal path)" % dst
        })
        return (output_objects, returnvalues.CLIENT_ERROR)
    if not check_write_access(abs_dest, parent_dir=True):
        logger.warning('%s called without write access: %s' %
                       (op_name, abs_dest))
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            'cannot move to "%s": inside a read-only location!' % relative_dest
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    for pattern in src_list:
        unfiltered_match = glob.glob(base_dir + pattern)
        match = []
        for server_path in unfiltered_match:
            # IMPORTANT: path must be expanded to abs for proper chrooting
            abs_path = os.path.abspath(server_path)
            if not valid_user_path(configuration, abs_path, base_dir, True):
                logger.warning('%s tried to %s restricted path %s ! (%s)' %
                               (client_id, op_name, abs_path, pattern))
                continue
            match.append(abs_path)

        # Now actually treat list of allowed matchings and notify if no
        # (allowed) match

        if not match:
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                '%s: no such file or directory! %s' % (op_name, pattern)
            })
            status = returnvalues.CLIENT_ERROR

        for abs_path in match:
            relative_path = abs_path.replace(base_dir, '')
            if verbose(flags):
                output_objects.append({
                    'object_type': 'file',
                    'name': relative_path
                })

            # Generally refuse handling symlinks including root vgrid shares
            if os.path.islink(abs_path):
                output_objects.append({
                    'object_type':
                    'warning',
                    'text':
                    """You're not allowed to
move entire special folders like %s shared folders!""" %
                    configuration.site_vgrid_label
                })
                status = returnvalues.CLIENT_ERROR
                continue
            # Additionally refuse operations on inherited subvgrid share roots
            elif in_vgrid_share(configuration, abs_path) == relative_path:
                output_objects.append({
                    'object_type':
                    'warning',
                    'text':
                    """You're not allowed to
move entire %s shared folders!""" % configuration.site_vgrid_label
                })
                status = returnvalues.CLIENT_ERROR
                continue
            elif os.path.realpath(abs_path) == os.path.realpath(base_dir):
                logger.error("%s: refusing move home dir: %s" %
                             (op_name, abs_path))
                output_objects.append({
                    'object_type':
                    'warning',
                    'text':
                    "You're not allowed to move your entire home directory!"
                })
                status = returnvalues.CLIENT_ERROR
                continue

            if not check_write_access(abs_path):
                logger.warning('%s called without write access: %s' %
                               (op_name, abs_path))
                output_objects.append({
                    'object_type':
                    'error_text',
                    'text':
                    'cannot move "%s": inside a read-only location!' % pattern
                })
                status = returnvalues.CLIENT_ERROR
                continue

            # If destination is a directory the src should be moved in there
            # Move with existing directory as target replaces the directory!

            abs_target = abs_dest
            if os.path.isdir(abs_target):
                if os.path.samefile(abs_target, abs_path):
                    output_objects.append({
                        'object_type':
                        'warning',
                        'text':
                        "Cannot move '%s' to a subdirectory of itself!" %
                        relative_path
                    })
                    status = returnvalues.CLIENT_ERROR
                    continue
                abs_target = os.path.join(abs_target,
                                          os.path.basename(abs_path))

            try:
                gdp_iolog(configuration, client_id, environ['REMOTE_ADDR'],
                          'moved', [relative_path, relative_dest])
                shutil.move(abs_path, abs_target)
                logger.info('%s %s %s done' % (op_name, abs_path, abs_target))
            except Exception, exc:
                if not isinstance(exc, GDPIOLogError):
                    gdp_iolog(configuration,
                              client_id,
                              environ['REMOTE_ADDR'],
                              'moved', [relative_path, relative_dest],
                              failed=True,
                              details=exc)
                output_objects.append({
                    'object_type':
                    'error_text',
                    'text':
                    "%s: '%s': %s" % (op_name, relative_path, exc)
                })
                logger.error("%s: failed on '%s': %s" %
                             (op_name, relative_path, exc))

                status = returnvalues.SYSTEM_ERROR
                continue
Example #7
0
File: ls.py Project: ucphhpc/migrid
def handle_dir(
    configuration,
    listing,
    dirname,
    dirname_with_dir,
    actual_dir,
    flags='',
):
    """handle a dir"""

    # Recursion can get here when called without explicit invisible files

    if invisible_path(dirname_with_dir):
        return
    special = ''
    extra_class = ''
    real_dir = os.path.realpath(actual_dir)
    abs_dir = os.path.abspath(actual_dir)
    # If we followed a symlink it is not a plain dir
    if real_dir != abs_dir:
        access_type = configuration.site_vgrid_label
        parent_dir = os.path.basename(os.path.dirname(actual_dir))
        # configuration.logger.debug("checking link %s type (%s)" % \
        #                           (dirname_with_dir, real_dir))
        # Separate vgrid special dirs from plain ones
        if in_vgrid_share(configuration, actual_dir) == dirname_with_dir:
            dir_type = 'shared files'
            extra_class = 'vgridshared'
        elif in_vgrid_readonly(configuration, actual_dir) == dirname_with_dir:
            access_type = 'read-only'
            dir_type = 'shared files'
            extra_class = 'vgridshared readonly'
        elif in_vgrid_pub_web(configuration, actual_dir) == \
                dirname_with_dir[len('public_base/'):]:
            dir_type = 'public web page'
            extra_class = 'vgridpublicweb'
        elif in_vgrid_priv_web(configuration, actual_dir) == \
                dirname_with_dir[len('private_base/'):]:
            dir_type = 'private web page'
            extra_class = 'vgridprivateweb'
        # NOTE: in_vgrid_X returns None on miss, so don't use replace directly
        elif (in_vgrid_store_res(configuration, actual_dir) or '').replace(
                os.sep, '_') == os.path.basename(dirname_with_dir):
            dir_type = 'storage resource files'
            if os.path.islink(actual_dir):
                extra_class = 'vgridstoreres'
        elif real_dir.startswith(configuration.seafile_mount):
            access_type = 'read-only'
            dir_type = 'Seafile library access'
            if os.path.islink(actual_dir):
                extra_class = 'seafilereadonly'
        elif real_dir.find(trash_destdir) != -1:
            access_type = 'recently deleted data'
            dir_type = 'sub'
        else:
            dir_type = 'sub'
        # TODO: improve this greedy matching here?
        # configuration.logger.debug("check real_dir %s vs %s" % (real_dir,
        #                                                        trash_destdir))
        if real_dir.endswith(trash_destdir):
            dir_type = ''
            extra_class = 'trashbin'
        special = ' - %s %s directory' % (access_type, dir_type)
    dir_obj = {
        'object_type': 'direntry',
        'type': 'directory',
        'name': dirname,
        'rel_path': dirname_with_dir,
        'rel_path_enc': quote(dirname_with_dir),
        'rel_dir_enc': quote(dirname_with_dir),
        # NOTE: dirname_with_dir is kept for backwards compliance
        'dirname_with_dir': dirname_with_dir,
        'flags': flags,
        'special': special,
        'extra_class': extra_class,
    }

    if long_list(flags):
        dir_obj['actual_dir'] = long_format(actual_dir)

    if file_info(flags):
        dir_obj['file_info'] = fileinfo_stat(actual_dir)

    listing.append(dir_obj)