Ejemplo n.º 1
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)
    output_objects.append(
        {'object_type': 'header', 'text': 'Public project links'})
    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)

    vgrid_public_base = configuration.vgrid_public_base
    linklist = []
    for public_vgrid_dir in os.listdir(vgrid_public_base):
        if os.path.exists(os.path.join(vgrid_public_base,
                                       public_vgrid_dir, 'index.html')):

            # public project listing is enabled, link to the vgrid public page

            new_link = {'object_type': 'link',
                        'text': public_vgrid_dir,
                        'destination': '%s/vgrid/%s/path/index.html'
                        % (get_site_base_url(configuration),
                            public_vgrid_dir)}
            linklist.append(new_link)
    output_objects.append({'object_type': 'linklist', 'links': linklist})

    return (output_objects, returnvalues.OK)
Ejemplo n.º 2
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_title=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)

    iosessionid = accepted['iosessionid'][-1]
    sandboxkey = accepted['sandboxkey'][-1]
    exe_name = accepted['exe_name'][-1]

    status = returnvalues.OK

    # Web format for cert access and no header for SID access
    if client_id:
        output_objects.append({
            'object_type': 'title',
            'text': 'SSS job activity checker'
        })
        output_objects.append({
            'object_type': 'header',
            'text': 'SSS job activity checker'
        })
    else:
        output_objects.append({'object_type': 'start'})

    # check that the job exists, iosessionid is ok (does symlink exist?)

    if iosessionid and os.path.islink(configuration.webserver_home +
                                      iosessionid):
        msg = 'jobactive'
    else:
        if sandboxkey and exe_name:
            (result, msg) = \
                     get_sandbox_exe_stop_command(configuration.sandbox_home,
                                                  sandboxkey, exe_name, logger)
            if result:
                msg = 'stop_command: %s' % msg
        else:
            msg = 'jobinactive'
        status = returnvalues.ERROR

    # Status code line followed by raw output
    if not client_id:
        output_objects.append({'object_type': 'script_status', 'text': ''})
        output_objects.append({
            'object_type': 'binary',
            'data': '%s' % status[0]
        })
    output_objects.append({'object_type': 'binary', 'data': msg})
    return (output_objects, status)
Ejemplo n.º 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=False)
    logger = configuration.logger
    logger.debug('oiddiscover: %s' % user_arguments_dict)
    output_objects.append({'object_type': 'header', 'text':
                           'OpenID Discovery for %s' %
                           configuration.short_title})
    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)

    # Force to raw file output unless something else is explicitly requested.
    # Relies on delay_format option in run_cgi_script_possibly_with_cert
    if not user_arguments_dict.get('output_format', []):
        user_arguments_dict['output_format'] = ['file']

    discovery_doc = generate_openid_discovery_doc(configuration)
    output_objects.append({'object_type': 'text', 'text':
                           'Advertising valid OpenID endpoints:'})
    # make sure we always have at least one output_format entry
    output_format = user_arguments_dict.get('output_format', []) + ['file']
    if output_format[0] == 'file':
        headers = [('Content-Type', 'application/xrds+xml'),
                   ('Content-Disposition', 'attachment; filename=oid.xrds'),
                   ('Content-Length', '%s' % len(discovery_doc))]
        start_entry = find_entry(output_objects, 'start')
        start_entry['headers'] = headers
        # output discovery_doc as raw xrds doc in any case
        output_objects.append({'object_type': 'file_output', 'lines':
                               [discovery_doc]})
    else:
        output_objects.append({'object_type': 'binary', 'data': discovery_doc})
    return (output_objects, returnvalues.OK)
Ejemplo n.º 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, 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,
        # NOTE: path cannot use wildcards here
        typecheck_overrides={},
    )
    if not validate_status:
        return (accepted, returnvalues.CLIENT_ERROR)

    flags = ''.join(accepted['flags'])
    patterns = accepted['path']
    current_dir = accepted['current_dir'][-1]
    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 = 'Create User Directory'
        userstyle = True
        widgets = True
    elif share_id:
        try:
            (share_mode, _) = extract_mode_id(configuration, share_id)
        except ValueError as 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 = 'Create Shared Directory'
        userstyle = False
        widgets = False
    else:
        logger.error('%s called without proper auth: %s' % (op_name, accepted))
        output_objects.append({
            'object_type': 'error_text',
            'text': 'Authentication is missing!'
        })
        return (output_objects, returnvalues.SYSTEM_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(base_dir, target_dir)) + os.sep

    title_entry = find_entry(output_objects, 'title')
    title_entry['text'] = page_title
    title_entry['skipwidgets'] = not widgets
    title_entry['skipuserstyle'] = not userstyle
    output_objects.append({'object_type': 'header', 'text': page_title})

    # Input validation assures target_dir can't escape base_dir
    if not os.path.isdir(base_dir):
        output_objects.append({
            'object_type': 'error_text',
            'text': 'Invalid client/sharelink id!'
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

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

    for pattern in patterns:

        # Check directory traversal attempts before actual handling to avoid
        # leaking information about file system layout while allowing
        # consistent error messages
        # NB: Globbing disabled on purpose here

        unfiltered_match = [base_dir + os.sep + current_dir + os.sep + 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):

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

                logger.warn('%s tried to %s %s restricted path! (%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: cannot create directory '%s': Permission denied" %
                (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
                })
            if not parents(flags) and os.path.exists(abs_path):
                output_objects.append({
                    'object_type': 'error_text',
                    'text': '%s: path exist!' % pattern
                })
                status = returnvalues.CLIENT_ERROR
                continue
            if not check_write_access(abs_path, parent_dir=True):
                logger.warning('%s called without write access: %s' %
                               (op_name, abs_path))
                output_objects.append({
                    'object_type':
                    'error_text',
                    'text':
                    'cannot create "%s": inside a read-only location!' %
                    pattern
                })
                status = returnvalues.CLIENT_ERROR
                continue
            try:
                gdp_iolog(configuration, client_id, environ['REMOTE_ADDR'],
                          'created', [relative_path])
                if parents(flags):
                    if not os.path.isdir(abs_path):
                        os.makedirs(abs_path)
                else:
                    os.mkdir(abs_path)
                logger.info('%s %s done' % (op_name, abs_path))
            except Exception as exc:
                if not isinstance(exc, GDPIOLogError):
                    gdp_iolog(configuration,
                              client_id,
                              environ['REMOTE_ADDR'],
                              'created', [relative_path],
                              failed=True,
                              details=exc)
                output_objects.append({
                    'object_type':
                    'error_text',
                    'text':
                    "%s: '%s' failed!" % (op_name, relative_path)
                })
                logger.error("%s: failed on '%s': %s" %
                             (op_name, relative_path, exc))

                status = returnvalues.SYSTEM_ERROR
                continue
            output_objects.append({
                'object_type':
                'text',
                'text':
                "created directory %s" % (relative_path)
            })
            if id_query:
                open_query = "%s;current_dir=%s" % (id_query, relative_path)
            else:
                open_query = "?current_dir=%s" % relative_path
            output_objects.append({
                'object_type': 'link',
                'destination': 'ls.py%s' % open_query,
                'text': 'Open %s' % relative_path
            })
            output_objects.append({'object_type': 'text', 'text': ''})

    output_objects.append({
        'object_type': 'link',
        'destination': 'ls.py%s' % id_query,
        'text': 'Return to files overview'
    })
    return (output_objects, status)
Ejemplo n.º 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=False)
    defaults = signature()[1]
    logger.debug('in extoidaction: %s' % user_arguments_dict)
    (validate_status, accepted) = validate_input(user_arguments_dict,
                                                 defaults,
                                                 output_objects,
                                                 allow_rejects=False)
    if not validate_status:
        return (accepted, returnvalues.CLIENT_ERROR)

    # Unfortunately OpenID does not use POST
    # 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)

    title_entry = find_entry(output_objects, 'title')
    title_entry[
        'text'] = '%s OpenID account sign up' % configuration.short_title
    title_entry['skipmenu'] = True
    output_objects.append({
        'object_type':
        'header',
        'text':
        '%s OpenID account sign up' % configuration.short_title
    })

    admin_email = configuration.admin_email
    smtp_server = configuration.smtp_server
    user_pending = os.path.abspath(configuration.user_pending)

    # force name to capitalized form (henrik karlsen -> Henrik Karlsen)

    id_url = os.environ['REMOTE_USER'].strip()
    openid_prefix = configuration.user_ext_oid_provider.rstrip('/') + '/'
    raw_login = id_url.replace(openid_prefix, '')
    full_name = accepted['openid.sreg.full_name'][-1].strip().title()
    country = accepted['openid.sreg.country'][-1].strip().upper()
    state = accepted['state'][-1].strip().title()
    organization = accepted['openid.sreg.organization'][-1].strip()
    organizational_unit = accepted['openid.sreg.organizational_unit'][
        -1].strip()
    locality = accepted['openid.sreg.locality'][-1].strip()

    # lower case email address

    email = accepted['openid.sreg.email'][-1].strip().lower()
    password = accepted['password'][-1]
    #verifypassword = accepted['verifypassword'][-1]

    # keep comment to a single line

    comment = accepted['comment'][-1].replace('\n', '   ')

    # single quotes break command line format - remove

    comment = comment.replace("'", ' ')
    accept_terms = (accepted['accept_terms'][-1].strip().lower()
                    in ('1', 'o', 'y', 't', 'on', 'yes', 'true'))

    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)

    if not accept_terms:
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            'You must accept the terms of use in sign up!'
        })
        output_objects.append({
            'object_type': 'link',
            'destination': 'javascript:history.back();',
            'class': 'genericbutton',
            'text': "Try again"
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    user_dict = {
        'full_name': full_name,
        'organization': organization,
        'organizational_unit': organizational_unit,
        'locality': locality,
        'state': state,
        'country': country,
        'email': email,
        'password': password,
        'comment': comment,
        'expire': default_account_expire(configuration, 'oid'),
        'openid_names': [raw_login],
        'auth': ['extoid'],
    }
    fill_distinguished_name(user_dict)
    user_id = user_dict['distinguished_name']
    if configuration.user_openid_providers and configuration.user_openid_alias:
        user_dict['openid_names'].append(
            user_dict[configuration.user_openid_alias])

    req_path = None
    try:
        (os_fd, req_path) = tempfile.mkstemp(dir=user_pending)
        os.write(os_fd, dumps(user_dict))
        os.close(os_fd)
    except Exception as err:
        logger.error('Failed to write OpenID account request to %s: %s' %
                     (req_path, err))
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            '''Request could not be sent to site administrators. Please
contact them manually on %s if this error persists.''' % admin_email
        })
        return (output_objects, returnvalues.SYSTEM_ERROR)

    logger.info('Wrote OpenID account request to %s' % req_path)
    tmp_id = req_path.replace(user_pending, '')
    user_dict['tmp_id'] = tmp_id

    # TODO: remove cert generation or generate pw for it
    mig_user = os.environ.get('USER', 'mig')
    helper_commands = user_manage_commands(configuration, mig_user, req_path,
                                           user_id, user_dict, 'oid')
    user_dict.update(helper_commands)
    user_dict['site'] = configuration.short_title
    user_dict['vgrid_label'] = configuration.site_vgrid_label
    user_dict['vgridman_links'] = generate_https_urls(
        configuration, '%(auto_base)s/%(auto_bin)s/vgridman.py', {})
    email_header = '%s OpenID request for %s' % \
                   (configuration.short_title, full_name)
    email_msg = """
Received an OpenID account sign up with user data
 * Full Name: %(full_name)s
 * Organization: %(organization)s
 * State: %(state)s
 * Country: %(country)s
 * Email: %(email)s
 * Comment: %(comment)s
 * Expire: %(expire)s

Command to create user on %(site)s server:
%(command_user_create)s

Optional command to create matching certificate:
%(command_cert_create)s

Finally add the user
%(distinguished_name)s
to any relevant %(vgrid_label)ss using one of the management links:
%(vgridman_links)s


--- If user must be denied access or deleted at some point ---

Command to reject user account request on %(site)s server:
%(command_user_reject)s

Remove the user
%(distinguished_name)s
from any relevant %(vgrid_label)ss using one of the management links:
%(vgridman_links)s

Optional command to revoke any user certificates:
%(command_cert_revoke)s
You need to copy the resulting signed certificate revocation list (crl.pem)
to the web server(s) for the revocation to take effect.

Command to suspend user on %(site)s server:
%(command_user_suspend)s

Command to delete user again on %(site)s server:
%(command_user_delete)s

---

""" % user_dict

    logger.info('Sending email: to: %s, header: %s, msg: %s, smtp_server: %s' %
                (admin_email, email_header, email_msg, smtp_server))
    if not send_email(admin_email, email_header, email_msg, logger,
                      configuration):
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            '''An error occured trying to send the email requesting your new
user account. Please email the site administrators (%s) manually and include
the session ID: %s''' % (admin_email, tmp_id)
        })
        return (output_objects, returnvalues.SYSTEM_ERROR)

    output_objects.append({
        'object_type':
        'text',
        'text':
        """Request sent to site administrators: Your user account will
be created as soon as possible, so please be patient. Once handled an email
will be sent to the account you have specified ('%s') with further information.
In case of inquiries about this request, please email the site administrators
(%s) and include the session ID: %s""" %
        (email, configuration.admin_email, tmp_id)
    })
    return (output_objects, returnvalues.OK)
Ejemplo n.º 6
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)
    show = accepted['show'][-1].lower()
    search = accepted['search'][-1].lower()

    # Topic to generator-function mapping - add new topics here by adding an
    # anchor_name -> {'title': topic, 'generator': generator_function,
    #                 'arg': generator_args}
    # entry.

    default_args = (configuration, output_objects)
    all_docs = {
        'mrsl': {'title': 'Job description: mRSL', 'generator': mrsl_keywords,
                 'args': default_args},
        'resconf': {'title': 'Resource configuration', 'generator':
                    resconf_keywords, 'args': default_args},
        'outformats': {'title': 'Valid outputformats', 'generator':
                       valid_outputformats, 'args': default_args},
        'runtimeenv': {'title': 'Runtime Environments', 'generator':
                       runtime_environments, 'args': default_args},
        'credits': {'title': 'License and Acknowledgements', 'generator':
                    license_information, 'args': default_args},
    }

    output_objects.append({'object_type': 'header', 'text':
                           '%s On-demand Documentation' %
                           configuration.short_title})
    if not show:
        output_objects.append({'object_type': 'text',
                               'text': '''
This is the integrated help system for %s.
You can search for a documentation topic or select the particular
section directly.
Please note that the integrated help is rather limited to short overviews and
technical specifications.''' % configuration.short_title})

        output_objects.append({'object_type': 'text',
                               'text': '''
You can find more user friendly tutorials and examples on the
official site support pages:'''})
        output_objects.append({'object_type': 'link', 'destination':
                               configuration.site_external_doc,
                               'class': 'urllink iconspace', 'title':
                               'external documentation',
                               'text': 'external %s documentation' %
                               configuration.site_title,
                               'plain_text': configuration.site_external_doc})

    html = '<br />Filter (using *,? etc.)'
    html += "<form method='get' action='docs.py'>"
    html += "<input type='hidden' name='show' value='' />"
    html += "<input type='text' name='search' value='' />"
    html += "<input type='submit' value='Filter' />"
    html += '</form><br />'
    output_objects.append({'object_type': 'html_form', 'text': html})

    # Fall back to show all topics

    if not search and not show:
        search = '*'

    if search:

        # Pattern matching: select all topics that _contain_ search pattern
        # i.e. like re.search rather than re.match

        search_keys = []
        for (key, val) in all_docs.items():

            # Match any prefix and suffix.
            # No problem with extra '*'s since'***' also matches 'a')

            topic = val['title']
            if fnmatch.fnmatch(topic.lower(), '*' + search + '*'):
                search_keys.append(key)

        output_objects.append(
            {'object_type': 'header', 'text': 'Documentation topics:'})
        for key in search_keys:
            display_topic(output_objects, key, all_docs)
        if not search_keys:
            output_objects.append(
                {'object_type': 'text', 'text': 'No topics matching %s' % search})

    if show:

        # Pattern matching: select all topics that _contain_ search pattern
        # i.e. like re.search rather than re.match

        show_keys = []
        for (key, val) in all_docs.items():

            # Match any prefix and suffix.
            # No problem with extra '*'s since'***' also matches 'a')

            topic = val['title']
            logger.info("match show %s vs %s or %s" % (show, topic, key))
            if fnmatch.fnmatch(topic.lower(), '*' + show + '*') or \
                    show == key:
                logger.info("found show match for %s" % show)
                show_keys.append(key)

        for key in show_keys:
            logger.info("show doc for %s" % key)
            display_doc(output_objects, key, all_docs)
        if not show_keys:
            output_objects.append(
                {'object_type': 'text', 'text': 'No topics matching %s' % show})

    return (output_objects, returnvalues.OK)
Ejemplo n.º 7
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,
                                  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,
        # NOTE: path can use wildcards
        typecheck_overrides={'path': valid_path_pattern},
    )
    if not validate_status:
        return (accepted, returnvalues.CLIENT_ERROR)

    flags = ''.join(accepted['flags'])
    pattern_list = accepted['path']
    iosessionid = accepted['iosessionid'][-1]
    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 File'
        if force(flags):
            rm_helper = delete_path
        else:
            rm_helper = remove_path
        userstyle = True
        widgets = True
    elif share_id:
        try:
            (share_mode, _) = extract_mode_id(configuration, share_id)
        except ValueError as 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 File'
        rm_helper = delete_path
        userstyle = False
        widgets = False
    elif iosessionid.strip() and iosessionid.isalnum():
        user_id = iosessionid
        base_dir = configuration.webserver_home
        target_dir = iosessionid
        page_title = 'Remove Session File'
        rm_helper = delete_path
        userstyle = False
        widgets = False
    else:
        logger.error('%s called without proper auth: %s' % (op_name, accepted))
        output_objects.append({
            'object_type': 'error_text',
            'text': 'Authentication is missing!'
        })
        return (output_objects, returnvalues.SYSTEM_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(base_dir, target_dir)) + os.sep

    title_entry = find_entry(output_objects, 'title')
    title_entry['text'] = page_title
    title_entry['skipwidgets'] = not widgets
    title_entry['skipuserstyle'] = not userstyle
    output_objects.append({'object_type': 'header', 'text': page_title})

    logger.debug("%s: with paths: %s" % (op_name, pattern_list))

    # Input validation assures target_dir can't escape base_dir
    if not os.path.isdir(base_dir):
        output_objects.append({
            'object_type': 'error_text',
            'text': 'Invalid client/sharelink/session id!'
        })
        logger.warning('%s used %s with invalid base dir: %s' %
                       (user_id, op_name, base_dir))
        return (output_objects, returnvalues.CLIENT_ERROR)

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

    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:
            # 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
            match.append(abs_path)

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

        if not match:
            logger.warning("%s: no matching paths: %s" %
                           (op_name, pattern_list))
            output_objects.append({
                'object_type': 'file_not_found',
                'name': pattern
            })
            status = returnvalues.FILE_NOT_FOUND

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

            # Make it harder to accidentially delete too much - e.g. do not
            # delete VGrid files without explicit selection of subdir contents

            if abs_path == os.path.abspath(base_dir):
                logger.error("%s: refusing rm home dir: %s" %
                             (op_name, abs_path))
                output_objects.append({
                    'object_type':
                    'warning',
                    'text':
                    "You're not allowed to delete your entire home directory!"
                })
                status = returnvalues.CLIENT_ERROR
                continue
            # Generally refuse handling symlinks including root vgrid shares
            elif os.path.islink(abs_path):
                logger.error("%s: refusing rm link: %s" % (op_name, abs_path))
                output_objects.append({
                    'object_type':
                    'warning',
                    'text':
                    """
You're not allowed to delete entire special folders like %s shares and %s
""" % (configuration.site_vgrid_label, trash_linkname)
                })
                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
remove entire %s shared folders!""" % configuration.site_vgrid_label
                })
                status = returnvalues.CLIENT_ERROR
                continue
            elif os.path.isdir(abs_path) and not recursive(flags):
                logger.error("%s: non-recursive call on dir '%s'" %
                             (op_name, abs_path))
                output_objects.append({
                    'object_type':
                    'error_text',
                    'text':
                    "cannot remove '%s': is a direcory" % relative_path
                })
                status = returnvalues.CLIENT_ERROR
                continue
            trash_base = get_trash_location(configuration, abs_path)
            if not trash_base and not force(flags):
                logger.error("%s: no trash for dir '%s'" % (op_name, abs_path))
                output_objects.append({
                    'object_type':
                    'error_text',
                    'text':
                    "No trash enabled for '%s' - read-only?" % relative_path
                })
                status = returnvalues.CLIENT_ERROR
                continue
            try:
                if rm_helper == remove_path and \
                    os.path.commonprefix([real_path, trash_base]) \
                        == trash_base:
                    logger.warning("%s: already in trash: '%s'" %
                                   (op_name, real_path))
                    output_objects.append({
                        'object_type':
                        'error_text',
                        'text':
                        """
'%s' is already in trash - no action: use force flag to permanently delete""" %
                        relative_path
                    })
                    status = returnvalues.CLIENT_ERROR
                    continue
            except Exception as err:
                logger.error("%s: check trash failed: %s" % (op_name, err))
                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 remove "%s": inside a read-only location!' %
                    pattern
                })
                status = returnvalues.CLIENT_ERROR
                continue

            # TODO: limit delete in vgrid share trash to vgrid owners / conf?
            #       ... malicious members can still e.g. truncate all files.
            #       we could consider removing write bit on move to trash.
            # TODO: user setting to switch on/off trash?
            # TODO: add direct delete checkbox in fileman move to trash dialog?
            # TODO: add empty trash option for Trash?
            # TODO: user settings to define read-only and auto-expire in trash?
            # TODO: add trash support for sftp/ftps/webdavs?

            gdp_iolog_action = 'deleted'
            gdp_iolog_paths = [relative_path]
            if rm_helper == remove_path:
                gdp_iolog_action = 'moved'
                trash_base_path = \
                    get_trash_location(configuration, abs_path, True)
                trash_relative_path = \
                    trash_base_path.replace(configuration.user_home, '')
                trash_relative_path = \
                    trash_relative_path.replace(
                        configuration.vgrid_files_home, '')
                gdp_iolog_paths.append(trash_relative_path)
            try:
                gdp_iolog(configuration, client_id, environ['REMOTE_ADDR'],
                          gdp_iolog_action, gdp_iolog_paths)
                gdp_iolog_status = True
            except GDPIOLogError as exc:
                gdp_iolog_status = False
                rm_err = [str(exc)]
            rm_status = False
            if gdp_iolog_status:
                (rm_status, rm_err) = rm_helper(configuration, abs_path)
            if not rm_status or not gdp_iolog_status:
                if gdp_iolog_status:
                    gdp_iolog(configuration,
                              client_id,
                              environ['REMOTE_ADDR'],
                              gdp_iolog_action,
                              gdp_iolog_paths,
                              failed=True,
                              details=rm_err)
                logger.error("%s: failed on '%s': %s" %
                             (op_name, abs_path, ', '.join(rm_err)))
                output_objects.append({
                    'object_type':
                    'error_text',
                    'text':
                    "remove '%s' failed: %s" %
                    (relative_path, '. '.join(rm_err))
                })
                status = returnvalues.SYSTEM_ERROR
                continue
            logger.info("%s: successfully (re)moved %s" % (op_name, abs_path))
            output_objects.append({
                'object_type': 'text',
                'text': "removed %s" % (relative_path)
            })

    output_objects.append({
        'object_type': 'link',
        'destination': 'ls.py%s' % id_query,
        'text': 'Return to files overview'
    })
    return (output_objects, status)
Ejemplo n.º 8
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_title=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)

    remote_ip = str(os.getenv('REMOTE_ADDR'))

    unique_resource_name = accepted['unique_resource_name'][-1]
    exe = accepted['exe'][-1]
    cputime = int(accepted['cputime'][-1])
    nodecount = int(accepted['nodecount'][-1])
    localjobname = accepted['localjobname'][-1]
    sandboxkey = accepted['sandboxkey'][-1]
    execution_delay = int(accepted['execution_delay'][-1])
    exe_pgid = int(accepted['exe_pgid'][-1])

    status = returnvalues.OK

    if not configuration.site_enable_resources:
        output_objects.append({
            'object_type': 'error_text',
            'text': "Resources are not enabled on this site"
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    # No header and footer here
    output_objects.append({'object_type': 'start'})
    output_objects.append({'object_type': 'script_status', 'text': ''})

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

    base_dir = os.path.abspath(
        os.path.join(configuration.resource_home,
                     unique_resource_name)) + os.sep

    if not is_resource(unique_resource_name, configuration.resource_home):
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            "Failure: You must be an owner of '%s' to get the PGID!" %
            unique_resource_name
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    # is_resource incorporates unique_resource_name verification - no need to
    # specifically check for illegal directory traversal on that variable.

    (load_status, resource_conf) = \
        get_resource_configuration(configuration.resource_home,
                                   unique_resource_name, logger)
    if not load_status:
        logger.error("Invalid requestnewjob - no resouce_conf for: %s : %s" %
                     (unique_resource_name, resource_conf))
        output_objects.append({
            'object_type': 'error_text',
            'text': 'invalid request: no such resource!'
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    # Check that resource address matches request source to make DoS harder
    proxy_fqdn = resource_conf.get('FRONTENDPROXY', None)
    try:
        check_source_ip(remote_ip, unique_resource_name, proxy_fqdn)
    except ValueError as vae:
        logger.error("Invalid requestnewjob: %s (%s)" % (vae, accepted))
        output_objects.append({
            'object_type': 'error_text',
            'text': 'invalid request: %s' % vae
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    if resource_conf.get('SANDBOX', False):
        if sandboxkey == '':
            logger.error("Missing sandboxkey for sandbox resource: %s" %
                         unique_resource_name)
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                'sandbox must set sandboxkey in job requests!'
            })
            return (output_objects, returnvalues.CLIENT_ERROR)
        # resource is a sandbox and a sandboxkey was received
        if resource_conf['SANDBOXKEY'] != sandboxkey:
            logger.error("Incorrect sandboxkey for sandbox resource: %s : %s" %
                         (unique_resource_name, sandboxkey))
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                'sandbox provided an invalid sandboxkey!'
            })
            return (output_objects, returnvalues.CLIENT_ERROR)

    # TODO: add full session ID check here

    # If the server is under heavy usage, requestjobs might come too fast and
    # thereby make the usage even heavier. The resource request a new job again
    # because the requestjob process is finished, resulting in more load. A
    # resource should not be able to have more than one job request at a time.
    # A "jobrequest_pending" file in the resource's home directory means that
    # a requestnewjob is processed. The file is also deleted when a resource
    # is started. This locking by file is not good if the MiG server runs on a
    # NFS file system. The lock file contains a timestamp used to autoexpire
    # old locks if a job wasn't handed out within the requested cputime.

    lock_file = os.path.abspath(
        os.path.join(base_dir, 'jobrequest_pending.%s' % exe))
    filehandle = None
    now = time.time()
    try:
        lock_until = now + min(300.0, float(cputime))
    except Exception as exc:
        logger.error('invalid cputime in requestnewjob: %s (%s)' %
                     (cputime, exc))
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            'invalid cputime: %s - must be a number!' % cputime
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    try:
        filehandle = open(lock_file, 'r+')
    except IOError as ioe:
        output_objects.append({
            'object_type':
            'text',
            'text':
            'No jobrequest_pending.%s lock found - creating one' % exe
        })

    if filehandle:
        try:
            fcntl.flock(filehandle.fileno(), fcntl.LOCK_EX)
            filehandle.seek(0, 0)

            lock_content = filehandle.read()
            if lock_content:
                try:
                    expire_time = float(lock_content)
                except Exception:

                    # Old expire file - force lock to expire

                    expire_time = 0.0

                if now < expire_time:
                    logger.error('invalid cputime in requestnewjob: %s' %
                                 cputime)
                    output_objects.append({
                        'object_type':
                        'error_text',
                        'text':
                        'requestnewjob is locked until last requestnewjob for'
                        'this exe (%s) has returned.' % exe
                    })
                    return (output_objects, returnvalues.CLIENT_ERROR)
                else:
                    logger.info('requestnewjob found expired lock '
                                '(%.2f < %.2f) - allowing new request.' %
                                (now, expire_time))
            filehandle.seek(0, 0)
            filehandle.write('%.2f' % lock_until)
            filehandle.close()
        except IOError as ioe:
            logger.error('Could not get exclusive lock in requestnewjob')
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                '''Could not get exclusive lock. Your last job request for %s
has been received on the %s server. The job should be available shortly. If you
receive this message often, please increase the timeout for job requests.''' %
                (exe, configuration.short_title)
            })
            return (output_objects, returnvalues.CLIENT_ERROR)
    else:

        # create file

        try:
            filehandle = open(lock_file, 'w')
            fcntl.flock(filehandle.fileno(), fcntl.LOCK_EX)
            filehandle.seek(0, 0)
            filehandle.write('%.2f' % lock_until)
            filehandle.close()
        except IOError as ioe:
            logger.error('Failed to create jobrequest_pending lock: %s' % ioe)
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                'Failed to create jobrequest_pending lock!'
            })
            return (output_objects, returnvalues.ERROR)

    # Tell "grid_script" that the resource requests a job

    message = 'RESOURCEREQUEST %s %s %s %s %s %s %s\n' % (
        exe,
        unique_resource_name,
        cputime,
        nodecount,
        localjobname,
        execution_delay,
        exe_pgid,
    )
    if not send_message_to_grid_script(message, logger, configuration):
        logger.error('could not send resource request for %s to grid_script!' %
                     unique_resource_name)
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            'Fatal error: could not handle resource job request'
        })
        return (output_objects, returnvalues.ERROR)

    output_objects.append({
        'object_type':
        'text',
        'text':
        'REQUESTNEWJOB OK. The job will '
        'be sent to the resource: %s.%s %s %s (sandboxkey: %s)' %
        (unique_resource_name, exe, str(exe_pgid), os.getenv('REMOTE_ADDR'),
         sandboxkey)
    })
    return (output_objects, status)
Ejemplo n.º 9
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,
                                  op_menu=False)
    logger = configuration.logger
    logger.info('%s: args: %s' % (op_name, user_arguments_dict))
    prefilter_map = {}

    output_objects.append({'object_type': 'header',
                           'text': 'Automatic %s sign up'
                           % configuration.short_title})
    (auth_type, auth_flavor) = detect_client_auth(configuration, environ)
    identity = extract_client_id(configuration, environ, lookup_dn=False)
    if client_id and auth_type == AUTH_CERTIFICATE:
        if auth_flavor == AUTH_MIG_CERT:
            base_url = configuration.migserver_https_mig_cert_url
        elif auth_flavor == AUTH_EXT_CERT:
            base_url = configuration.migserver_https_ext_cert_url
        else:
            logger.warning('no matching sign up auth flavor %s' % auth_flavor)
            output_objects.append({'object_type': 'error_text', 'text':
                                   '%s sign up not supported' % auth_flavor})
            return (output_objects, returnvalues.SYSTEM_ERROR)
    elif identity and auth_type == AUTH_OPENID_V2:
        if auth_flavor == AUTH_MIG_OID:
            base_url = configuration.migserver_https_mig_oid_url
        elif auth_flavor == AUTH_EXT_OID:
            base_url = configuration.migserver_https_ext_oid_url
        else:
            logger.warning('no matching sign up auth flavor %s' % auth_flavor)
            output_objects.append({'object_type': 'error_text', 'text':
                                   '%s sign up not supported' % auth_flavor})
            return (output_objects, returnvalues.SYSTEM_ERROR)
        for name in ('openid.sreg.cn', 'openid.sreg.fullname',
                     'openid.sreg.full_name'):
            prefilter_map[name] = filter_commonname
    elif identity and auth_type == AUTH_OPENID_CONNECT:
        if auth_flavor == AUTH_MIG_OIDC:
            base_url = configuration.migserver_https_mig_oidc_url
        elif auth_flavor == AUTH_EXT_OIDC:
            base_url = configuration.migserver_https_ext_oidc_url
        else:
            logger.warning('no matching sign up auth flavor %s' % auth_flavor)
            output_objects.append({'object_type': 'error_text', 'text':
                                   '%s sign up not supported' % auth_flavor})
            return (output_objects, returnvalues.SYSTEM_ERROR)
        oidc_keys = signature(AUTH_OPENID_CONNECT)[1].keys()
        # NOTE: again we lowercase to avoid case sensitivity in validation
        for key in environ:
            low_key = key.replace('OIDC_CLAIM_', 'oidc.claim.').lower()
            if low_key in oidc_keys:
                user_arguments_dict[low_key] = [environ[key]]
    else:
        logger.error('autocreate without ID rejected for %s' % client_id)
        output_objects.append({'object_type': 'error_text',
                               'text': 'Missing user credentials'})
        return (output_objects, returnvalues.CLIENT_ERROR)
    defaults = signature(auth_type)[1]
    (validate_status, accepted) = validate_input(user_arguments_dict,
                                                 defaults, output_objects,
                                                 allow_rejects=False,
                                                 prefilter_map=prefilter_map)
    if not validate_status:
        logger.warning('%s from %s got invalid input: %s' %
                       (op_name, client_id, accepted))
        return (accepted, returnvalues.CLIENT_ERROR)

    logger.debug('Accepted arguments: %s' % accepted)
    # logger.debug('with environ: %s' % environ)

    admin_email = configuration.admin_email
    smtp_server = configuration.smtp_server
    (openid_names, oid_extras) = ([], {})
    tmp_id = 'tmp%s' % time.time()

    logger.info('Received autocreate from %s with ID %s' % (client_id, tmp_id))

    # Extract raw values

    if auth_type == AUTH_CERTIFICATE:
        uniq_id = accepted['cert_id'][-1].strip()
        raw_name = accepted['cert_name'][-1].strip()
        country = accepted['country'][-1].strip()
        state = accepted['state'][-1].strip()
        org = accepted['org'][-1].strip()
        org_unit = ''
        # NOTE: leave role and association alone here
        role = ''
        association = ''
        locality = ''
        timezone = ''
        email = accepted['email'][-1].strip()
    elif auth_type == AUTH_OPENID_V2:
        uniq_id = accepted['openid.sreg.nickname'][-1].strip() \
            or accepted['openid.sreg.short_id'][-1].strip()
        raw_name = accepted['openid.sreg.fullname'][-1].strip() \
            or accepted['openid.sreg.full_name'][-1].strip()
        country = accepted['openid.sreg.country'][-1].strip()
        state = accepted['openid.sreg.state'][-1].strip()
        org = accepted['openid.sreg.o'][-1].strip() \
            or accepted['openid.sreg.organization'][-1].strip()
        org_unit = accepted['openid.sreg.ou'][-1].strip() \
            or accepted['openid.sreg.organizational_unit'][-1].strip()

        # We may receive multiple roles and associations

        role = ','.join([i for i in accepted['openid.sreg.role'] if i])
        association = ','.join([i for i in
                                accepted['openid.sreg.association']
                                if i])
        locality = accepted['openid.sreg.locality'][-1].strip()
        timezone = accepted['openid.sreg.timezone'][-1].strip()

        # We may encounter results without an email, fall back to uniq_id then

        email = accepted['openid.sreg.email'][-1].strip() or uniq_id
    elif auth_type == AUTH_OPENID_CONNECT:
        uniq_id = accepted['oidc.claim.upn'][-1].strip() \
            or accepted['oidc.claim.sub'][-1].strip()
        raw_name = accepted['oidc.claim.fullname'][-1].strip()
        country = accepted['oidc.claim.country'][-1].strip()
        state = accepted['oidc.claim.state'][-1].strip()
        org = accepted['oidc.claim.o'][-1].strip() \
            or accepted['oidc.claim.organization'][-1].strip()
        org_unit = accepted['oidc.claim.ou'][-1].strip() \
            or accepted['oidc.claim.organizational_unit'][-1].strip()

        # We may receive multiple roles and associations

        role = ','.join([i for i in accepted['oidc.claim.role'] if i])
        association = ','.join([i for i in
                                accepted['oidc.claim.association']
                                if i])
        locality = accepted['oidc.claim.locality'][-1].strip()
        timezone = accepted['oidc.claim.timezone'][-1].strip()

        # We may encounter results without an email, fall back to uniq_id then

        email = accepted['oidc.claim.email'][-1].strip() or uniq_id

    # TODO: switch to canonical_user fra mig.shared.base instead?
    # Fix case of values:
    # force name to capitalized form (henrik karlsen -> Henrik Karlsen)
    # please note that we get utf8 coded bytes here and title() treats such
    # chars as word termination. Temporarily force to unicode.

    try:
        full_name = force_utf8(force_unicode(raw_name).title())
    except Exception:
        logger.warning('could not use unicode form to capitalize full name'
                       )
        full_name = raw_name.title()
    country = country.upper()
    state = state.upper()
    email = email.lower()
    accept_terms = (accepted['accept_terms'][-1].strip().lower() in
                    ('1', 'o', 'y', 't', 'on', 'yes', 'true'))

    if auth_type in (AUTH_OPENID_V2, AUTH_OPENID_CONNECT):

        # KU OpenID sign up does not deliver accept_terms so we implicitly
        # let it imply acceptance for now
        accept_terms = True

        # Remap some oid attributes if on KIT format with faculty in
        # organization and institute in organizational_unit. We can add them
        # as different fields as long as we make sure the x509 fields are
        # preserved.
        # Additionally in the special case with unknown institute (ou=ukendt)
        # we force organization to KU to align with cert policies.
        # We do that to allow autocreate updating existing cert users.

        if org_unit not in ('', 'NA'):
            org_unit = org_unit.upper()
            oid_extras['faculty'] = org
            oid_extras['institute'] = org_unit
            org = org_unit.upper()
            org_unit = 'NA'
            if org == 'UKENDT':
                org = 'KU'
                logger.info('unknown affilition, set organization to %s'
                            % org)

        # Stay on virtual host - extra useful while we test dual OpenID

        base_url = environ.get('REQUEST_URI', base_url).split('?')[0]
        backend = 'home.py'
        if configuration.site_enable_gdp:
            backend = 'gdpman.py'
        elif configuration.site_autolaunch_page:
            backend = os.path.basename(configuration.site_autolaunch_page)
        elif configuration.site_landing_page:
            backend = os.path.basename(configuration.site_landing_page)
        base_url = base_url.replace('autocreate.py', backend)

        raw_login = ''
        if auth_type == AUTH_OPENID_V2:
            # OpenID 2.0 provides user ID on URL format - only add plain ID
            for oid_provider in configuration.user_openid_providers:
                openid_prefix = oid_provider.rstrip('/') + '/'
                if identity.startswith(openid_prefix):
                    raw_login = identity.replace(openid_prefix, '')
                    break
        elif auth_type == AUTH_OPENID_CONNECT:
            raw_login = identity

        if raw_login and not raw_login in openid_names:
            openid_names.append(raw_login)
        if email and not email in openid_names:
            openid_names.append(email)
        # TODO: Add additional ext oid/oidc provider ID aliases here?

    # we should have the proxy file read...

    proxy_content = accepted['proxy_upload'][-1]

    # keep comment to a single line

    comment = accepted['comment'][-1].replace('\n', '   ')

    # single quotes break command line format - remove

    comment = comment.replace("'", ' ')

    # TODO: improve and enforce full authsig from extoid/extoidc provider
    authsig_list = accepted.get('authsig', [])
    # if len(authsig_list) != 1:
    #    logger.warning('%s from %s got invalid authsig: %s' %
    #                   (op_name, client_id, authsig_list))

    user_dict = {
        'short_id': uniq_id,
        'full_name': full_name,
        'organization': org,
        'organizational_unit': org_unit,
        'locality': locality,
        'state': state,
        'country': country,
        'email': email,
        'role': role,
        'association': association,
        'timezone': timezone,
        'password': '',
        'comment': 'Signed up through autocreate with %s' % auth_type,
        'openid_names': openid_names,
    }
    user_dict.update(oid_extras)

    # We must receive some ID from the provider otherwise we probably hit the
    # already logged in situation and must autologout first

    if not uniq_id and not email:
        if auth_type == AUTH_OPENID_V2 and identity and \
                accepted.get('openid.sreg.required', ''):
            logger.warning('autocreate forcing autologut for %s' % client_id)
            output_objects.append({'object_type': 'html_form',
                                   'text': '''<p class="spinner iconleftpad">
Auto log out first to avoid sign up problems ...
</p>'''})
            req_url = environ['SCRIPT_URI']
            html = \
                """
            <a id='autologout' href='%s'></a>
            <script type='text/javascript'>
                document.getElementById('autologout').click();
            </script>""" \
                % openid_autologout_url(configuration, identity,
                                        client_id, req_url, user_arguments_dict)
            output_objects.append({'object_type': 'html_form',
                                   'text': html})
        else:
            logger.warning('%s autocreate without ID refused for %s' %
                           (auth_type, client_id))

        return (output_objects, returnvalues.CLIENT_ERROR)

    # NOTE: Unfortunately external OpenID 2.0 redirect does not enforce POST
    # Extract helper environments from Apache to verify request authenticity

    redirector = environ.get('HTTP_REFERER', '')
    extoid_prefix = configuration.user_ext_oid_provider.replace('id/', '')
    # TODO: extend redirector check to match the full signup request?
    #       may not work with recent browser policy changes to limit referrer
    #       details on cross site requests.
    # NOTE: redirector check breaks for FF default policy so disabled again!
    if auth_flavor == AUTH_EXT_OID and redirector and \
            not redirector.startswith(extoid_prefix) and \
            not redirector.startswith(configuration.migserver_https_sid_url) \
            and not redirector.startswith(configuration.migserver_http_url) \
            and not redirector.startswith(get_site_base_url(configuration)):
        logger.error('stray %s autocreate rejected for %r (ref: %r)' %
                     (auth_flavor, client_id, redirector))
        output_objects.append({'object_type': 'error_text', 'text': '''Only
accepting authentic requests through %s OpenID 2.0''' %
                               configuration.user_ext_oid_title})
        return (output_objects, returnvalues.CLIENT_ERROR)
    elif auth_flavor != AUTH_EXT_OID and not safe_handler(
            configuration, 'post', op_name, client_id,
            get_csrf_limit(configuration), accepted):
        logger.error('unsafe %s autocreate rejected for %s' % (auth_flavor,
                                                               client_id))
        output_objects.append({'object_type': 'error_text', 'text': '''Only
accepting CSRF-filtered POST requests to prevent unintended updates'''})
        return (output_objects, returnvalues.CLIENT_ERROR)

    if auth_flavor == AUTH_EXT_CERT:
        ext_login_title = "%s certificate" % configuration.user_ext_cert_title
        personal_page_url = configuration.migserver_https_ext_cert_url
        # TODO: consider limiting expire to real cert expire if before default?
        user_dict['expire'] = default_account_expire(configuration,
                                                     AUTH_CERTIFICATE)
        try:
            distinguished_name_to_user(uniq_id)
            user_dict['distinguished_name'] = uniq_id
        except:
            logger.error('%s autocreate with bad DN refused for %s' %
                         (auth_flavor, client_id))
            output_objects.append({'object_type': 'error_text',
                                   'text': '''Illegal Distinguished name:
Please note that the distinguished name must be a valid certificate DN with
multiple "key=val" fields separated by "/".
'''})
            return (output_objects, returnvalues.CLIENT_ERROR)
    elif auth_flavor == AUTH_EXT_OID:
        ext_login_title = "%s login" % configuration.user_ext_oid_title
        personal_page_url = configuration.migserver_https_ext_oid_url
        user_dict['expire'] = default_account_expire(configuration,
                                                     AUTH_OPENID_V2)
        fill_distinguished_name(user_dict)
        uniq_id = user_dict['distinguished_name']
    elif auth_flavor == AUTH_EXT_OIDC:
        ext_login_title = "%s login" % configuration.user_ext_oid_title
        personal_page_url = configuration.migserver_https_ext_oidc_url
        user_dict['expire'] = default_account_expire(configuration,
                                                     AUTH_OPENID_CONNECT)
        fill_distinguished_name(user_dict)
        uniq_id = user_dict['distinguished_name']
    else:
        # Reject the migX sign up methods through this handler
        logger.error('%s autocreate not supported for %s - only ext auth' %
                     (auth_flavor, client_id))
        output_objects.append({'object_type': 'error_text', 'text': '''
Unsuported %s sign up method - you should sign up through the official
sign up wrappers or go through the dedicated web form for %s.''' %
                               (auth_type, auth_flavor)})
        return (output_objects, returnvalues.CLIENT_ERROR)

    # IMPORTANT: do NOT let a user create with ID different from client_id
    if auth_type == AUTH_CERTIFICATE and client_id != uniq_id:
        logger.error('refusing autocreate invalid user for %s: %s' %
                     (client_id, user_dict))
        output_objects.append({'object_type': 'error_text', 'text': '''Only
accepting create matching supplied ID!'''})
        return (output_objects, returnvalues.CLIENT_ERROR)

    if not accept_terms:
        output_objects.append({'object_type': 'error_text', 'text':
                               'You must accept the terms of use in sign up!'})
        output_objects.append(
            {'object_type': 'link', 'destination': 'javascript:history.back();',
             'class': 'genericbutton', 'text': "Try again"})
        return (output_objects, returnvalues.CLIENT_ERROR)

    # Save auth access method

    user_dict['auth'] = user_dict.get('auth', None)
    if not user_dict['auth']:
        user_dict['auth'] = []
    elif isinstance(user_dict['auth'], basestring):
        user_dict['auth'] = [user_dict['auth']]
    user_dict['auth'].append(auth_flavor)

    fill_helper = {'short_title': configuration.short_title,
                   'base_url': base_url, 'admin_email': admin_email,
                   'ext_login_title': ext_login_title,
                   'front_page_url': get_site_base_url(configuration),
                   'personal_page_url': personal_page_url}
    fill_helper.update(user_dict)

    # If server allows automatic addition of users with a CA validated cert
    # we create the user immediately and skip mail

    if auth_type == AUTH_CERTIFICATE and configuration.auto_add_cert_user \
            or auth_type == AUTH_OPENID_V2 and \
            configuration.auto_add_oid_user \
            or auth_type == AUTH_OPENID_CONNECT and \
            configuration.auto_add_oid_user:
        fill_user(user_dict)

        logger.info('create user: %s' % user_dict)

        # Now all user fields are set and we can begin adding the user

        db_path = os.path.join(configuration.mig_server_home,
                               user_db_filename)
        try:
            create_user(user_dict, configuration.config_file, db_path,
                        ask_renew=False, default_renew=True)
            if configuration.site_enable_griddk \
                    and accepted['proxy_upload'] != ['']:

                # save the file, display expiration date

                proxy_out = handle_proxy(proxy_content, uniq_id,
                                         configuration)
                output_objects.extend(proxy_out)
        except Exception as err:
            logger.error('create failed for %s: %s' % (uniq_id, err))
            output_objects.append({'object_type': 'error_text', 'text': '''
Could not create the user account for you:
Please report this problem to the site administrators (%(admin_email)s).'''
                                   % fill_helper})
            return (output_objects, returnvalues.SYSTEM_ERROR)

        logger.info('created user account for %s' % uniq_id)

        email_header = 'Welcome to %s' % configuration.short_title
        email_msg = """Hi and welcome to %(short_title)s!

Your account sign up succeeded and you can now log in to your account using
your %(ext_login_title)s from
%(front_page_url)s
There you'll also find further information about making the most of
%(short_title)s, including a user guide and answers to Frequently Asked
Questions, plus site status and support information.
You're welcome to contact us with questions or comments using the contact
details there and in the footer of your personal %(short_title)s pages.

Please note that by signing up and using %(short_title)s you also formally
accept the site Terms of Use, which you'll always find in the current form at
%(front_page_url)s/terms.html

All the best,
The %(short_title)s Admins
""" % fill_helper

        logger.info('Send email: to: %s, header: %s, msg: %s, smtp_server: %s'
                    % (email, email_header, email_msg, smtp_server))
        if not send_email(email, email_header, email_msg, logger,
                          configuration):
            output_objects.append({
                'object_type': 'error_text', 'text': """An error occured trying
to send your account welcome email. Please inform the site admins (%s) manually
and include the session ID: %s""" % (admin_email, tmp_id)})
            return (output_objects, returnvalues.SYSTEM_ERROR)

        logger.info('sent welcome email for %s to %s' % (uniq_id, email))

        output_objects.append({'object_type': 'html_form', 'text': """
<p>Creating your %(short_title)s user account and sending welcome email ... </p>
<p class='spinner iconleftpad'>
redirecting to your <a href='%(personal_page_url)s'> personal pages </a> in a
moment.
</p>
<script type='text/javascript'>
    setTimeout(function() {location.href='%(personal_page_url)s';}, 3000);
</script>
""" % fill_helper})
        return (output_objects, returnvalues.OK)

    else:
        logger.warning('autocreate disabled and refused for %s' % client_id)
        output_objects.append({
            'object_type': 'error_text', 'text': """Automatic user creation
disabled on this site. Please contact the site admins (%(admin_email)s) if you
think it should be enabled.
""" % fill_helper})
        return (output_objects, returnvalues.ERROR)
Ejemplo n.º 10
0
    o.out('Please use HTTPS with session id for authenticating job requests!')
    o.reply_and_exit(o.ERROR)

# TODO: add session ID check here

remote_ip = str(os.getenv('REMOTE_ADDR'))

fieldstorage = cgi.FieldStorage()
user_arguments_dict = fieldstorage_to_dict(fieldstorage)
defaults = signature()[1]
output_objects = []
# IMPORTANT: validate all input args before doing ANYTHING with them!
(validate_status, accepted) = validate_input(
    user_arguments_dict,
    defaults,
    output_objects,
    allow_rejects=False,
    # NOTE: path cannot use wildcards here
    typecheck_overrides={},
)
if not validate_status:
    logger.error("input validation for %s failed: %s" % (client_id, accepted))
    o.out('Invalid input arguments received!')
    o.reply_and_exit(o.ERROR)

exe = accepted['exe'][-1]
unique_resource_name = accepted['unique_resource_name'][-1]
cputime = accepted['cputime'][-1]
nodecount = accepted['nodecount'][-1]
localjobname = accepted['localjobname'][-1]
#sandboxkey = accepted['sandboxkey'][-1]
execution_delay = accepted['execution_delay'][-1]
Ejemplo n.º 11
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, op_menu=False)
    defaults = signature(configuration)[1]
    (validate_status, accepted) = validate_input(user_arguments_dict,
                                                 defaults,
                                                 output_objects,
                                                 allow_rejects=False)
    if not validate_status:
        return (accepted, returnvalues.CLIENT_ERROR)

    openid_error = ', '.join(accepted['modauthopenid.error'])
    openid_referrer = accepted['modauthopenid.referrer'][-1]
    # OpenID server errors may return here with redirect url set
    redirect_url = accepted['redirect_url'][-1]

    main_url = os.path.join(configuration.migserver_https_sid_url, 'public/')

    client_addr = environ.get('REMOTE_ADDR', '0.0.0.0')
    client_refer = environ.get('HTTP_REFERER', '')

    title_entry = find_entry(output_objects, 'title')
    title_entry[
        'text'] = '%s OpenID Response Handler' % configuration.short_title
    add_import = '''
<script type="text/javascript" src="/images/js/jquery.ajaxhelpers.js"></script>
    '''
    add_init = '''
        function delayed_redirect(redir_url, redirmsg_select) {
            setTimeout(function() {
                           $(redirmsg_select).fadeIn(4000);
                       }, 1000);
            setTimeout(function() {
                           location.href=redir_url;
                       }, 15000);
        }
    '''
    add_ready = '''
        var action = "login", oid_title, oid_url, tag_prefix;
        oid_title = "%s";
        oid_url = "%s";
        tag_prefix = "extoid_";
        check_oid_available(action, oid_title, oid_url, tag_prefix);
        oid_title = "%s";
        var oid_url = "%s";
        tag_prefix = "migoid_";
        check_oid_available(action, oid_title, oid_url, tag_prefix);
''' % (configuration.user_ext_oid_title, configuration.user_ext_oid_provider,
       configuration.user_mig_oid_title, configuration.user_mig_oid_provider)
    title_entry['script']['advanced'] += add_import
    title_entry['script']['init'] += add_init
    title_entry['script']['ready'] += add_ready
    title_entry['skipmenu'] = True
    # NOTE: keep empty header for narrow page layout
    header_entry = {'object_type': 'header', 'text': ''}
    output_objects.append(header_entry)

    logger.info('oidresponse for %r at %s from %r: %s' %
                (client_id, client_addr, openid_referrer, openid_error))
    html = ""
    add_action = '''
<p class="fadein_msg spinner iconleftpad iconspace hidden">
Redirecting to main page in a moment ...
</p>

<script type="text/javascript">
    delayed_redirect("%s", ".fadein_msg");
</script>
''' % main_url
    err_txt, report_txt = '', ''
    report_fail = """, so you cannot currently use it for authentication to %s.
<br />
Please report the problem to your OpenID identity provider if it persists.
""" % configuration.short_title
    if redirect_url:
        report_fail += """<br />The error happened on access to %s ."""  \
            % redirect_url

    if 'no_idp_found' in openid_error:
        err_txt += "OpenID server did not respond!"
        report_txt += """It appears the requested OpenID authentication service
is offline""" + report_fail
    elif 'canceled' in openid_error:
        # Sign up requests after login end here and may need autologout
        if openid_referrer.find('/autocreate.py') != -1:
            logger.info('oidresponse force autologut for %r at %s from %r' %
                        (client_id, client_addr, openid_referrer))
            logger.debug('oidresponse env: %s' % environ)
            err_txt += "OpenID sign up reached an inconsistent login state"
            report_txt += """Sign up to %s only works if you are <em>not</em>
already logged in to the OpenID service.
""" % configuration.short_title
            identity = environ.get('REMOTE_USER', None)
            # NOTE: for cgi-sid we don't have the oid username so use a dummy
            if not identity:
                if configuration.user_mig_oid_provider and \
                        openid_referrer.startswith(
                            configuration.migserver_https_mig_oid_url):
                    identity = configuration.user_mig_oid_provider
                    identity = os.path.join(identity, client_id)
                elif configuration.user_ext_oid_provider and \
                        openid_referrer.startswith(
                            configuration.migserver_https_ext_oid_url):
                    identity = configuration.user_ext_oid_provider
                    identity = os.path.join(identity, client_id)
                else:
                    logger.error('oidresponse from unexpected ref: %s' %
                                 client_refer)

            if identity:
                autologout_url = openid_basic_logout_url(
                    configuration, identity, main_url)
                add_action = '''
            <p class="fadein_msg spinner iconleftpad iconspace hidden">
            Redirecting through auto-logout to clean up for your next %s sign
            up attempt ...
            </p>
            <script type="text/javascript">
                delayed_redirect("%s", ".fadein_msg");
            </script>
            ''' % (configuration.short_title, autologout_url)

            logger.info('oidresponse for %r at %s from %r with action: %s' %
                        (client_id, client_addr, openid_referrer, add_action))
        else:
            err_txt += "OpenID login canceled!"
            report_txt += """You have to either enter your OpenID login and
accept that it is used for %s login or choose another login method.
""" % configuration.short_title

    else:
        err_txt += "OpenID server error!"
        report_txt += """It appears there's a problem with the requested
OpenID authentication service""" + report_fail

    html += """<h2>OpenID Service Reported an Error</h2>
<div class='errortext'>
%s (error code(s): %s)
</div>
<div class='warningtext'>
%s
</div>
""" % (err_txt, openid_error, report_txt)
    var_map = {
        'migoid_url': configuration.migserver_https_mig_oid_url,
        'migoid_title': configuration.user_mig_oid_title,
        'extoid_url': configuration.migserver_https_ext_oid_url,
        'extoid_title': configuration.user_ext_oid_title,
        'migcert_url': configuration.migserver_https_mig_cert_url,
        'migcert_title': configuration.user_mig_cert_title,
        'extcert_url': configuration.migserver_https_ext_cert_url,
        'extcert_title': configuration.user_ext_cert_title,
        'short_title': configuration.short_title
    }
    output_objects.append({'object_type': 'html_form', 'text': html % var_map})
    if add_action:
        output_objects.append({'object_type': 'html_form', 'text': add_action})

    return (output_objects, returnvalues.OK)
Ejemplo n.º 12
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,
                                  op_menu=client_id)
    defaults = signature()[1]
    (validate_status, accepted) = validate_input(
        user_arguments_dict,
        defaults,
        output_objects,
        allow_rejects=False,
        # NOTE: path can use wildcards, current_dir cannot
        typecheck_overrides={'path': valid_path_pattern},
    )
    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]

    status = returnvalues.OK

    read_mode, write_mode = True, True
    # 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_ls"
        page_title = 'User Files'
        userstyle = True
        widgets = True
        visibility_mods = '''
            .%(main_class)s .disable_read { display: none; }
            .%(main_class)s .disable_write { display: none; }
            '''
    elif share_id:
        try:
            (share_mode, _) = extract_mode_id(configuration, share_id)
        except ValueError as 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_ls"
        page_title = 'Shared Files'
        userstyle = False
        widgets = False
        # default to include file info
        if flags == '':
            flags += 'f'
        if share_mode == 'read-only':
            write_mode = False
            visibility_mods = '''
            .%(main_class)s .enable_write { display: none; }
            .%(main_class)s .disable_read { display: none; }
            '''
        elif share_mode == 'write-only':
            read_mode = False
            visibility_mods = '''
            .%(main_class)s .enable_read { display: none; }
            .%(main_class)s .disable_write { display: none; }
            '''
        else:
            visibility_mods = '''
            .%(main_class)s .disable_read { display: none; }
            .%(main_class)s .disable_write { display: none; }
            '''
    else:
        logger.error('%s called without proper auth: %s' % (op_name, accepted))
        output_objects.append({
            'object_type': 'error_text',
            'text': 'Authentication is missing!'
        })
        return (output_objects, returnvalues.SYSTEM_ERROR)

    visibility_toggle = '''
        <style>
        %s
        </style>
        ''' % (visibility_mods % {
        'main_class': main_class
    })

    # 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(base_dir, target_dir)) + os.sep

    if not os.path.isdir(base_dir):
        logger.error('%s called on missing base_dir: %s' % (op_name, base_dir))
        output_objects.append({
            'object_type': 'error_text',
            'text': 'No such %s!' % page_title.lower()
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    title_entry = find_entry(output_objects, 'title')
    title_entry['text'] = page_title
    title_entry['skipwidgets'] = not widgets
    title_entry['skipuserstyle'] = not userstyle
    user_settings = title_entry.get('user_settings', {})

    open_button_id = 'open_fancy_upload'
    form_method = 'post'
    csrf_limit = get_csrf_limit(configuration)
    fill_helpers = {
        'dest_dir': current_dir + os.sep,
        'share_id': share_id,
        'flags': flags,
        'tmp_flags': flags,
        'long_set': long_list(flags),
        'recursive_set': recursive(flags),
        'all_set': all(flags),
        'fancy_open': open_button_id,
        'fancy_dialog': fancy_upload_html(configuration),
        'form_method': form_method,
        'csrf_field': csrf_field,
        'csrf_limit': csrf_limit
    }
    target_op = 'uploadchunked'
    csrf_token = make_csrf_token(configuration, form_method, target_op,
                                 client_id, csrf_limit)
    fill_helpers.update({'target_op': target_op, 'csrf_token': csrf_token})
    (cf_import, cf_init, cf_ready) = confirm_js(configuration)
    (fu_import, fu_init,
     fu_ready) = fancy_upload_js(configuration,
                                 'function() { location.reload(); }', share_id,
                                 csrf_token)
    add_import = '''
%s
%s
    ''' % (cf_import, fu_import)
    add_init = '''
%s
%s
%s
%s
    ''' % (cf_init, fu_init, select_all_javascript(),
           selected_file_actions_javascript())
    add_ready = '''
%s
%s
    /* wrap openFancyUpload in function to avoid event data as argument */
    $("#%s").click(function() { openFancyUpload(); });
    $("#checkall_box").click(toggleChecked);
    ''' % (cf_ready, fu_ready, open_button_id)
    # TODO: can we update style inline to avoid explicit themed_styles?
    styles = themed_styles(
        configuration,
        advanced=['jquery.fileupload.css', 'jquery.fileupload-ui.css'],
        skin=['fileupload-ui.custom.css'],
        user_settings=user_settings)
    styles['advanced'] += '''
    %s
    ''' % visibility_toggle
    title_entry['style'] = styles
    title_entry['script']['advanced'] += add_import
    title_entry['script']['init'] += add_init
    title_entry['script']['ready'] += add_ready
    title_entry['script']['body'] = ' class="%s"' % main_class
    output_objects.append({'object_type': 'header', 'text': page_title})

    # TODO: move to output html handler
    output_objects.append({
        'object_type': 'html_form',
        'text': confirm_html(configuration)
    })

    # Shared URL helpers
    ls_url_template = 'ls.py?%scurrent_dir=%%(rel_dir_enc)s;flags=%s' % \
                      (id_args, flags)
    csrf_token = make_csrf_token(configuration, form_method, 'rm', client_id,
                                 csrf_limit)
    rm_url_template = 'rm.py?%spath=%%(rel_path_enc)s;%s=%s' % \
                      (id_args, csrf_field, csrf_token)
    rmdir_url_template = 'rm.py?%spath=%%(rel_path_enc)s;flags=r;%s=%s' % \
        (id_args, csrf_field, csrf_token)
    editor_url_template = 'editor.py?%spath=%%(rel_path_enc)s' % id_args
    redirect_url_template = '/%s/%%(rel_path_enc)s' % redirect_path

    location_pre_html = """
<div class='files'>
<table class='files'>
<tr class=title><td class=centertext>
Working directory:
</td></tr>
<tr><td class='centertext'>
"""
    output_objects.append({
        'object_type': 'html_form',
        'text': location_pre_html
    })
    # Use current_dir nav location links
    for pattern in pattern_list[:1]:
        links = []
        links.append({
            'object_type': 'link',
            'text': root_link_name,
            'destination': ls_url_template % {
                'rel_dir_enc': '.'
            }
        })
        prefix = ''
        parts = os.path.normpath(current_dir).split(os.sep)
        for i in parts:
            if i == ".":
                continue
            prefix = os.path.join(prefix, i)
            links.append({
                'object_type': 'link',
                'text': i,
                'destination': ls_url_template % {
                    'rel_dir_enc': quote(prefix)
                }
            })
        output_objects.append({
            'object_type': 'multilinkline',
            'links': links,
            'sep': ' %s ' % os.sep
        })
    location_post_html = """
</td></tr>
</table>
</div>
<br />
"""

    output_objects.append({
        'object_type': 'html_form',
        'text': location_post_html
    })

    more_html = """
<div class='files if_full'>
<form method='%(form_method)s' name='fileform' onSubmit='return selectedFilesAction();'>
<table class='files'>
<tr class=title><td class=centertext colspan=2>
Advanced file actions
</td></tr>
<tr><td>
Action on paths selected below
(please hold mouse cursor over button for a description):
</td>
<td class=centertext>
<input type='hidden' name='output_format' value='html' />
<input type='hidden' name='flags' value='v' />
<input type='submit' title='Show concatenated contents (cat)' onClick='document.pressed=this.value' value='cat' />
<input type='submit' onClick='document.pressed=this.value' value='head' title='Show first lines (head)' />
<input type='submit' onClick='document.pressed=this.value' value='tail' title='Show last lines (tail)' />
<input type='submit' onClick='document.pressed=this.value' value='wc' title='Count lines/words/chars (wc)' />
<input type='submit' onClick='document.pressed=this.value' value='stat' title='Show details (stat)' />
<input type='submit' onClick='document.pressed=this.value' value='touch' title='Update timestamp (touch)' />
<input type='submit' onClick='document.pressed=this.value' value='truncate' title='truncate! (truncate)' />
<input type='submit' onClick='document.pressed=this.value' value='rm' title='delete! (rm)' />
<input type='submit' onClick='document.pressed=this.value' value='rmdir' title='Remove directory (rmdir)' />
<input type='submit' onClick='document.pressed=this.value' value='submit' title='Submit file (submit)' />
</td></tr>
</table>    
</form>
</div>
""" % {
        'form_method': form_method
    }

    output_objects.append({'object_type': 'html_form', 'text': more_html})
    dir_listings = []
    output_objects.append({
        'object_type': 'dir_listings',
        'dir_listings': dir_listings,
        'flags': flags,
        'redirect_name': redirect_name,
        'redirect_path': redirect_path,
        'share_id': share_id,
        'ls_url_template': ls_url_template,
        'rm_url_template': rm_url_template,
        'rmdir_url_template': rmdir_url_template,
        'editor_url_template': editor_url_template,
        'redirect_url_template': redirect_url_template,
    })

    first_match = None
    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

        current_path = os.path.normpath(os.path.join(base_dir, current_dir))
        unfiltered_match = glob.glob(current_path + os.sep + 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)' %
                               (user_id, op_name, abs_path, pattern))
                continue
            match.append(abs_path)
            if not first_match:
                first_match = abs_path

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

        if not match:
            output_objects.append({
                'object_type': 'file_not_found',
                'name': pattern
            })
            status = returnvalues.FILE_NOT_FOUND

        # Never show any ls output in write-only mode (css hide is not enough!)
        if not read_mode:
            continue

        for abs_path in match:
            if abs_path + os.sep == base_dir:
                relative_path = '.'
            else:
                relative_path = abs_path.replace(base_dir, '')
            entries = []
            dir_listing = {
                'object_type': 'dir_listing',
                'relative_path': relative_path,
                'entries': entries,
                'flags': flags,
            }
            try:
                gdp_iolog(configuration, client_id, environ['REMOTE_ADDR'],
                          'accessed', [relative_path])
            except GDPIOLogError as 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))
                continue
            handle_ls(configuration, output_objects, entries, base_dir,
                      abs_path, flags, 0)
            dir_listings.append(dir_listing)

    output_objects.append({
        'object_type':
        'html_form',
        'text':
        """<br/>
    <div class='files disable_read'>
    <p class='info icon'>"""
    })
    # Shared message for text (e.g. user scripts) and html-format
    if not read_mode:
        # Please note that we use verbatim to get info icon right in html
        output_objects.append({
            'object_type':
            'verbatim',
            'text':
            """
This is a write-only share so you do not have access to see the files, only
upload data and create directories.
    """
        })
    output_objects.append({
        'object_type':
        'html_form',
        'text':
        """
    </p>
    </div>
    <div class='files enable_read'>
    <form method='get' action='ls.py'>
    <table class='files'>
    <tr class=title><td class=centertext>
    Filter paths (wildcards like * and ? are allowed)
    <input type='hidden' name='output_format' value='html' />
    <input type='hidden' name='flags' value='%(flags)s' />
    <input type='hidden' name='share_id' value='%(share_id)s' />
    <input name='current_dir' type='hidden' value='%(dest_dir)s' />
    <input type='text' name='path' value='' />
    <input type='submit' value='Filter' />
    </td></tr>
    </table>    
    </form>
    </div>
    """ % fill_helpers
    })

    # Short/long format buttons

    fill_helpers['tmp_flags'] = flags + 'l'
    htmlform = """
    <table class='files if_full'>
    <tr class=title><td class=centertext colspan=4>
    File view options
    </td></tr>
    <tr><td colspan=4><br /></td></tr>
    <tr class=title><td>Parameter</td><td>Setting</td><td>Enable</td><td>Disable</td></tr>
    <tr><td>Long format</td><td>
    %(long_set)s</td><td>
    <form method='get' action='ls.py'>
    <input type='hidden' name='output_format' value='html' />
    <input type='hidden' name='flags' value='%(tmp_flags)s' />
    <input type='hidden' name='share_id' value='%(share_id)s' />
    <input name='current_dir' type='hidden' value='%(dest_dir)s' />
    """ % fill_helpers

    for entry in pattern_list:
        htmlform += "<input type='hidden' name='path' value='%s' />" % entry
    fill_helpers['tmp_flags'] = flags.replace('l', '')
    htmlform += """
    <input type='submit' value='On' /><br />
    </form>
    </td><td>
    <form method='get' action='ls.py'>
    <input type='hidden' name='output_format' value='html' />
    <input type='hidden' name='flags' value='%(tmp_flags)s' />
    <input type='hidden' name='share_id' value='%(share_id)s' />
    <input name='current_dir' type='hidden' value='%(dest_dir)s' />
    """ % fill_helpers
    for entry in pattern_list:
        htmlform += "<input type='hidden' name='path' value='%s' />" % entry
    htmlform += """
    <input type='submit' value='Off' /><br />
    </form>
    </td></tr>
    """

    # Recursive output

    fill_helpers['tmp_flags'] = flags + 'r'
    htmlform += """
    <!-- Non-/recursive list buttons -->
    <tr><td>Recursion</td><td>
    %(recursive_set)s</td><td>""" % fill_helpers
    htmlform += """
    <form method='get' action='ls.py'>
    <input type='hidden' name='output_format' value='html' />
    <input type='hidden' name='flags' value='%(tmp_flags)s' />
    <input type='hidden' name='share_id' value='%(share_id)s' />
    <input name='current_dir' type='hidden' value='%(dest_dir)s' />
    """ % fill_helpers
    for entry in pattern_list:
        htmlform += "<input type='hidden' name='path' value='%s' />" % entry
    fill_helpers['tmp_flags'] = flags.replace('r', '')
    htmlform += """
    <input type='submit' value='On' /><br />
    </form>
    </td><td>
    <form method='get' action='ls.py'>
    <input type='hidden' name='output_format' value='html' />
    <input type='hidden' name='flags' value='%(tmp_flags)s' />
    <input type='hidden' name='share_id' value='%(share_id)s' />
    <input name='current_dir' type='hidden' value='%(dest_dir)s' />
    """ % fill_helpers

    for entry in pattern_list:
        htmlform += "<input type='hidden' name='path' value='%s' />"\
                    % entry
        htmlform += """
    <input type='submit' value='Off' /><br />
    </form>
    </td></tr>
    """

    htmlform += """
    <!-- Show dot files buttons -->
    <tr><td>Show hidden files</td><td>
    %(all_set)s</td><td>""" % fill_helpers
    fill_helpers['tmp_flags'] = flags + 'a'
    htmlform += """
    <form method='get' action='ls.py'>
    <input type='hidden' name='output_format' value='html' />
    <input type='hidden' name='flags' value='%(tmp_flags)s' />
    <input type='hidden' name='share_id' value='%(share_id)s' />
    <input name='current_dir' type='hidden' value='%(dest_dir)s' />
    """ % fill_helpers
    for entry in pattern_list:
        htmlform += "<input type='hidden' name='path' value='%s' />" % entry
    fill_helpers['tmp_flags'] = flags.replace('a', '')
    htmlform += """
    <input type='submit' value='On' /><br />
    </form>
    </td><td>
    <form method='get' action='ls.py'>
    <input type='hidden' name='output_format' value='html' />
    <input type='hidden' name='flags' value='%(tmp_flags)s' />
    <input type='hidden' name='share_id' value='%(share_id)s' />
    <input name='current_dir' type='hidden' value='%(dest_dir)s' />
    """ % fill_helpers
    for entry in pattern_list:
        htmlform += "<input type='hidden' name='path' value='%s' />" % entry
    htmlform += """
    <input type='submit' value='Off' /><br />
    </form>
    </td></tr>
    </table>
    """

    # show flag buttons after contents to limit clutter

    output_objects.append({'object_type': 'html_form', 'text': htmlform})

    # create additional action forms

    if first_match:
        htmlform = """
<br />
<div class='files disable_write'>
<p class='info icon'>
This is a read-only share so you do not have access to edit or add files, only
view data.
</p>
</div>
<table class='files enable_write if_full'>
<tr class=title><td class=centertext colspan=3>
Edit file
</td></tr>
<tr><td>
Fill in the path of a file to edit and press 'edit' to open that file in the<br />
online file editor. Alternatively a file can be selected for editing through<br />
the listing of personal files. 
</td><td colspan=2 class=righttext>
<form name='editor' method='get' action='editor.py'>
<input type='hidden' name='output_format' value='html' />
<input type='hidden' name='share_id' value='%(share_id)s' />
<input name='current_dir' type='hidden' value='%(dest_dir)s' />
<input type='text' name='path' size=50 value='' required />
<input type='submit' value='edit' />
</form>
</td></tr>
</table>
<br />""" % fill_helpers
        target_op = 'mkdir'
        csrf_token = make_csrf_token(configuration, form_method, target_op,
                                     client_id, csrf_limit)
        fill_helpers.update({'target_op': target_op, 'csrf_token': csrf_token})
        htmlform += """
<table class='files enable_write'>
<tr class=title><td class=centertext colspan=4>
Create directory
</td></tr>
<tr><td>
Name of new directory to be created in current directory (%(dest_dir)s)
</td><td class=righttext colspan=3>
<form method='%(form_method)s' action='%(target_op)s.py'>
<input type='hidden' name='%(csrf_field)s' value='%(csrf_token)s' />
<input type='hidden' name='share_id' value='%(share_id)s' />
<input name='current_dir' type='hidden' value='%(dest_dir)s' />
<input name='path' size=50 required />
<input type='submit' value='Create' name='mkdirbutton' />
</form>
</td></tr>
</table>
<br />
""" % fill_helpers
        target_op = 'textarea'
        csrf_token = make_csrf_token(configuration, form_method, target_op,
                                     client_id, csrf_limit)
        fill_helpers.update({'target_op': target_op, 'csrf_token': csrf_token})
        htmlform += """
<form enctype='multipart/form-data' method='%(form_method)s'
    action='%(target_op)s.py'>
<input type='hidden' name='%(csrf_field)s' value='%(csrf_token)s' />
<input type='hidden' name='share_id' value='%(share_id)s' />
<table class='files enable_write if_full'>
<tr class='title'><td class=centertext colspan=4>
Upload file
</td></tr>
<tr><td colspan=4>
Upload file to current directory (%(dest_dir)s)
</td></tr>
<tr class='if_full'><td colspan=2>
Extract package files (.zip, .tar.gz, .tar.bz2)
</td><td colspan=2>
<input type=checkbox name='extract_0' />
</td></tr>
<tr class='if_full'><td colspan=2>
Submit mRSL files (also .mRSL files included in packages)
</td><td colspan=2>
<input type=checkbox name='submitmrsl_0' />
</td></tr>
<tr><td>    
File to upload
</td><td class=righttext colspan=3>
<input name='fileupload_0_0_0' type='file'/>
</td></tr>
<tr><td>
Optional remote filename (extra useful in windows)
</td><td class=righttext colspan=3>
<input name='default_remotefilename_0' type='hidden' value='%(dest_dir)s'/>
<input name='remotefilename_0' type='text' size='50' value='%(dest_dir)s'/>
<input type='submit' value='Upload' name='sendfile'/>
</td></tr>
</table>
</form>
%(fancy_dialog)s
<table class='files enable_write'>
<tr class='title'><td class='centertext'>
Upload files efficiently (using chunking).
</td></tr>
<tr><td class='centertext'>
<button id='%(fancy_open)s'>Open Upload dialog</button>
</td></tr>
</table>
<script type='text/javascript' >
    setUploadDest('%(dest_dir)s');
</script>
    """ % fill_helpers
        output_objects.append({'object_type': 'html_form', 'text': htmlform})
    return (output_objects, status)
Ejemplo n.º 13
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)
    output_objects.append({
        'object_type':
        'header',
        'text':
        '%s One-click resource' % configuration.short_title
    })
    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)

    debug = ('true' == accepted['debug'][0].lower())
    console = ('true' == accepted['console'][0].lower())

    if not configuration.site_enable_sandboxes:
        output_objects.append({
            'object_type':
            'text',
            'text':
            '''Sandbox resources 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)

    (status, result) = get_resource(client_id, configuration, logger)
    if not status:
        output_objects.append({'object_type': 'html_form', 'text': result})
        return (output_objects, returnvalues.CLIENT_ERROR)

    fields = {
        'sandboxkey': result[0],
        'resource_name': result[1],
        'cookie': result[2],
        'cputime': result[3],
        'codebase': '%s/sid_redirect/%s.oneclick/'\
             % (configuration.migserver_https_sid_url, result[0]),
        'oneclick_code': 'MiG.oneclick.Applet.class',
        'resource_code': 'MiG.oneclick.Resource.class',
        'oneclick_archive': 'MiGOneClickCodebase.jar',
        'info_code': 'JavaInfoApplet.class',
        'info_archive': '',
        'server': configuration.migserver_https_sid_url,
        'site' : configuration.short_title,
        }

    if debug:
        body = """
DEBUG input vars:
%s
""" % fields
        output_objects.append({'object_type': 'text', 'text': body})

    elif console:
        body = \
                 """
codebase: %(codebase)s
code: %(resource_code)s
archive: %(oneclick_archive)s
server: %(server)s
sandboxkey: %(sandboxkey)s
resource_name: %(resource_name)s
cputime: %(cputime)s
        """ % fields
        output_objects.append({'object_type': 'text', 'text': body})
    else:
        body = """
        <object type='application/x-java-applet' height='600' width='800'>
        <param name='codebase' value='%(codebase)s' />
        <param name='code' value='%(oneclick_code)s' />
        <param name='archive' value='%(oneclick_archive)s' />
        <param name='server' value='%(server)s'>
        <param name='sandboxkey' value='%(sandboxkey)s'>
        <param name='resource_name' value='%(resource_name)s'>
        <param name='cputime' value='%(cputime)s'>
        OneClick applet failed to run (requires Java plug-in).
        </object>
        <p>
        Your computer will act as a %(site)s One-click resource as long as
        this browser window/tab remains open.
        </p>
        <h3>Java requirements and background</h3>
        Please note that if you get no applet picture above with status text,
        it is a likely indicator that you do not have the required Java plugin
        installed in your browser. You can download and install it from
        <a class='urllink iconspace' href='http://www.java.com/en/download/manual.jsp'>
        Sun Java Downloads</a>. The browser probably needs to be restarted
        after the installation before the plugin will be enabled.<br />
        Other Java implementations may <i>appear</i> to work but not really
        deliver job results correctly, so if you want to be sure, please
        install the Sun Java plugin.<br />
        Your browser provides the following Java information:<br />
        <object type='application/x-java-applet' height='60' width='400'>
        <param name='codebase' value='%(codebase)s' />
        <param name='code' value='%(info_code)s' />
        Java plugin not installed or disabled.
        </object>
        """ % fields
        output_objects.append({'object_type': 'html_form', 'text': body})

    return (output_objects, returnvalues.OK)
Ejemplo n.º 14
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)
    queue = accepted['queue'][-1]
    action = accepted['action'][-1]
    iosessionid = accepted['iosessionid'][-1]
    msg = accepted['msg'][-1]
    msg_id = accepted['msg_id'][-1]

    # Web format for cert access and no header for SID access

    if client_id:
        output_objects.append({
            'object_type': 'header',
            'text': 'Message queue %s' % action
        })
    else:
        output_objects.append({'object_type': 'start'})

    # Always return at least a basic file_output entry

    file_entry = {
        'object_type': 'file_output',
        'lines': [],
        'wrap_binary': True,
        'wrap_targets': ['lines']
    }

    if not action in valid_actions:
        output_objects.append({'object_type': 'error_text', 'text'
                               : 'Invalid action "%s" (supported: %s)' % \
                               (action, ', '.join(valid_actions))})
        output_objects.append(file_entry)
        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)

    # Find user home from session or certificate

    if iosessionid:
        client_home = os.path.realpath(
            os.path.join(configuration.webserver_home, iosessionid))
        client_dir = os.path.basename(client_home)
    elif client_id:
        client_dir = client_id_dir(client_id)
    else:
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            'Either certificate or session ID is required'
        })
        output_objects.append(file_entry)
        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

    if not os.path.isdir(base_dir):
        output_objects.append({
            'object_type': 'error_text',
            'text': 'No matching session or user home!'
        })
        output_objects.append(file_entry)
        return (output_objects, returnvalues.CLIENT_ERROR)

    mqueue_base = os.path.join(base_dir, mqueue_prefix) + os.sep

    default_queue_dir = os.path.join(mqueue_base, default_mqueue)

    # Create mqueue base and default queue dir if missing

    if not os.path.exists(default_queue_dir):
        try:
            os.makedirs(default_queue_dir)
        except:
            pass

    # IMPORTANT: path must be expanded to abs for proper chrooting
    queue_path = os.path.abspath(os.path.join(mqueue_base, queue))
    if not valid_user_path(configuration, queue_path, mqueue_base):
        output_objects.append({
            'object_type': 'error_text',
            'text': 'Invalid queue name: "%s"' % queue
        })
        output_objects.append(file_entry)
        return (output_objects, returnvalues.CLIENT_ERROR)

    lock_path = os.path.join(mqueue_base, lock_name)
    lock_handle = open(lock_path, 'a')
    fcntl.flock(lock_handle.fileno(), fcntl.LOCK_EX)

    status = returnvalues.OK
    if action == "interactive":
        form_method = 'post'
        csrf_limit = get_csrf_limit(configuration)
        fill_helpers = {
            'queue': queue,
            'msg': msg,
            'form_method': form_method,
            'csrf_field': csrf_field,
            'csrf_limit': csrf_limit,
        }
        target_op = 'mqueue'
        csrf_token = make_csrf_token(configuration, form_method, target_op,
                                     client_id, csrf_limit)
        fill_helpers.update({'target_op': target_op, 'csrf_token': csrf_token})

        output_objects.append({
            'object_type':
            'text',
            'text':
            '''
Fill in the fields below to control and access your personal message queues.
Jobs can receive from and send to the message queues during execution, and use
them as a means of job inter-communication. Expect message queue operations to
take several seconds on the resources, however. That is, use it for tasks like
orchestrating long running jobs, and not for low latency communication.
'''
        })
        html = '''
<form name="mqueueform" method="%(form_method)s" action="%(target_op)s.py">
<table class="mqueue">
<tr><td class=centertext>
</td></tr>
<tr><td>
Action:<br />
<input type="hidden" name="%(csrf_field)s" value="%(csrf_token)s" />
<input type=radio name=action value="create" onclick="javascript: document.mqueueform.queue.disabled=false; document.mqueueform.msg.disabled=true;" />create queue
<input type=radio name=action checked value="send" onclick="javascript: document.mqueueform.queue.disabled=false; document.mqueueform.msg.disabled=false;" />send message to queue
<input type=radio name=action value="receive" onclick="javascript: document.mqueueform.queue.disabled=false; document.mqueueform.msg.disabled=true;" />receive message from queue
<input type=radio name=action value="remove" onclick="javascript: document.mqueueform.queue.disabled=false; document.mqueueform.msg.disabled=true;" />remove queue
<input type=radio name=action value="listqueues" onclick="javascript: document.mqueueform.queue.disabled=true; document.mqueueform.msg.disabled=true;" />list queues
<input type=radio name=action value="listmessages" onclick="javascript: document.mqueueform.queue.disabled=false; document.mqueueform.msg.disabled=true;" />list messages
<input type=radio name=action value="show" onclick="javascript: document.mqueueform.queue.disabled=false; document.mqueueform.msg.disabled=true;" />show message
</td></tr>
<tr><td>
Queue:<br />
<input class="fillwidth" type=text name=queue value="%(queue)s" />
</td></tr>
<tr><td>
<div id="msgfieldf">
<input class="fillwidth" type=text name=msg value="%(msg)s" /><br />
</div>
</td></tr>
<tr><td>
<input type="submit" value="Apply" />
</td></tr>
</table>
</form>
''' % fill_helpers
        output_objects.append({'object_type': 'html_form', 'text': html})
        output_objects.append({
            'object_type':
            'text',
            'text':
            '''
Further live job control is avalable through the live I/O interface.
They provide a basic interface for centrally managing input and output files
for active jobs.
'''
        })
        output_objects.append({
            'object_type': 'link',
            'destination': 'liveio.py',
            'text': 'Live I/O interface'
        })
        return (output_objects, returnvalues.OK)
    elif action == 'create':
        try:
            os.mkdir(queue_path)
            output_objects.append({
                'object_type': 'text',
                'text': 'New "%s" queue created' % queue
            })
        except Exception as err:
            output_objects.append({'object_type': 'error_text', 'text'
                                   : 'Could not create "%s" queue: "%s"' % \
                                   (queue, err)})
            status = returnvalues.CLIENT_ERROR
    elif action == 'remove':
        try:
            for entry in os.listdir(queue_path):
                os.remove(os.path.join(queue_path, entry))
            os.rmdir(queue_path)
            output_objects.append({
                'object_type': 'text',
                'text': 'Existing "%s" queue removed' % queue
            })
        except Exception as err:
            output_objects.append({'object_type': 'error_text', 'text'
                                   : 'Could not remove "%s" queue: "%s"' % \
                                   (queue, err)})
            status = returnvalues.CLIENT_ERROR
    elif action == 'send':
        try:
            if not msg_id:
                msg_id = "%.0f" % time.time()
            msg_path = os.path.join(queue_path, msg_id)
            msg_fd = open(msg_path, 'w')
            msg_fd.write(msg)
            msg_fd.close()
            output_objects.append({
                'object_type': 'text',
                'text': 'Message sent to "%s" queue' % queue
            })
        except Exception as err:
            output_objects.append({'object_type': 'error_text', 'text'
                                   : 'Could not send to "%s" queue: "%s"' % \
                                   (queue, err)})
            status = returnvalues.CLIENT_ERROR
    elif action == 'receive':
        try:
            if not msg_id:
                messages = os.listdir(queue_path)
                messages.sort()
                if messages:
                    msg_id = messages[0]
            if msg_id:
                message_path = os.path.join(queue_path, msg_id)
                message_fd = open(message_path, 'r')
                message = message_fd.readlines()
                message_fd.close()
                os.remove(message_path)
                file_entry['path'] = os.path.basename(message_path)
            else:
                message = [mqueue_empty]
            # Update file_output entry for raw data with output_format=file
            file_entry['lines'] = message
        except Exception as err:
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                'Could not receive from "%s" queue: "%s"' % (queue, err)
            })
            status = returnvalues.CLIENT_ERROR
    elif action == 'show':
        try:
            if not msg_id:
                messages = os.listdir(queue_path)
                messages.sort()
                if messages:
                    msg_id = messages[0]
            if msg_id:
                message_path = os.path.join(queue_path, msg_id)
                message_fd = open(message_path, 'r')
                message = message_fd.readlines()
                message_fd.close()
                file_entry['path'] = os.path.basename(message_path)
            else:
                message = [mqueue_empty]
            # Update file_output entry for raw data with output_format=file
            file_entry['lines'] = message
        except Exception as err:
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                'Could not show %s from "%s" queue: "%s"' %
                (msg_id, queue, err)
            })
            status = returnvalues.CLIENT_ERROR
    elif action == 'listmessages':
        try:
            messages = os.listdir(queue_path)
            messages.sort()
            output_objects.append({'object_type': 'list', 'list': messages})
        except Exception as err:
            output_objects.append({'object_type': 'error_text', 'text'
                                   : 'Could not list "%s" queue: "%s"' % \
                                   (queue, err)})
            status = returnvalues.CLIENT_ERROR
    elif action == 'listqueues':
        try:
            queues = [i for i in os.listdir(mqueue_base) if \
                      os.path.isdir(os.path.join(mqueue_base, i))]
            queues.sort()
            output_objects.append({'object_type': 'list', 'list': queues})
        except Exception as err:
            output_objects.append({
                'object_type': 'error_text',
                'text': 'Could not list queues: "%s"' % err
            })
            status = returnvalues.CLIENT_ERROR
    else:
        output_objects.append({
            'object_type': 'error_text',
            'text': 'Unexpected mqueue action: "%s"' % action
        })
        status = returnvalues.SYSTEM_ERROR

    lock_handle.close()

    output_objects.append(file_entry)
    output_objects.append({
        'object_type':
        'link',
        'destination':
        'mqueue.py?queue=%s;msg=%s' % (queue, msg),
        'text':
        'Back to message queue interaction'
    })
    return (output_objects, returnvalues.OK)
Ejemplo n.º 15
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=False)
    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)

    oid_url = accepted['url'][-1]
    openid_status = {
        'object_type': 'openid_status',
        'server': None,
        'status': None,
        'error': ""
    }
    # IMPORTANT: we only allow ping of configured openid servers to avoid abuse
    # otherwise the urlopen could be tricked to use e.g. file:/etc/passwd or
    # be used to attack remote sites
    if oid_url in configuration.user_openid_providers:
        # TODO: build url from conf
        ping_url = oid_url.replace("/id/", "/ping")
        openid_status['server'] = ping_url
        logger.debug("%s openid server on %s" % (op_name, ping_url))
        try:
            # Never use proxies
            ping_status = urllib.urlopen(ping_url, proxies={})
            http_status = ping_status.getcode()
            data = ping_status.read()
            ping_status.close()
            if http_status == 200:
                # TODO: better parsing
                if "<h1>True</h1>" in data:
                    openid_status['status'] = "online"
                else:
                    openid_status['status'] = "down"
                    openid_status['error'] = data
            else:
                openid_status['status'] = "down"
                openid_status['error'] = "server returned error code %s" % \
                                         http_status
        except Exception as exc:
            openid_status['status'] = "down"
            openid_status['error'] = "unexpected server response (%s)" % exc
        if openid_status['status'] == "online":
            logger.info("%s on %s succeeded" % (op_name, oid_url))
        else:
            logger.error("%s against %s returned error: " %
                         (op_name, oid_url) +
                         " %(error)s (%(status)s)" % openid_status)
    elif oid_url in configuration.user_openidconnect_providers:
        # TODO: build url from conf
        # ping_url = oid_url.replace(
        #    "/oauth/nam/.well-known/openid-configuration", "")
        ping_url = oid_url
        openid_status['server'] = ping_url
        logger.debug("%s openid connect server on %s" % (op_name, ping_url))
        try:
            # Never use proxies
            ping_status = urllib.urlopen(ping_url, proxies={})
            http_status = ping_status.getcode()
            data = ping_status.read()
            ping_status.close()
            if http_status == 200:
                # TODO: better parsing
                if "authorization_endpoint" in data:
                    openid_status['status'] = "online"
                else:
                    openid_status['status'] = "down"
                    openid_status['error'] = data
            else:
                openid_status['status'] = "down"
                openid_status['error'] = "server returned error code %s" % \
                                         http_status
        except Exception as exc:
            openid_status['status'] = "down"
            openid_status['error'] = "unexpected server response (%s)" % exc
        if openid_status['status'] == "online":
            logger.info("%s on %s succeeded" % (op_name, oid_url))
        else:
            logger.error("%s against %s returned error: " %
                         (op_name, oid_url) +
                         " %(error)s (%(status)s)" % openid_status)
    else:
        logger.error("%s against %s is not a valid openid provider" %
                     (op_name, oid_url))
        openid_status['server'] = "no such server configured"
        openid_status['status'] = "unavailable"
        openid_status['error'] = "OpenID login from %s not enabled" % oid_url
    output_objects.append(openid_status)
    return (output_objects, returnvalues.OK)
Ejemplo n.º 16
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_title=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)

    remote_ip = str(os.getenv('REMOTE_ADDR'))

    res_type = accepted['type'][-1]
    unique_resource_name = accepted['unique_resource_name'][-1]
    exe_name = accepted['exe_name'][-1]

    status = returnvalues.OK

    if not configuration.site_enable_resources:
        output_objects.append({
            'object_type': 'error_text',
            'text': "Resources are not enabled on this site"
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    # Web format for cert access and no header for SID access
    if client_id:
        output_objects.append({
            'object_type': 'title',
            'text': 'Load resource script PGID'
        })
        output_objects.append({
            'object_type': 'header',
            'text': 'Load resource script PGID'
        })
    else:
        output_objects.append({'object_type': 'start'})

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

    base_dir = os.path.abspath(
        os.path.join(configuration.resource_home,
                     unique_resource_name)) + os.sep

    if not is_owner(client_id, unique_resource_name,
                    configuration.resource_home, logger):
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            "Failure: You must be an owner of '%s' to get the PGID!" %
            unique_resource_name
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    # is_owner incorporates unique_resource_name verification - no need to
    # specifically check for illegal directory traversal on that variable.
    # exe_name is not automatically checked however - do it manually

    if not valid_dir_input(base_dir, 'EXE_' + exe_name + '.PGID'):

        # out of bounds - rogue resource!?!?

        output_objects.append({
            'object_type': 'error_text',
            'text': 'invalid exe_name! %s' % exe_name
        })
        logger.error('''getrespgid called with illegal parameter(s) in what
appears to be an illegal directory traversal attempt!: unique_resource_name %s,
exe %s, client_id %s''' % (unique_resource_name, exe_name, client_id))
        return (output_objects, returnvalues.CLIENT_ERROR)

    # Check that resource address matches request source to make DoS harder
    try:
        check_source_ip(remote_ip, unique_resource_name)
    except ValueError as vae:
        output_objects.append({
            'object_type': 'error_text',
            'text': 'invalid request: %s' % vae
        })
        logger.error("Invalid put pgid: %s" % vae)
        return (output_objects, returnvalues.CLIENT_ERROR)

    # TODO: add full session ID check here

    if 'FE' == res_type:
        pgid_path = os.path.join(base_dir, 'FE.PGID')
    elif 'EXE' == res_type:
        pgid_path = os.path.join(base_dir + 'EXE_%s.PGID' % exe_name)
    else:
        output_objects.append({
            'object_type': 'error_text',
            'text': "Unknown type: '%s'" % res_type
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    try:
        pgid_file = open(pgid_path, 'r+')
        fcntl.flock(pgid_file, fcntl.LOCK_EX)
        pgid_file.seek(0, 0)
        pgid = pgid_file.readline().strip()
        fcntl.flock(pgid_file, fcntl.LOCK_UN)
        pgid_file.close()

        msg = "%s\n'%s' PGID succesfully retrieved." % (pgid, res_type)
    except Exception as err:
        if 'FE' == res_type:
            msg = "Resource frontend: '%s' is stopped." % unique_resource_name
        elif 'EXE' == res_type:
            msg = ("Error reading PGID for resource: '%s' EXE: '%s'\n"
                   'Either resource has never been started or a server '
                   'error occured.') % (unique_resource_name, exe_name)
        status = returnvalues.CLIENT_ERROR

    # Status code line followed by raw output
    if not client_id:
        output_objects.append({'object_type': 'script_status', 'text': ''})
        output_objects.append({
            'object_type': 'binary',
            'data': '%s' % status[0]
        })
    output_objects.append({'object_type': 'binary', 'data': msg})
    return (output_objects, status)
Ejemplo n.º 17
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=False)
    client_dir = client_id_dir(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)

    if not configuration.site_enable_openid or \
            not 'migoid' in configuration.site_signup_methods:
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            '''Local OpenID login is not enabled on this site'''
        })
        return (output_objects, returnvalues.SYSTEM_ERROR)

    title_entry = find_entry(output_objects, 'title')
    title_entry['text'] = '%s OpenID account request' % \
                          configuration.short_title
    title_entry['skipmenu'] = True
    form_fields = [
        'full_name', 'organization', 'email', 'country', 'state', 'password',
        'verifypassword', 'comment'
    ]
    title_entry['style']['advanced'] += account_css_helpers(configuration)
    add_import, add_init, add_ready = account_js_helpers(
        configuration, form_fields)
    title_entry['script']['advanced'] += add_import
    title_entry['script']['init'] += add_init
    title_entry['script']['ready'] += add_ready
    title_entry['script']['body'] = "class='staticpage'"

    header_entry = {
        'object_type':
        'header',
        'text':
        '%s account request - with OpenID login' % configuration.short_title
    }
    output_objects.append(header_entry)

    output_objects.append({
        'object_type':
        'html_form',
        'text':
        '''
    <div id="contextual_help">

    </div>
'''
    })

    # 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

    user_fields = {
        'full_name': '',
        'organization': '',
        'email': '',
        'state': '',
        'country': '',
        'password': '',
        'verifypassword': '',
        'comment': ''
    }
    if not os.path.isdir(base_dir) and client_id:

        # Redirect to extcert page with certificate requirement but without
        # changing access method (CGI vs. WSGI).

        extcert_url = os.environ['REQUEST_URI'].replace('-sid', '-bin')
        extcert_url = os.path.join(os.path.dirname(extcert_url), 'extcert.py')
        extcert_link = {
            'object_type': 'link',
            'destination': extcert_url,
            'text': 'Sign up with existing certificate (%s)' % client_id
        }
        output_objects.append({
            'object_type':
            'warning',
            'text':
            '''Apparently
you already have a suitable %s certificate that you may sign up with:''' %
            configuration.short_title
        })
        output_objects.append(extcert_link)
        output_objects.append({
            'object_type':
            'warning',
            'text':
            '''However,
if you want a dedicated %s %s User OpenID you can still request one below:''' %
            (configuration.short_title, configuration.user_mig_oid_title)
        })
    elif client_id:
        for entry in (title_entry, header_entry):
            entry['text'] = entry['text'].replace('request', 'request / renew')
        output_objects.append({
            'object_type':
            'html_form',
            'text':
            '''<p>
Apparently you already have valid %s credentials, but if you want to add %s
User OpenID access to the same account you can do so by posting the form below.
Changing fields is <span class="warningtext"> not </span> supported, so all fields
must remain unchanged for it to work.
Otherwise it results in a request for a new account and OpenID without access
to your old files, jobs and privileges. </p>''' %
            (configuration.short_title, configuration.user_mig_oid_title)
        })
        user_fields.update(distinguished_name_to_user(client_id))

    # Override with arg values if set
    for field in user_fields:
        if not field in accepted:
            continue
        override_val = accepted[field][-1].strip()
        if override_val:
            user_fields[field] = override_val
    user_fields = canonical_user(configuration, user_fields,
                                 user_fields.keys())

    # Site policy dictates min length greater or equal than password_min_len
    policy_min_len, policy_min_classes = parse_password_policy(configuration)
    user_fields.update({
        'valid_name_chars':
        '%s (and common accents)' % html_escape(valid_name_chars),
        'valid_password_chars':
        html_escape(valid_password_chars),
        'password_min_len':
        max(policy_min_len, password_min_len),
        'password_max_len':
        password_max_len,
        'password_min_classes':
        max(policy_min_classes, 1),
        'site':
        configuration.short_title
    })
    form_method = 'post'
    csrf_limit = get_csrf_limit(configuration)
    fill_helpers = {
        'form_method': form_method,
        'csrf_field': csrf_field,
        'csrf_limit': csrf_limit
    }
    target_op = 'reqoidaction'
    csrf_token = make_csrf_token(configuration, form_method, target_op,
                                 client_id, csrf_limit)
    fill_helpers.update({'target_op': target_op, 'csrf_token': csrf_token})

    fill_helpers.update({'site_signup_hint': configuration.site_signup_hint})
    # Write-protect ID fields if requested
    for field in cert_field_map:
        fill_helpers['readonly_%s' % field] = ''
    ro_fields = [i for i in accepted['ro_fields'] if i in cert_field_map]
    if keyword_auto in accepted['ro_fields']:
        ro_fields += [i for i in cert_field_map if not i in ro_fields]
    for field in ro_fields:
        fill_helpers['readonly_%s' % field] = 'readonly'
    fill_helpers.update(user_fields)
    html = """
<p class='sub-title'>Please enter your information in at least the
<span class='highlight_required'>mandatory</span> fields below and press the
Send button to submit the account request to the %(site)s administrators.</p>

<p class='personal leftpad highlight_message'>
IMPORTANT: we need to identify and notify you about login info, so please use a
working Email address clearly affiliated with your Organization!
</p>

%(site_signup_hint)s

<hr />

    """

    html += account_request_template(configuration,
                                     default_values=fill_helpers)

    # TODO: remove this legacy version?
    html += """
<div style="height: 0; visibility: hidden; display: none;">
<!--OLD FORM-->
<form method='%(form_method)s' action='%(target_op)s.py' onSubmit='return validate_form();'>
<input type='hidden' name='%(csrf_field)s' value='%(csrf_token)s' />


<table>
<!-- NOTE: javascript support for unicode pattern matching is lacking so we
           only restrict e.g. Full Name to words separated by space here. The
           full check takes place in the backend, but users are better of with
           sane early warnings than the cryptic backend errors.
-->

<tr><td class='mandatory label'>Full name</td><td><input id='full_name_field' type=text name=cert_name value='%(full_name)s' required pattern='[^ ]+([ ][^ ]+)+' title='Your full name, i.e. two or more names separated by space' /></td><td class=fill_space><br /></td></tr>
<tr><td class='mandatory label'>Email address</td><td><input id='email_field' type=email name=email value='%(email)s' required title='A valid email address that you read' /> </td><td class=fill_space><br /></td></tr>
<tr><td class='mandatory label'>Organization</td><td><input id='organization_field' type=text name=org value='%(organization)s' required pattern='[^ ]+([ ][^ ]+)*' title='Name of your organisation: one or more abbreviations or words separated by space' /></td><td class=fill_space><br /></td></tr>
<tr><td class='mandatory label'>Two letter country-code</td><td><input id='country_field' type=text name=country minlength=2 maxlength=2 value='%(country)s' required pattern='[A-Z]{2}' title='The two capital letters used to abbreviate your country' /></td><td class=fill_space><br /></td></tr>
<tr><td class='optional label'>State</td><td><input id='state_field' type=text name=state value='%(state)s' pattern='([A-Z]{2})?' maxlength=2 title='Leave empty or enter the capital 2-letter abbreviation of your state if you are a US resident' /> </td><td class=fill_space><br /></td></tr>
<tr><td class='mandatory label'>Password</td><td><input id='password_field' type=password name=password minlength=%(password_min_len)d maxlength=%(password_max_len)d value='%(password)s' required pattern='.{%(password_min_len)d,%(password_max_len)d}' title='Password of your choice, see help box for limitations' /> </td><td class=fill_space><br /></td></tr>
<tr><td class='mandatory label'>Verify password</td><td><input id='verifypassword_field' type=password name=verifypassword minlength=%(password_min_len)d maxlength=%(password_max_len)d value='%(verifypassword)s' required pattern='.{%(password_min_len)d,%(password_max_len)d}' title='Repeat your chosen password' /></td><td class=fill_space><br /></td></tr>
<!-- NOTE: we technically allow saving the password on scrambled form hide it by default -->
<tr class='hidden'><td class='optional label'>Password recovery</td><td class=''><input id='passwordrecovery_checkbox' type=checkbox name=passwordrecovery></td><td class=fill_space><br/></td></tr>
<tr><td class='optional label'>Optional comment or reason why you should<br />be granted a %(site)s account:</td><td><textarea id='comment_field' rows=4 name=comment title='A free-form comment where you can explain what you need the account for' ></textarea></td><td class=fill_space><br /></td></tr>
<tr><td class='label'><!-- empty area --></td><td>

<input id='submit_button' type=submit value=Send /></td><td class=fill_space><br/></td></tr>
</table>
</form>
<hr />
<div class='warn_message'>Please note that if you enable password recovery your password will be saved on encoded format but recoverable by the %(site)s administrators</div>
</div>
<br />
<br />
<!-- Hidden help text -->
<div id='help_text'>
  <div id='1full_name_help'>Your full name, restricted to the characters in '%(valid_name_chars)s'</div>
  <div id='1organization_help'>Organization name or acronym  matching email</div>
  <div id='1email_help'>Email address associated with your organization if at all possible</div>
  <div id='1country_help'>Country code of your organization and on the form DE/DK/GB/US/.. , <a href='https://en.wikipedia.org/wiki/ISO_3166-1'>help</a></div>
  <div id='1state_help'>Optional 2-letter ANSI state code of your organization, please just leave empty unless it is in the US or similar, <a href='https://en.wikipedia.org/wiki/List_of_U.S._state_abbreviations'>help</a></div>
  <div id='1password_help'>Password is restricted to the characters:<br/><tt>%(valid_password_chars)s</tt><br/>Certain other complexity requirements apply for adequate strength. For example it must be %(password_min_len)s to %(password_max_len)s characters long and contain at least %(password_min_classes)d different character classes.</div>
  <div id='1verifypassword_help'>Please repeat password</div>
  <!--<div id='1comment_help'>Optional, but a short informative comment may help us verify your account needs and thus speed up our response. Typically the name of a local collaboration partner or project may be helpful.</div>-->
</div>

"""

    output_objects.append({
        'object_type': 'html_form',
        'text': html % fill_helpers
    })
    return (output_objects, returnvalues.OK)
Ejemplo n.º 18
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,
                                  op_menu=client_id)
    # TODO: this handler is incomplete and NOT yet hooked up with Xgi-bin
    return (output_objects, returnvalues.SYSTEM_ERROR)
    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,
        # NOTE: path cannot use wildcards here
        typecheck_overrides={},
    )
    if not validate_status:
        return (accepted, returnvalues.CLIENT_ERROR)

    filename = accepted['filename'][-1]
    patterns = accepted['path']
    iosessionid = accepted['iosessionid'][-1]
    file_startpos = accepted['file_startpos'][-1]
    file_endpos = accepted['file_endpos'][-1]

    if file_startpos:
        file_startpos = int(file_startpos)
    else:
        file_startpos = -1
    if file_endpos:
        file_endpos = int(file_endpos)
    else:
        file_endpos = -1

    # Legacy naming
    if filename:
        patterns = [filename]

    valid_methods = ['GET', 'PUT', 'DELETE']
    action = os.getenv('REQUEST_METHOD')
    if not action in valid_methods:
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            '''Only accepting %s requests''' % ', '.join(valid_methods)
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    # TODO: introduce CSRF protection here when clients support it
    # if not safe_handler(configuration, action, 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 job IO session ID
    if client_id:
        user_id = client_id
        target_dir = client_id_dir(client_id)
        base_dir = configuration.user_home
        page_title = 'User range file access'
        widgets = True
        userstyle = True
    elif iosessionid:
        user_id = iosessionid
        target_dir = iosessionid
        base_dir = configuration.webserver_home
        page_title = 'Create Shared Directory'
        widgets = False
        userstyle = False
    else:
        logger.error('%s called without proper auth: %s' % (op_name, accepted))
        output_objects.append({
            'object_type': 'error_text',
            'text': 'Authentication is missing!'
        })
        return (output_objects, returnvalues.SYSTEM_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(base_dir, target_dir)) + os.sep

    title_entry = find_entry(output_objects, 'title')
    title_entry['text'] = page_title
    title_entry['skipwidgets'] = not widgets
    title_entry['skipuserstyle'] = not userstyle
    output_objects.append({'object_type': 'header', 'text': page_title})

    # Input validation assures target_dir can't escape base_dir
    if not os.path.isdir(base_dir):
        output_objects.append({
            'object_type': 'error_text',
            'text': 'Invalid client/iosession id!'
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    for pattern in patterns:

        # Check directory traversal attempts before actual handling to avoid
        # leaking information about file system layout while allowing
        # consistent error messages
        # NB: Globbing disabled on purpose here

        unfiltered_match = [base_dir + os.sep + 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):

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

                logger.warn('%s tried to %s %s restricted path! (%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: cannot access file '%s': Permission denied" %
                (op_name, pattern)
            })
            status = returnvalues.CLIENT_ERROR

        for abs_path in match:
            relative_path = abs_path.replace(base_dir, '')
        if action == 'GET':
            if not do_get(configuration, output_objects, abs_path,
                          file_startpos, file_endpos):
                output_objects.append({
                    'object_type': 'error_text',
                    'text': '''Could not gett %r''' % pattern
                })
                status = returnvalues.SYSTEM_ERROR
        elif action == 'PUT':
            if not do_put(configuration, output_objects, abs_path,
                          file_startpos, file_endpos):
                output_objects.append({
                    'object_type': 'error_text',
                    'text': '''Could not put %r''' % pattern
                })
                status = returnvalues.SYSTEM_ERROR
        elif action == 'DELETE':
            if not do_delete(configuration, output_objects, abs_path):
                output_objects.append({
                    'object_type':
                    'error_text',
                    'text':
                    '''Could not delete %r''' % pattern
                })
                status = returnvalues.SYSTEM_ERROR
        else:
            output_objects.append({
                'object_type': 'error_text',
                'text': 'Unsupported action: %r' % action
            })
            return (output_objects, returnvalues.CLIENT_ERROR)

    return (output_objects, status)
Ejemplo n.º 19
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_title=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)

    remote_ip = str(os.getenv('REMOTE_ADDR'))

    res_type = accepted['type'][-1]
    unique_resource_name = accepted['unique_resource_name'][-1]
    exe_name = accepted['exe_name'][-1]
    pgid = accepted['pgid'][-1]

    status = returnvalues.OK

    if not configuration.site_enable_resources:
        output_objects.append({
            'object_type': 'error_text',
            'text': "Resources are not enabled on this site"
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    # Web format for cert access and no header for SID access
    if client_id:
        output_objects.append({
            'object_type': 'title',
            'text': 'Load resource script PGID'
        })
        output_objects.append({
            'object_type': 'header',
            'text': 'Load resource script PGID'
        })
    else:
        output_objects.append({'object_type': 'start'})

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

    base_dir = os.path.abspath(
        os.path.join(configuration.resource_home,
                     unique_resource_name)) + os.sep

    # We do not have a trusted base dir here since there's no certificate data.
    # Manually check input variables

    if not valid_dir_input(configuration.resource_home, unique_resource_name):

        # out of bounds - rogue resource!?!?

        msg = 'invalid unique_resource_name! %s' % unique_resource_name
        logger.error(
            'putrespgid FE called with illegal parameter(s) in what appears to be an illegal directory traversal attempt!: unique_resource_name %s, exe %s, client_id %s'
            % (unique_resource_name, exe_name, client_id))
        return (output_objects, returnvalues.CLIENT_ERROR)

    if not valid_dir_input(base_dir, 'EXE_%s.PGID' % exe_name):

        # out of bounds - rogue resource!?!?

        msg = 'invalid unique_resource_name / exe_name! %s / %s' \
              % (unique_resource_name, exe_name)
        logger.error(
            'putrespgid EXE called with illegal parameter(s) in what appears to be an illegal directory traversal attempt!: unique_resource_name %s, exe %s, client_id %s'
            % (unique_resource_name, exe_name, client_id))
        return (output_objects, returnvalues.CLIENT_ERROR)

    (load_status, resource_conf) = \
        get_resource_configuration(configuration.resource_home,
                                   unique_resource_name, logger)
    if not load_status:
        logger.error("Invalid putrespgid - no resouce_conf for: %s : %s" %
                     (unique_resource_name, resource_conf))
        output_objects.append({
            'object_type': 'error_text',
            'text': 'invalid request: no such resource!'
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    # Check that resource address matches request source to make DoS harder
    proxy_fqdn = resource_conf.get('FRONTENDPROXY', None)
    try:
        check_source_ip(remote_ip, unique_resource_name, proxy_fqdn)
    except ValueError as vae:
        logger.error("Invalid put pgid: %s" % vae)
        output_objects.append({
            'object_type': 'error_text',
            'text': 'invalid request: %s' % vae
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    # TODO: add full session ID check here

    if 'FE' == res_type:
        (result, msg) = put_fe_pgid(configuration.resource_home,
                                    unique_resource_name, pgid, logger, True)
        if result:
            msg += '(%s) (%s)' % (remote_ip, res_type)
    elif 'EXE' == res_type:
        (result, msg) = put_exe_pgid(configuration.resource_home,
                                     unique_resource_name, exe_name, pgid,
                                     logger, True)
        if result:
            msg += '(%s) (%s)' % (remote_ip, res_type)
    else:
        msg = "Unknown type: '%s'" % res_type
        status = returnvalues.CLIENT_ERROR

    # Status code line followed by raw output
    if not client_id:
        output_objects.append({'object_type': 'script_status', 'text': ''})
        output_objects.append({
            'object_type': 'binary',
            'data': '%s' % status[0]
        })
    output_objects.append({'object_type': 'binary', 'data': msg})
    return (output_objects, status)
Ejemplo n.º 20
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=False)
    defaults = signature()[1]
    client_dir = client_id_dir(client_id)
    logger.debug('in peersaction: %s' % user_arguments_dict)
    (validate_status, accepted) = validate_input(user_arguments_dict,
                                                 defaults,
                                                 output_objects,
                                                 allow_rejects=False)
    if not validate_status:
        return (accepted, returnvalues.CLIENT_ERROR)

    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)

    title_entry = find_entry(output_objects, 'title')
    title_entry['text'] = 'Save Peers'
    output_objects.append({'object_type': 'header', 'text': 'Save Peers'})

    admin_email = configuration.admin_email
    smtp_server = configuration.smtp_server
    user_pending = os.path.abspath(configuration.user_pending)

    user_map = get_full_user_map(configuration)
    user_dict = user_map.get(client_id, None)
    # Optional site-wide limitation of peers permission
    if not user_dict or \
            not peers_permit_allowed(configuration, user_dict):
        logger.warning("user %s is not allowed to permit peers!" % client_id)
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            'Only privileged users can permit external peers!'
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    action = accepted['action'][-1].strip()
    label = accepted['peers_label'][-1].strip()
    kind = accepted['peers_kind'][-1].strip()
    raw_expire = accepted['peers_expire'][-1].strip()
    peers_content = accepted['peers_content']
    peers_format = accepted['peers_format'][-1].strip()
    peers_invite = accepted['peers_invite'][-1].strip()
    do_invite = (peers_invite.lower() in ['on', 'true', 'yes', '1'])

    try:
        expire = datetime.datetime.strptime(raw_expire, '%Y-%m-%d')
        if datetime.datetime.now() > expire:
            raise ValueError("specified expire value is in the past!")
    except Exception as exc:
        logger.error("expire %r could not be parsed into a (future) date" %
                     raw_expire)
        output_objects.append({
            'object_type':
            'text',
            'text':
            'No valid expire provided - using default: %d days' %
            default_expire_days
        })
        expire = datetime.datetime.now()
        expire += datetime.timedelta(days=default_expire_days)
    expire = expire.date().isoformat()

    if not action in peer_actions:
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            'Unsupported peer action %r - only %s are allowed' %
            (action, ', '.join(peer_actions))
        })
        return (output_objects, returnvalues.CLIENT_ERROR)
    if not kind in peer_kinds:
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            'Unsupported peer kind %r - only %s are allowed' %
            (kind, ', '.join(peer_kinds))
        })
        return (output_objects, returnvalues.CLIENT_ERROR)
    # TODO: implement and enable more formats?
    if peers_format not in ("csvform", 'userid'):
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            'Only Import Peers is implemented so far!'
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    peers_path = os.path.join(configuration.user_settings, client_dir,
                              peers_filename)
    try:
        all_peers = load(peers_path)
    except Exception as exc:
        logger.warning("could not load peers from: %s" % exc)
        all_peers = {}

    # Extract peer(s) from request
    (peers, err) = parse_peers(configuration, peers_content, peers_format)
    if not err and not peers:
        err = ["No valid peers provided"]
    if err:
        output_objects.append({
            'object_type': 'error_text',
            'text': 'Parsing failed: %s' % '.\n '.join(err)
        })
        output_objects.append({
            'object_type': 'link',
            'destination': 'peers.py',
            'text': 'Back to peers'
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    # NOTE: general cases of operation here:
    # * import multiple peers in one go (add new, update existing)
    # * add one or more new peers
    # * update one or more existing peers
    # * remove one or more existing peers
    # * accept one or more pending requests
    # * reject one or more pending requests
    # The kind and expire values are generally applied for all included peers.

    # NOTE: we check all peers before any action
    for user in peers:
        fill_distinguished_name(user)
        peer_id = user['distinguished_name']
        cur_peer = all_peers.get(peer_id, {})
        if 'add' == action and cur_peer:
            output_objects.append({
                'object_type': 'error_text',
                'text': 'Peer %r already exists!' % peer_id
            })
            return (output_objects, returnvalues.CLIENT_ERROR)
        elif 'update' == action and not cur_peer:
            output_objects.append({
                'object_type': 'error_text',
                'text': 'Peer %r does not exists!' % peer_id
            })
            return (output_objects, returnvalues.CLIENT_ERROR)
        elif 'remove' == action and not cur_peer:
            output_objects.append({
                'object_type': 'error_text',
                'text': 'Peer %r does not exists!' % peer_id
            })
            return (output_objects, returnvalues.CLIENT_ERROR)
        elif 'accept' == action and cur_peer:
            output_objects.append({
                'object_type': 'error_text',
                'text': 'Peer %r already accepted!' % peer_id
            })
            return (output_objects, returnvalues.CLIENT_ERROR)
        elif 'reject' == action and cur_peer:
            output_objects.append({
                'object_type': 'error_text',
                'text': 'Peer %r already accepted!' % peer_id
            })
            return (output_objects, returnvalues.CLIENT_ERROR)
        elif 'import' == action and cur_peer:
            # Only warn on import with existing match
            output_objects.append({
                'object_type': 'text',
                'text': 'Updating existing peer %r' % peer_id
            })

    # Now apply changes
    for user in peers:
        peer_id = user['distinguished_name']
        cur_peer = all_peers.get(peer_id, {})
        user.update({'label': label, 'kind': kind, 'expire': expire})
        if 'add' == action:
            all_peers[peer_id] = user
        elif 'update' == action:
            all_peers[peer_id] = user
        elif 'remove' == action:
            del all_peers[peer_id]
        elif 'accept' == action:
            all_peers[peer_id] = user
        elif 'reject' == action:
            pass
        elif 'import' == action:
            all_peers[peer_id] = user
        logger.info("%s peer %s" % (action, peer_id))

    try:
        dump(all_peers, peers_path)
        logger.debug('%s %s peers %s in %s' %
                     (client_id, action, all_peers, peers_path))
        output_objects.append({
            'object_type': 'text',
            'text': "Completed %s peers" % action
        })
        for user in peers:
            output_objects.append({
                'object_type': 'text',
                'text': "%(distinguished_name)s" % user
            })
        if action in ['import', 'add', 'update']:
            client_name = extract_field(client_id, 'full_name')
            client_email = extract_field(client_id, 'email')

            if do_invite:
                succeeded, failed = [], []
                email_header = '%s Invitation' % configuration.short_title
                email_msg_template = """Hi %%s,
This is an automatic email sent on behalf of %s who vouched for you to get a
user account on %s. You can accept the invitation by going to
%%s
entering a password of your choice and submitting the form.
If you do not want a user account you can safely ignore this email.

We would be grateful if you report any abuse of the invitation system to the
site administrators (%s).
""" % (client_name, configuration.short_title, admin_email)
                for peer_user in peers:
                    peer_name = peer_user['full_name']
                    peer_email = peer_user['email']
                    peer_url = os.path.join(
                        configuration.migserver_https_sid_url, 'cgi-sid',
                        'reqoid.py')
                    peer_req = {}
                    for field in peers_fields:
                        peer_req[field] = peer_user.get(field, '')
                    peer_req['comment'] = 'Invited by %s (%s) for %s purposes' \
                                          % (client_name, client_email, kind)
                    # Mark ID fields as readonly in the form to limit errors
                    peer_req['ro_fields'] = keyword_auto
                    peer_url += '?%s' % urllib.urlencode(peer_req)
                    email_msg = email_msg_template % (peer_name, peer_url)
                    logger.info(
                        'Sending invitation: to: %s, header: %s, msg: %s, smtp_server: %s'
                        % (peer_email, email_header, email_msg, smtp_server))
                    if send_email(peer_email, email_header, email_msg, logger,
                                  configuration):
                        succeeded.append(peer_email)
                    else:
                        failed.append(peer_email)

                if failed:
                    output_objects.append({
                        'object_type':
                        'error_text',
                        'text':
                        """An error occured trying to email the peer
invitation to %s . Please inform the site admins (%s) if the problem persists.
""" % (', '.join(failed), admin_email)
                    })
                if succeeded:
                    output_objects.append({
                        'object_type':
                        'text',
                        'text':
                        """Sent invitation to %s with a link to a mostly pre-filled %s account request
form with the exact ID fields you provided here.""" %
                        (', '.join(succeeded), configuration.short_title)
                    })
            else:
                output_objects.append({
                    'object_type':
                    'text',
                    'text':
                    """Please tell your peers
to request an account at %s with the exact ID fields you provided here and
importantly mentioning the purpose and your email (%s) in the sign up Comment
field. Alternatively you can use the invite button to send out an email with a
link to a mostly prefilled request form.""" %
                    (configuration.short_title, client_email)
                })
    except Exception as exc:
        logger.error('Failed to save %s peers to %s: %s' %
                     (client_id, peers_path, exc))
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            '''
Could not %s peers %r. Please contact the site admins on %s if this error
persists.
''' % (action, label, admin_email)
        })
        return (output_objects, returnvalues.SYSTEM_ERROR)

    if action in ["accept", "reject"]:
        changed = [(i['distinguished_name'], i) for i in peers]
        if not manage_pending_peers(configuration, client_id, "remove",
                                    changed):
            logger.warning('could not update pending peers for %s after %s' %
                           (client_id, action))

    logger.info('%s completed for %s peers for %s in %s' %
                (action, label, client_id, peers_path))

    user_lines = []
    pretty_peers = {'label': label, 'kind': kind, 'expire': expire}
    for user in peers:
        user_lines.append(user['distinguished_name'])
    pretty_peers['user_lines'] = '\n'.join(user_lines)
    email_header = '%s Peers %s' % (configuration.short_title, action)
    email_msg = """Received %s peers from %s
""" % (action, client_id)
    email_msg += """
Kind: %(kind)s , Expire: %(expire)s, Label: %(label)s , Peers:
%(user_lines)s
""" % pretty_peers

    logger.info('Sending email: to: %s, header: %s, msg: %s, smtp_server: %s' %
                (admin_email, email_header, email_msg, smtp_server))
    if not send_email(admin_email, email_header, email_msg, logger,
                      configuration):
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            '''
An error occured trying to send the email about your %s peers to the site
administrators. Please manually inform them (%s) if the problem persists.
''' % (action, admin_email)
        })
        return (output_objects, returnvalues.SYSTEM_ERROR)

    output_objects.append({
        'object_type':
        'text',
        'text':
        '''
Informed the site admins about your %s peers action to let them accept peer
account requests you already validated.''' % action
    })

    output_objects.append({
        'object_type': 'link',
        'destination': 'peers.py',
        'text': 'Back to peers'
    })
    return (output_objects, returnvalues.OK)
Ejemplo n.º 21
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=False)
    defaults = signature()[1]
    (validate_status, accepted) = validate_input(user_arguments_dict,
                                                 defaults,
                                                 output_objects,
                                                 allow_rejects=False)
    if not validate_status:
        logger.warning('%s invalid input: %s' % (op_name, accepted))
        return (accepted, returnvalues.CLIENT_ERROR)

    if not configuration.site_enable_openid or \
            not 'migoid' in configuration.site_signup_methods:
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            '''Local OpenID login is not enabled on this site'''
        })
        return (output_objects, returnvalues.SYSTEM_ERROR)

    title_entry = find_entry(output_objects, 'title')
    title_entry['text'] = '%s OpenID account request' % \
                          configuration.short_title
    title_entry['skipmenu'] = True
    output_objects.append({
        'object_type':
        'header',
        'text':
        '%s OpenID account request' % configuration.short_title
    })

    admin_email = configuration.admin_email
    smtp_server = configuration.smtp_server
    user_pending = os.path.abspath(configuration.user_pending)

    # TODO: switch to canonical_user fra mig.shared.base instead?
    # force name to capitalized form (henrik karlsen -> Henrik Karlsen)
    # please note that we get utf8 coded bytes here and title() treats such
    # chars as word termination. Temporarily force to unicode.

    raw_name = accepted['cert_name'][-1].strip()
    try:
        cert_name = force_utf8(force_unicode(raw_name).title())
    except Exception:
        cert_name = raw_name.title()
    country = accepted['country'][-1].strip().upper()
    state = accepted['state'][-1].strip().upper()
    org = accepted['org'][-1].strip()

    # lower case email address

    email = accepted['email'][-1].strip().lower()
    password = accepted['password'][-1]
    verifypassword = accepted['verifypassword'][-1]
    # The checkbox typically returns value 'on' if selected
    passwordrecovery = (accepted['passwordrecovery'][-1].strip().lower()
                        in ('1', 'o', 'y', 't', 'on', 'yes', 'true'))

    # keep comment to a single line

    comment = accepted['comment'][-1].replace('\n', '   ')

    # single quotes break command line format - remove

    comment = comment.replace("'", ' ')
    accept_terms = (accepted['accept_terms'][-1].strip().lower()
                    in ('1', 'o', 'y', 't', 'on', 'yes', 'true'))

    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)

    if not accept_terms:
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            'You must accept the terms of use in sign up!'
        })
        output_objects.append({
            'object_type': 'link',
            'destination': 'javascript:history.back();',
            'class': 'genericbutton',
            'text': "Try again"
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    if password != verifypassword:
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            'Password and verify password are not identical!'
        })
        output_objects.append({
            'object_type': 'link',
            'destination': 'javascript:history.back();',
            'class': 'genericbutton',
            'text': "Try again"
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    try:
        assure_password_strength(configuration, password)
    except Exception as exc:
        logger.warning(
            "%s invalid password for '%s' (policy %s): %s" %
            (op_name, cert_name, configuration.site_password_policy, exc))
        output_objects.append({
            'object_type': 'error_text',
            'text': 'Invalid password requested: %s.' % exc
        })
        output_objects.append({
            'object_type': 'link',
            'destination': 'javascript:history.back();',
            'class': 'genericbutton',
            'text': "Try again"
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    if not existing_country_code(country, configuration):
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            '''Illegal country code:
Please read and follow the instructions shown in the help bubble when filling
the country field on the request page!
Specifically if you are from the U.K. you need to use GB as country code in
line with the ISO-3166 standard.
'''
        })
        output_objects.append({
            'object_type': 'link',
            'destination': 'javascript:history.back();',
            'class': 'genericbutton',
            'text': "Try again"
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    # TODO: move this check to conf?

    if not forced_org_email_match(org, email, configuration):
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            '''Illegal email and organization combination:
Please read and follow the instructions in red on the request page!
If you are a student with only a @*.ku.dk address please just use KU as
organization. As long as you state that you want the account for course
purposes in the comment field, you will be given access to the necessary
resources anyway.
'''
        })
        output_objects.append({
            'object_type': 'link',
            'destination': 'javascript:history.back();',
            'class': 'genericbutton',
            'text': "Try again"
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    # NOTE: we save password on scrambled form only if explicitly requested
    if passwordrecovery:
        logger.info('saving %s scrambled password to enable recovery' % email)
        scrambled_pw = scramble_password(configuration.site_password_salt,
                                         password)
    else:
        logger.info('only saving %s password hash' % email)
        scrambled_pw = ''
    user_dict = {
        'full_name': cert_name,
        'organization': org,
        'state': state,
        'country': country,
        'email': email,
        'comment': comment,
        'password': scrambled_pw,
        'password_hash': make_hash(password),
        'expire': default_account_expire(configuration, 'oid'),
        'openid_names': [],
        'auth': ['migoid'],
    }

    fill_distinguished_name(user_dict)
    user_id = user_dict['distinguished_name']
    user_dict['authorized'] = (user_id == client_id)
    if configuration.user_openid_providers and configuration.user_openid_alias:
        user_dict['openid_names'] += \
            [user_dict[configuration.user_openid_alias]]
    logger.info('got account request from reqoid: %s' % user_dict)

    # For testing only

    if cert_name.upper().find('DO NOT SEND') != -1:
        output_objects.append({
            'object_type': 'text',
            'text': "Test request ignored!"
        })
        return (output_objects, returnvalues.OK)

    req_path = None
    try:
        (os_fd, req_path) = tempfile.mkstemp(dir=user_pending)
        os.write(os_fd, dumps(user_dict))
        os.close(os_fd)
    except Exception as err:
        logger.error('Failed to write OpenID account request to %s: %s' %
                     (req_path, err))
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            '''Request could not be sent to site
administrators. Please contact them manually on %s if this error persists.''' %
            admin_email
        })
        return (output_objects, returnvalues.SYSTEM_ERROR)

    logger.info('Wrote OpenID account request to %s' % req_path)
    tmp_id = os.path.basename(req_path)
    user_dict['tmp_id'] = tmp_id

    mig_user = os.environ.get('USER', 'mig')
    helper_commands = user_manage_commands(configuration, mig_user, req_path,
                                           user_id, user_dict, 'oid')
    user_dict.update(helper_commands)
    user_dict['site'] = configuration.short_title
    user_dict['vgrid_label'] = configuration.site_vgrid_label
    user_dict['vgridman_links'] = generate_https_urls(
        configuration, '%(auto_base)s/%(auto_bin)s/vgridman.py', {})
    email_header = '%s OpenID request for %s' % \
                   (configuration.short_title, cert_name)
    email_msg = \
        """
Received an OpenID request with account data
 * Full Name: %(full_name)s
 * Organization: %(organization)s
 * State: %(state)s
 * Country: %(country)s
 * Email: %(email)s
 * Comment: %(comment)s
 * Expire: %(expire)s

Command to create user on %(site)s server:
%(command_user_create)s

Command to inform user and %(site)s admins:
%(command_user_notify)s

Optional command to create matching certificate:
%(command_cert_create)s

Finally add the user
%(distinguished_name)s
to any relevant %(vgrid_label)ss using one of the management links:
%(vgridman_links)s


--- If user must be denied access or deleted at some point ---

Command to reject user account request on %(site)s server:
%(command_user_reject)s

Remove the user
%(distinguished_name)s
from any relevant %(vgrid_label)ss using one of the management links:
%(vgridman_links)s

Optional command to revoke any matching user certificate:
%(command_cert_revoke)s
You need to copy the resulting signed certificate revocation list (crl.pem)
to the web server(s) for the revocation to take effect.

Command to suspend user on %(site)s server:
%(command_user_suspend)s

Command to delete user again on %(site)s server:
%(command_user_delete)s

---

""" % user_dict

    logger.info('Sending email: to: %s, header: %s, msg: %s, smtp_server: %s' %
                (admin_email, email_header, email_msg, smtp_server))
    if not send_email(admin_email, email_header, email_msg, logger,
                      configuration):
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            '''An error occured trying to send the email
requesting the site administrators to create a new OpenID and account. Please
email them (%s) manually and include the session ID: %s''' %
            (admin_email, tmp_id)
        })
        return (output_objects, returnvalues.SYSTEM_ERROR)

    output_objects.append({
        'object_type':
        'text',
        'text':
        """Request sent to site administrators:
Your OpenID account request will be verified and handled as soon as possible,
so please be patient.
Once handled an email will be sent to the account you have specified ('%s') with
further information. In case of inquiries about this request, please email
the site administrators (%s) and include the session ID: %s""" %
        (email, configuration.admin_email, tmp_id)
    })
    return (output_objects, returnvalues.OK)
Ejemplo n.º 22
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,
        # NOTE: path can use wildcards, current_dir cannot
        typecheck_overrides={'path': valid_path_pattern},
    )
    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 as 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
    else:
        logger.error('%s called without proper auth: %s' % (op_name, accepted))
        output_objects.append({'object_type': 'error_text', 'text': 'Authentication is missing!'
                               })
        return (output_objects, returnvalues.SYSTEM_ERROR)

    visibility_toggle = '''
        <style>
        %s
        </style>
        ''' % (visibility_mods % {'main_class': main_class})

    # 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(base_dir, target_dir)) + os.sep

    if not os.path.isdir(base_dir):
        logger.error('%s called on missing base_dir: %s' % (op_name, base_dir))
        output_objects.append({'object_type': 'error_text', 'text': 'No such %s!' % page_title.lower()
                               })
        return (output_objects, returnvalues.CLIENT_ERROR)

    title_entry = find_entry(output_objects, 'title')
    title_entry['text'] = page_title
    title_entry['skipwidgets'] = not widgets
    title_entry['skipuserstyle'] = not userstyle

    fill_helpers = {'dest_dir': current_dir + os.sep, 'share_id': share_id,
                    'flags': flags, 'tmp_flags': flags, 'long_set':
                    long_list(flags), 'recursive_set': recursive(flags),
                    'all_set': all(flags)}
    add_import, add_init, add_ready = '', '', ''
    title_entry['style']['advanced'] += '''
    %s
    ''' % visibility_toggle
    title_entry['script']['advanced'] += add_import
    title_entry['script']['init'] += add_init
    title_entry['script']['ready'] += add_ready
    title_entry['script']['body'] = ' class="%s"' % main_class
    output_objects.append({'object_type': 'header', 'text': page_title})

    # Shared URL helpers
    ls_url_template = 'ls.py?%scurrent_dir=%%(rel_dir_enc)s;flags=%s' % \
                      (id_args, flags)
    redirect_url_template = '/%s/%%(rel_path_enc)s' % redirect_path

    location_pre_html = """
<div class='files'>
<table class='files'>
<tr class=title><td class=centertext>
Working directory:
</td></tr>
<tr><td class='centertext'>
"""
    output_objects.append(
        {'object_type': 'html_form', 'text': location_pre_html})
    # Use current_dir nav location links
    for pattern in pattern_list[:1]:
        links = []
        links.append({'object_type': 'link', 'text': root_link_name,
                      'destination': ls_url_template % {'rel_dir_enc': '.'}})
        prefix = ''
        parts = os.path.normpath(current_dir).split(os.sep)
        for i in parts:
            if i == ".":
                continue
            prefix = os.path.join(prefix, i)
            links.append({'object_type': 'link', 'text': i,
                          'destination': ls_url_template %
                          {'rel_dir_enc': quote(prefix)}})
        output_objects.append(
            {'object_type': 'multilinkline', 'links': links, 'sep': ' %s ' % os.sep})
    location_post_html = """
</td></tr>
</table>
</div>
<br />
"""

    output_objects.append(
        {'object_type': 'html_form', 'text': location_post_html})

    dir_listings = []
    output_objects.append({
        'object_type': 'dir_listings',
        'dir_listings': dir_listings,
        'flags': flags,
        'redirect_name': redirect_name,
        'redirect_path': redirect_path,
        'share_id': share_id,
        'ls_url_template': ls_url_template,
        'rm_url_template': '',
        'rmdir_url_template': '',
        'editor_url_template': '',
        'redirect_url_template': redirect_url_template,
        'show_dest': show_dest,
    })

    first_match = None
    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

        current_path = os.path.normpath(os.path.join(base_dir, current_dir))
        unfiltered_match = glob.glob(current_path + os.sep + 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)'
                               % (user_id, op_name, abs_path, pattern))
                continue
            match.append(abs_path)
            if not first_match:
                first_match = abs_path

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

        if not match:
            output_objects.append({'object_type': 'file_not_found',
                                   'name': pattern})
            status = returnvalues.FILE_NOT_FOUND

        for abs_path in match:
            if abs_path + os.sep == base_dir:
                relative_path = '.'
            else:
                relative_path = abs_path.replace(base_dir, '')
            entries = []
            dir_listing = {
                'object_type': 'dir_listing',
                'relative_path': relative_path,
                'entries': entries,
                'flags': flags,
            }

            dest = ''
            if show_dest:
                if os.path.isfile(abs_path):
                    dest = os.path.basename(abs_path)
                elif recursive(flags):

                    # references to '.' or similar are stripped by abspath

                    if abs_path + os.sep == base_dir:
                        dest = ''
                    else:

                        # dest = os.path.dirname(abs_path).replace(base_dir, "")

                        dest = os.path.basename(abs_path) + os.sep

            handle_expand(configuration, output_objects, entries, base_dir,
                          abs_path, flags, dest, 0, show_dest)
            dir_listings.append(dir_listing)

    output_objects.append({'object_type': 'html_form', 'text': """
    <div class='files disable_read'>
    <form method='get' action='ls.py'>
    <table class='files'>
    <tr class=title><td class=centertext>
    Filter paths (wildcards like * and ? are allowed)
    <input type='hidden' name='output_format' value='html' />
    <input type='hidden' name='flags' value='%(flags)s' />
    <input type='hidden' name='share_id' value='%(share_id)s' />
    <input name='current_dir' type='hidden' value='%(dest_dir)s' />
    <input type='text' name='path' value='' />
    <input type='submit' value='Filter' />
    </td></tr>
    </table>    
    </form>
    </div>
    """ % fill_helpers})

    # Short/long format buttons

    fill_helpers['tmp_flags'] = flags + 'l'
    htmlform = """
    <table class='files if_full'>
    <tr class=title><td class=centertext colspan=4>
    File view options
    </td></tr>
    <tr><td colspan=4><br /></td></tr>
    <tr class=title><td>Parameter</td><td>Setting</td><td>Enable</td><td>Disable</td></tr>
    <tr><td>Long format</td><td>
    %(long_set)s</td><td>
    <form method='get' action='ls.py'>
    <input type='hidden' name='output_format' value='html' />
    <input type='hidden' name='flags' value='%(tmp_flags)s' />
    <input type='hidden' name='share_id' value='%(share_id)s' />
    <input name='current_dir' type='hidden' value='%(dest_dir)s' />
    """ % fill_helpers

    for entry in pattern_list:
        htmlform += "<input type='hidden' name='path' value='%s' />" % entry
    fill_helpers['tmp_flags'] = flags.replace('l', '')
    htmlform += """
    <input type='submit' value='On' /><br />
    </form>
    </td><td>
    <form method='get' action='ls.py'>
    <input type='hidden' name='output_format' value='html' />
    <input type='hidden' name='flags' value='%(tmp_flags)s' />
    <input type='hidden' name='share_id' value='%(share_id)s' />
    <input name='current_dir' type='hidden' value='%(dest_dir)s' />
    """ % fill_helpers
    for entry in pattern_list:
        htmlform += "<input type='hidden' name='path' value='%s' />" % entry
    htmlform += """
    <input type='submit' value='Off' /><br />
    </form>
    </td></tr>
    """

    # Recursive output

    fill_helpers['tmp_flags'] = flags + 'r'
    htmlform += """
    <!-- Non-/recursive list buttons -->
    <tr><td>Recursion</td><td>
    %(recursive_set)s</td><td>""" % fill_helpers
    htmlform += """
    <form method='get' action='ls.py'>
    <input type='hidden' name='output_format' value='html' />
    <input type='hidden' name='flags' value='%(tmp_flags)s' />
    <input type='hidden' name='share_id' value='%(share_id)s' />
    <input name='current_dir' type='hidden' value='%(dest_dir)s' />
    """ % fill_helpers
    for entry in pattern_list:
        htmlform += "<input type='hidden' name='path' value='%s' />" % entry
    fill_helpers['tmp_flags'] = flags.replace('r', '')
    htmlform += """
    <input type='submit' value='On' /><br />
    </form>
    </td><td>
    <form method='get' action='ls.py'>
    <input type='hidden' name='output_format' value='html' />
    <input type='hidden' name='flags' value='%(tmp_flags)s' />
    <input type='hidden' name='share_id' value='%(share_id)s' />
    <input name='current_dir' type='hidden' value='%(dest_dir)s' />
    """ % fill_helpers

    for entry in pattern_list:
        htmlform += "<input type='hidden' name='path' value='%s' />"\
                    % entry
        htmlform += """
    <input type='submit' value='Off' /><br />
    </form>
    </td></tr>
    """

    htmlform += """
    <!-- Show dot files buttons -->
    <tr><td>Show hidden files</td><td>
    %(all_set)s</td><td>""" % fill_helpers
    fill_helpers['tmp_flags'] = flags + 'a'
    htmlform += """
    <form method='get' action='ls.py'>
    <input type='hidden' name='output_format' value='html' />
    <input type='hidden' name='flags' value='%(tmp_flags)s' />
    <input type='hidden' name='share_id' value='%(share_id)s' />
    <input name='current_dir' type='hidden' value='%(dest_dir)s' />
    """ % fill_helpers
    for entry in pattern_list:
        htmlform += "<input type='hidden' name='path' value='%s' />" % entry
    fill_helpers['tmp_flags'] = flags.replace('a', '')
    htmlform += """
    <input type='submit' value='On' /><br />
    </form>
    </td><td>
    <form method='get' action='ls.py'>
    <input type='hidden' name='output_format' value='html' />
    <input type='hidden' name='flags' value='%(tmp_flags)s' />
    <input type='hidden' name='share_id' value='%(share_id)s' />
    <input name='current_dir' type='hidden' value='%(dest_dir)s' />
    """ % fill_helpers
    for entry in pattern_list:
        htmlform += "<input type='hidden' name='path' value='%s' />" % entry
    htmlform += """
    <input type='submit' value='Off' /><br />
    </form>
    </td></tr>
    </table>
    """

    # show flag buttons after contents to limit clutter

    output_objects.append({'object_type': 'html_form', 'text': htmlform})

    return (output_objects, status)