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)
def main(client_id, user_arguments_dict): """Main function used by front end""" (configuration, logger, output_objects, op_name) = \ initialize_main_variables(client_id) output_objects.append({'object_type': 'text', 'text' : '--------- Trying to Clean front end ----------' }) defaults = signature()[1] (validate_status, accepted) = validate_input_and_cert( user_arguments_dict, defaults, output_objects, client_id, configuration, allow_rejects=False, ) if not validate_status: return (accepted, returnvalues.CLIENT_ERROR) unique_resource_name = accepted['unique_resource_name'][-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) 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 ' + unique_resource_name + ' to clean the front end!'}) return (output_objects, returnvalues.CLIENT_ERROR) exit_status = returnvalues.OK (status, msg) = stop_resource_frontend(unique_resource_name, configuration, logger) if not status: output_objects.append({'object_type': 'error_text', 'text' : 'Problems stopping front end during clean: %s' % msg}) return (output_objects, returnvalues.CLIENT_ERROR) (status2, msg2) = clean_resource_frontend(unique_resource_name, configuration.resource_home, logger) if not status2: output_objects.append({'object_type': 'error_text', 'text' : 'Problems cleaning front end during clean: %s' % msg2}) exit_status = returnvalues.SYSTEM_ERROR if status and status2: output_objects.append({'object_type': 'text', 'text' : 'Clean front end success: Stop output: %s Clean output %s' % (msg, msg2)}) return (output_objects, exit_status)
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)
def main(client_id, user_arguments_dict): """Main function used by front end""" (configuration, logger, output_objects, op_name) = \ initialize_main_variables(client_id, op_header=False) client_dir = client_id_dir(client_id) defaults = signature()[1] (validate_status, accepted) = validate_input_and_cert( user_arguments_dict, defaults, output_objects, client_id, configuration, allow_rejects=False, ) if not validate_status: return (accepted, returnvalues.CLIENT_ERROR) if not configuration.site_enable_jobs: output_objects.append({ 'object_type': 'error_text', 'text': '''Job execution is not enabled on this system''' }) return (output_objects, returnvalues.SYSTEM_ERROR) status = returnvalues.OK title_entry = find_entry(output_objects, 'title') title_entry['text'] = 'Job Manager' user_settings = title_entry.get('user_settings', {}) title_entry['style'] = css_tmpl(configuration, user_settings) csrf_map = {} method = 'post' limit = get_csrf_limit(configuration) for target_op in csrf_backends: csrf_map[target_op] = make_csrf_token(configuration, method, target_op, client_id, limit) (add_import, add_init, add_ready) = js_tmpl_parts(csrf_map) title_entry['script']['advanced'] += add_import title_entry['script']['init'] += add_init title_entry['script']['ready'] += add_ready output_objects.append({'object_type': 'header', 'text': 'Job Manager'}) output_objects.append({ 'object_type': 'table_pager', 'entry_name': 'jobs', 'default_entries': default_pager_entries, 'form_append': pager_append() }) output_objects.append({'object_type': 'html_form', 'text': html_post()}) return (output_objects, status)
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) client_dir = client_id_dir(client_id) defaults = signature()[1] (validate_status, accepted) = validate_input_and_cert( user_arguments_dict, defaults, output_objects, client_id, configuration, allow_rejects=False, ) if not validate_status: return (accepted, returnvalues.CLIENT_ERROR) job_id_list = accepted['job_id'] external_dict = mrslkeywords.get_keywords_dict(configuration) if not configuration.site_enable_jobs: output_objects.append({'object_type': 'error_text', 'text': '''Job execution is not enabled on this system'''}) 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(configuration.mrsl_files_dir, client_dir)) + os.sep status = returnvalues.OK for job_id in job_id_list: # job = Job() filepath = os.path.join(base_dir, job_id) filepath += '.mRSL' (new_job_obj_status, new_job_obj) = \ create_job_object_from_pickled_mrsl(filepath, logger, external_dict) if not new_job_obj_status: output_objects.append({'object_type': 'error_text', 'text' : new_job_obj}) status = returnvalues.CLIENT_ERROR else: # return new_job_obj output_objects.append({'object_type': 'jobobj', 'jobobj' : new_job_obj}) return (output_objects, status)
def main(client_id, user_arguments_dict): """Main function used by front end""" (configuration, logger, output_objects, op_name) = \ initialize_main_variables(client_id) defaults = signature()[1] (validate_status, accepted) = validate_input_and_cert( user_arguments_dict, defaults, output_objects, client_id, configuration, allow_rejects=False, ) if not validate_status: return (accepted, returnvalues.CLIENT_ERROR) unique_resource_name = accepted['unique_resource_name'][-1] if not is_owner(client_id, unique_resource_name, configuration.resource_home, logger): output_objects.append({ 'object_type': 'error_text', 'text': 'You must be an owner of %s to get the list of owners!' % 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 base_dir = os.path.abspath( os.path.join(configuration.resource_home, unique_resource_name)) + os.sep owners_file = os.path.join(base_dir, 'owners') (list_status, msg) = list_items_in_pickled_list(owners_file, logger) if not list_status: output_objects.append({ 'object_type': 'error_text', 'text': 'Could not get list of owners, reason: %s' % msg }) return (output_objects, returnvalues.SYSTEM_ERROR) output_objects.append({'object_type': 'list', 'list': msg}) return (output_objects, returnvalues.OK)
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_and_cert( user_arguments_dict, defaults, output_objects, client_id, configuration, allow_rejects=False, ) if not validate_status: return (accepted, returnvalues.CLIENT_ERROR) # output_objects.append({'object_type': 'header', 'text': 'Welcome to %s!' % # configuration.short_title}) # Generate and insert the page HTML title_entry = find_entry(output_objects, 'title') title_entry['text'] = '%s Home' % configuration.short_title # jquery support for AJAX saving (add_import, add_init, add_ready) = save_settings_js(configuration) add_init += ''' function addApp() { $("#app-nav-container").hide(); $("#add-app__window").show(); } function closeAddApp() { console.log("close add app"); $("#app-nav-container").show(); $("#add-app__window").hide(); } ''' add_ready += ''' load_tips("%s"); ''' % configuration.site_tips_snippet_url title_entry['script']['advanced'] += add_import title_entry['script']['init'] += add_init title_entry['script']['ready'] += add_ready html = html_tmpl(configuration, client_id, title_entry) output_objects.append({'object_type': 'html_form', 'text': html}) return (output_objects, returnvalues.OK)
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) # IMPORTANT: no title in init above so we MUST call it immediately here # or basic styling will break e.g. on crashes. styles = themed_styles(configuration) scripts = themed_scripts(configuration, logged_in=False) output_objects.append({ 'object_type': 'title', 'text': 'Auto Launch', 'skipmenu': True, 'style': styles, 'script': scripts }) defaults = signature()[1] (validate_status, accepted) = validate_input_and_cert( user_arguments_dict, defaults, output_objects, client_id, configuration, allow_rejects=False, ) if not validate_status: return (accepted, returnvalues.CLIENT_ERROR) user_settings = load_settings(client_id, configuration) # NOTE: loaded settings may be the boolean False rather than dict here if not user_settings: user_settings = {} default_page = user_settings.get('DEFAULT_PAGE', None) if default_page and default_page in menu_items: redirect_location = menu_items[default_page]['url'] else: redirect_location = configuration.site_landing_page headers = [('Status', '302 Moved'), ('Location', redirect_location)] logger.debug("user settings and site landing page gave %s" % redirect_location) output_objects.append({'object_type': 'start', 'headers': headers}) output_objects.append({'object_type': 'script_status'}) return (output_objects, returnvalues.OK)
def main(client_id, user_arguments_dict): """Main function used by front end""" (configuration, logger, output_objects, op_name) = \ initialize_main_variables(client_id) defaults = signature()[1] (validate_status, accepted) = validate_input_and_cert( user_arguments_dict, defaults, output_objects, client_id, configuration, allow_rejects=False, ) if not validate_status: return (accepted, returnvalues.CLIENT_ERROR) vgrid_name = accepted['vgrid_name'][-1] # Validity of user and vgrid names is checked in this init function so # no need to worry about illegal directory traversal through variables (ret_val, msg, ret_variables) = init_vgrid_script_list(vgrid_name, client_id, configuration) if not ret_val: output_objects.append({'object_type': 'error_text', 'text': msg}) return (output_objects, returnvalues.CLIENT_ERROR) # list (list_status, msg) = vgrid_list(vgrid_name, 'members', configuration, allow_missing=True) if not list_status: output_objects.append({ 'object_type': 'error_text', 'text': '%s' % msg }) return (output_objects, returnvalues.SYSTEM_ERROR) output_objects.append({'object_type': 'list', 'list': msg}) return (output_objects, returnvalues.OK)
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)
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) defaults = signature()[1] title_entry = find_entry(output_objects, 'title') label = "%s" % configuration.site_vgrid_label title_entry['text'] = "Reject %s Request" % label output_objects.append({ 'object_type': 'header', 'text': 'Reject %s Request' % label }) (validate_status, accepted) = validate_input_and_cert( user_arguments_dict, defaults, output_objects, client_id, configuration, allow_rejects=False, ) if not validate_status: return (accepted, returnvalues.CLIENT_ERROR) vgrid_name = accepted['vgrid_name'][-1].strip() request_name = unhexlify(accepted['request_name'][-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) # Validity of user and vgrid names is checked in this init function so # no need to worry about illegal directory traversal through variables (ret_val, msg, ret_variables) = \ init_vgrid_script_add_rem(vgrid_name, client_id, request_name, 'request', configuration) if not ret_val: output_objects.append({'object_type': 'error_text', 'text': msg}) return (output_objects, returnvalues.CLIENT_ERROR) elif msg: # In case of warnings, msg is non-empty while ret_val remains True output_objects.append({'object_type': 'warning', 'text': msg}) if request_name: request_dir = os.path.join(configuration.vgrid_home, vgrid_name) req = load_access_request(configuration, request_dir, request_name) if not req or not delete_access_request(configuration, request_dir, request_name): logger.error("failed to delete owner request for %s in %s" % \ (vgrid_name, request_name)) output_objects.append({ 'object_type': 'error_text', 'text': 'Failed to remove saved vgrid request for %s in %s!'\ % (vgrid_name, request_name)}) return (output_objects, returnvalues.CLIENT_ERROR) output_objects.append({ 'object_type': 'text', 'text': ''' Deleted %(request_type)s access request to %(target)s for %(entity)s . ''' % req }) if req['request_type'] == 'vgridresource': id_field = "unique_resource_name" else: id_field = "cert_id" form_method = 'post' csrf_limit = get_csrf_limit(configuration) fill_helpers = { 'protocol': any_protocol, 'id_field': id_field, 'vgrid_label': label, 'form_method': form_method, 'csrf_field': csrf_field, 'csrf_limit': csrf_limit } fill_helpers.update(req) target_op = 'sendrequestaction' 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': 'html_form', 'text': """ <p> You can use the reply form below if you want to additionally send an explanation for rejecting the request. </p> <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=request_type value='vgridreject' /> <input type=hidden name=vgrid_name value='%(target)s' /> <input type=hidden name=%(id_field)s value='%(entity)s' /> <input type=hidden name=protocol value='%(protocol)s' /> <table> <tr> <td class='title'>Optional reject message to requestor(s)</td> </tr><tr> <td><textarea name=request_text cols=72 rows=10> We have decided to reject your %(request_type)s request to our %(target)s %(vgrid_label)s. Regards, the %(target)s %(vgrid_label)s owners </textarea></td> </tr> <tr> <td><input type='submit' value='Inform requestor(s)' /></td> </tr> </table> </form> <br /> """ % fill_helpers }) output_objects.append({ 'object_type': 'link', 'destination': 'adminvgrid.py?vgrid_name=%s' % vgrid_name, 'text': 'Back to administration for %s' % vgrid_name }) return (output_objects, returnvalues.OK)
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)
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) defaults = signature()[1] (validate_status, accepted) = validate_input_and_cert( user_arguments_dict, defaults, output_objects, client_id, configuration, allow_rejects=False, ) if not validate_status: return (accepted, returnvalues.CLIENT_ERROR) unique_res_names = accepted['unique_resource_name'] if not configuration.site_enable_resources: output_objects.append({ 'object_type': 'error_text', 'text': '''Resources are not enabled on this system''' }) return (output_objects, returnvalues.SYSTEM_ERROR) # prepare for confirm dialog, tablesort and toggling the views (css/js) title_entry = find_entry(output_objects, 'title') title_entry['text'] = "Resource Administration" # jquery support for tablesorter and confirmation on request and leave # requests table initially sorted by 4, 3 (date first and with alphabetical # client ID) table_specs = [{ 'table_id': 'accessrequeststable', 'pager_id': 'accessrequests_pager', 'sort_order': '[[4,0],[3,0]]' }] (add_import, add_init, add_ready) = man_base_js(configuration, table_specs, {'width': 600}) add_init += ''' var toggleHidden = function(classname) { // classname supposed to have a leading dot $(classname).toggleClass("hidden"); }; /* helper for dynamic form input fields */ function onOwnerInputChange() { makeSpareFields("#dynownerspares", "cert_id"); } ''' add_ready += ''' /* init add owners form with dynamic input fields */ onOwnerInputChange(); $("#dynownerspares").on("blur", "input[name=cert_id]", function(event) { //console.debug("in add owner blur handler"); onOwnerInputChange(); } ); ''' title_entry['script']['advanced'] += add_import title_entry['script']['init'] += add_init title_entry['script']['ready'] += add_ready output_objects.append({ 'object_type': 'html_form', 'text': man_base_html(configuration) }) form_method = 'post' csrf_limit = get_csrf_limit(configuration) fill_helpers = { 'short_title': configuration.short_title, 'vgrid_label': configuration.site_vgrid_label, 'form_method': form_method, 'csrf_field': csrf_field, 'csrf_limit': csrf_limit } (re_stat, re_list) = list_runtime_environments(configuration) if not re_stat: logger.warning('Failed to load list of runtime environments') output_objects.append({ 'object_type': 'error_text', 'text': 'Error getting list of runtime environments' }) return (output_objects, returnvalues.SYSTEM_ERROR) output_objects.append({'object_type': 'header', 'text': 'Manage Resource'}) output_objects.append({ 'object_type': 'sectionheader', 'text': '%(short_title)s Resources Owned' % fill_helpers }) quick_links = [{ 'object_type': 'text', 'text': 'Quick links to all your resources and individual management' }] quick_links.append({ 'object_type': 'html_form', 'text': '<div class="hidden quicklinks">' }) quick_links.append({ 'object_type': 'link', 'destination': "javascript:toggleHidden('.quicklinks');", 'class': 'removeitemlink iconspace', 'title': 'Toggle view', 'text': 'Hide quick links' }) quick_links.append({'object_type': 'text', 'text': ''}) quick_res = {} quick_links_index = len(output_objects) output_objects.append({'object_type': 'sectionheader', 'text': ''}) owned = 0 res_map = get_resource_map(configuration) for unique_resource_name in res_map.keys(): if sandbox_resource(unique_resource_name): continue owner_list = res_map[unique_resource_name][OWNERS] resource_config = res_map[unique_resource_name][CONF] visible_res_name = res_map[unique_resource_name][RESID] if client_id in owner_list: quick_res[unique_resource_name] = { 'object_type': 'multilinkline', 'links': [{ 'object_type': 'link', 'destination': '?unique_resource_name=%s' % unique_resource_name, 'class': 'adminlink iconspace', 'title': 'Manage %s' % unique_resource_name, 'text': 'Manage %s' % unique_resource_name, }, { 'object_type': 'link', 'destination': 'viewres.py?unique_resource_name=%s' % visible_res_name, 'class': 'infolink iconspace', 'title': 'View %s' % unique_resource_name, 'text': 'View %s' % unique_resource_name, }] } if unique_resource_name in unique_res_names: raw_conf_file = os.path.join(configuration.resource_home, unique_resource_name, 'config.MiG') try: filehandle = open(raw_conf_file, 'r') raw_conf = filehandle.readlines() filehandle.close() except: raw_conf = [''] res_html = display_resource(client_id, unique_resource_name, raw_conf, resource_config, owner_list, re_list, configuration, fill_helpers) output_objects.append({ 'object_type': 'html_form', 'text': res_html }) # Pending requests target_op = "addresowner" csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) helper = html_post_helper( target_op, "%s.py" % target_op, { 'unique_resource_name': unique_resource_name, 'cert_id': '__DYNAMIC__', 'request_name': '__DYNAMIC__', csrf_field: csrf_token }) output_objects.append({ 'object_type': 'html_form', 'text': helper }) target_op = "rejectresreq" csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) helper = html_post_helper( target_op, "%s.py" % target_op, { 'unique_resource_name': unique_resource_name, 'request_name': '__DYNAMIC__', csrf_field: csrf_token }) output_objects.append({ 'object_type': 'html_form', 'text': helper }) request_dir = os.path.join(configuration.resource_home, unique_resource_name) request_list = [] for req_name in list_access_requests(configuration, request_dir): req = load_access_request(configuration, request_dir, req_name) if not req: continue if req.get('request_type', None) != "resourceowner": logger.error( "unexpected request_type %(request_type)s" % req) continue request_item = build_accessrequestitem_object( configuration, req) # Convert filename with exotic chars into url-friendly pure hex version shared_args = { "unique_resource_name": unique_resource_name, "request_name": hexlify(req["request_name"]) } accept_args, reject_args = {}, {} accept_args.update(shared_args) reject_args.update(shared_args) if req['request_type'] == "resourceowner": accept_args["cert_id"] = req["entity"] request_item['acceptrequestlink'] = { 'object_type': 'link', 'destination': "javascript: confirmDialog(%s, '%s', %s, %s);" % ("addresowner", "Accept %(target)s %(request_type)s request from %(entity)s" % req, 'undefined', "{%s}" % ', '.join([ "'%s': '%s'" % pair for pair in accept_args.items() ])), 'class': 'addlink iconspace', 'title': 'Accept %(target)s %(request_type)s request from %(entity)s' % req, 'text': '' } request_item['rejectrequestlink'] = { 'object_type': 'link', 'destination': "javascript: confirmDialog(%s, '%s', %s, %s);" % ("rejectresreq", "Reject %(target)s %(request_type)s request from %(entity)s" % req, 'undefined', "%s" % reject_args), 'class': 'removelink iconspace', 'title': 'Reject %(target)s %(request_type)s request from %(entity)s' % req, 'text': '' } request_list.append(request_item) output_objects.append({ 'object_type': 'sectionheader', 'text': "Pending Requests" }) output_objects.append({ 'object_type': 'table_pager', 'id_prefix': 'accessrequests_', 'entry_name': 'access requests', 'default_entries': default_pager_entries }) output_objects.append({ 'object_type': 'accessrequests', 'accessrequests': request_list }) output_objects.append({ 'object_type': 'sectionheader', 'text': 'Retire resource' }) output_objects.append({ 'object_type': 'text', 'text': ''' Use the link below to permanently remove the resource from the grid after stopping all units and the front end. ''' }) target_op = "delres" csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) js_name = 'delres%s' % hexlify(unique_resource_name) helper = html_post_helper( js_name, '%s.py' % target_op, { 'unique_resource_name': unique_resource_name, csrf_field: csrf_token }) output_objects.append({ 'object_type': 'html_form', 'text': helper }) output_objects.append({ 'object_type': 'link', 'destination': "javascript: confirmDialog(%s, '%s');" % (js_name, 'Really delete %s? (fails if it is busy)' % unique_resource_name), 'class': 'removelink iconspace', 'title': 'Delete %s' % unique_resource_name, 'text': 'Delete %s' % unique_resource_name }) owned += 1 if owned == 0: output_objects.append({ 'object_type': 'text', 'text': 'You are not listed as owner of any resources!' }) else: sorted_links = quick_res.items() sorted_links.sort() for (res_id, link_obj) in sorted_links: quick_links.append(link_obj) # add new line quick_links.append({'object_type': 'text', 'text': ''}) quick_links.append({ 'object_type': 'html_form', 'text': '</div><div class="quicklinks">' }) quick_links.append({ 'object_type': 'link', 'destination': "javascript:toggleHidden('.quicklinks');", 'class': 'additemlink iconspace', 'title': 'Toggle view', 'text': 'Show quick links' }) quick_links.append({'object_type': 'html_form', 'text': '</div>'}) output_objects = output_objects[:quick_links_index]\ + quick_links + output_objects[quick_links_index:] return (output_objects, returnvalues.OK)
def main(client_id, user_arguments_dict): """Main function used by front end""" (configuration, logger, output_objects, op_name) = \ initialize_main_variables(client_id) client_dir = client_id_dir(client_id) defaults = signature()[1] (validate_status, accepted) = validate_input_and_cert( user_arguments_dict, defaults, output_objects, client_id, configuration, allow_rejects=False, ) if not validate_status: return (accepted, returnvalues.CLIENT_ERROR) patterns = accepted['job_id'] 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 configuration.site_enable_jobs: output_objects.append({ 'object_type': 'error_text', 'text': '''Job execution is not enabled on this system''' }) return (output_objects, returnvalues.SYSTEM_ERROR) if not patterns: output_objects.append({ 'object_type': 'error_text', 'text': 'No job_id specified!' }) return (output_objects, returnvalues.NO_SUCH_JOB_ID) # 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.mrsl_files_dir, client_dir)) + os.sep filelist = [] keywords_dict = mrslkeywords.get_keywords_dict(configuration) for pattern in patterns: pattern = pattern.strip() # Backward compatibility - all_jobs keyword should match all jobs if pattern == all_jobs: pattern = '*' # 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 + '.mRSL') 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 # Insert valid job files in filelist for later treatment 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: You do not have any matching job IDs!' % pattern }) status = returnvalues.CLIENT_ERROR else: filelist += match # resubmit is hard on the server if len(filelist) > 100: output_objects.append({ 'object_type': 'error_text', 'text': 'Too many matching jobs (%s)!' % len(filelist) }) return (output_objects, returnvalues.CLIENT_ERROR) resubmitobjs = [] status = returnvalues.OK for filepath in filelist: mrsl_file = filepath.replace(base_dir, '') job_id = mrsl_file.replace('.mRSL', '') # ("Resubmitting job with job_id: %s" % job_id) resubmitobj = {'object_type': 'resubmitobj', 'job_id': job_id} mrsl_dict = unpickle(filepath, logger) if not mrsl_dict: resubmitobj['message'] = "No such job: %s (%s)" % (job_id, mrsl_file) status = returnvalues.CLIENT_ERROR resubmitobjs.append(resubmitobj) continue resubmit_items = keywords_dict.keys() # loop selected keywords and create mRSL string resubmit_job_string = '' for dict_elem in resubmit_items: value = '' # Extract job value with fallback to default to support optional # fields job_value = mrsl_dict.get(dict_elem, keywords_dict[dict_elem]['Value']) if keywords_dict[dict_elem]['Type'].startswith( 'multiplekeyvalues'): for (elem_key, elem_val) in job_value: if elem_key: value += '%s=%s\n' % (str(elem_key).strip(), str(elem_val).strip()) elif keywords_dict[dict_elem]['Type'].startswith('multiple'): for elem in job_value: if elem: value += '%s\n' % str(elem).rstrip() else: if str(job_value): value += '%s\n' % str(job_value).rstrip() # Only insert keywords with an associated value if value: if value.rstrip() != '': resubmit_job_string += '''::%s:: %s ''' % (dict_elem, value.rstrip()) # save tempfile (filehandle, tempfilename) = \ tempfile.mkstemp(dir=configuration.mig_system_files, text=True) os.write(filehandle, resubmit_job_string) os.close(filehandle) # submit job the usual way (new_job_status, msg, new_job_id) = new_job(tempfilename, client_id, configuration, False, True) if not new_job_status: resubmitobj['status'] = False resubmitobj['message'] = msg status = returnvalues.SYSTEM_ERROR resubmitobjs.append(resubmitobj) continue # o.out("Resubmit failed: %s" % msg) # o.reply_and_exit(o.ERROR) resubmitobj['status'] = True resubmitobj['new_job_id'] = new_job_id resubmitobjs.append(resubmitobj) # o.out("Resubmit successful: %s" % msg) # o.out("%s" % msg) output_objects.append({ 'object_type': 'resubmitobjs', 'resubmitobjs': resubmitobjs }) return (output_objects, status)
def main(client_id, user_arguments_dict): """Main function used by front end""" (configuration, logger, output_objects, op_name) = \ initialize_main_variables(client_id, op_header=False) client_dir = client_id_dir(client_id) defaults = signature()[1] (validate_status, accepted) = validate_input_and_cert( user_arguments_dict, defaults, output_objects, client_id, configuration, allow_rejects=False, # NOTE: path cannot use wildcards here typecheck_overrides={}, ) if not validate_status: return (accepted, returnvalues.CLIENT_ERROR) path = accepted['path'][-1] chosen_newline = accepted['newline'][-1] submitjob = accepted['submitjob'][-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) if not configuration.site_enable_jobs and submitjob: output_objects.append({ 'object_type': 'error_text', 'text': '''Job execution is not enabled on this system''' }) 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(configuration.user_home, client_dir)) + os.sep # HTML spec dictates newlines in forms to be MS style (\r\n) # rather than un*x style (\n): change if requested. form_newline = '\r\n' allowed_newline = {'unix': '\n', 'mac': '\r', 'windows': '\r\n'} output_objects.append({ 'object_type': 'header', 'text': 'Saving changes to edited file' }) if not chosen_newline in allowed_newline.keys(): output_objects.append({ 'object_type': 'error_text', 'text': 'Unsupported newline style supplied: %s (must be one of %s)' % (chosen_newline, ', '.join(allowed_newline.keys())) }) return (output_objects, returnvalues.CLIENT_ERROR) saved_newline = allowed_newline[chosen_newline] # Check directory traversal attempts before actual handling to avoid # leaking information about file system layout while allowing consistent # error messages abs_path = '' unfiltered_match = glob.glob(base_dir + path) for server_path in unfiltered_match: # IMPORTANT: path must be expanded to abs for proper chrooting abs_path = os.path.abspath(server_path) if not valid_user_path(configuration, abs_path, base_dir, True): logger.warning('%s tried to %s restricted path %s ! (%s)' % (client_id, op_name, abs_path, path)) output_objects.append({ 'object_type': 'error_text', 'text': "Invalid path! (%s expands to an illegal path)" % path }) return (output_objects, returnvalues.CLIENT_ERROR) if abs_path == '': # IMPORTANT: path must be expanded to abs for proper chrooting abs_path = os.path.abspath(os.path.join(base_dir, path.lstrip(os.sep))) if not valid_user_path(configuration, abs_path, base_dir, True): logger.warning('%s tried to %s restricted path %s ! (%s)' % (client_id, op_name, abs_path, path)) output_objects.append({ 'object_type': 'error_text', 'text': "Invalid path! (%s expands to an illegal path)" % path }) return (output_objects, returnvalues.CLIENT_ERROR) 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 edit "%s": inside a read-only location!' % path }) status = returnvalues.CLIENT_ERROR return (output_objects, returnvalues.CLIENT_ERROR) (owner, time_left) = acquire_edit_lock(abs_path, client_id) if owner != client_id: output_objects.append({ 'object_type': 'error_text', 'text': "You don't have the lock for %s!" % path }) return (output_objects, returnvalues.CLIENT_ERROR) try: fh = open(abs_path, 'w+') fh.write(user_arguments_dict['editarea'][0].replace( form_newline, saved_newline)) fh.close() # everything ok output_objects.append({ 'object_type': 'text', 'text': 'Saved changes to %s.' % path }) logger.info('saved changes to %s' % path) release_edit_lock(abs_path, client_id) except Exception as exc: # Don't give away information about actual fs layout output_objects.append({ 'object_type': 'error_text', 'text': '%s could not be written! (%s)' % (path, str(exc).replace(base_dir, '')) }) return (output_objects, returnvalues.SYSTEM_ERROR) if submitjob: output_objects.append({ 'object_type': 'text', 'text': 'Submitting saved file to parser' }) submitstatus = {'object_type': 'submitstatus', 'name': path} (new_job_status, msg, job_id) = new_job(abs_path, client_id, configuration, False, True) if not new_job_status: submitstatus['status'] = False submitstatus['message'] = msg else: submitstatus['status'] = True submitstatus['job_id'] = job_id output_objects.append({ 'object_type': 'submitstatuslist', 'submitstatuslist': [submitstatus] }) output_objects.append({ 'object_type': 'link', 'destination': 'javascript:history.back()', 'class': 'backlink iconspace', 'title': 'Go back to previous page', 'text': 'Back to previous page' }) return (output_objects, returnvalues.OK)
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) status = returnvalues.OK defaults = signature()[1] title_entry = find_entry(output_objects, 'title') title_entry['text'] = 'Resource management' (validate_status, accepted) = validate_input_and_cert( user_arguments_dict, defaults, output_objects, client_id, configuration, allow_rejects=False, ) if not validate_status: return (accepted, returnvalues.CLIENT_ERROR) show_sandboxes = (accepted['show_sandboxes'][-1] != 'false') operation = accepted['operation'][-1] caching = (accepted['caching'][-1].lower() in ('true', 'yes')) if not configuration.site_enable_resources: output_objects.append({'object_type': 'error_text', 'text': '''Resources are not enabled on this system'''}) return (output_objects, returnvalues.SYSTEM_ERROR) if not operation in allowed_operations: output_objects.append({'object_type': 'text', 'text': '''Operation must be one of %s.''' % ', '.join(allowed_operations)}) return (output_objects, returnvalues.OK) logger.info("%s %s begin for %s" % (op_name, operation, client_id)) pending_updates = False if operation in show_operations: # jquery support for tablesorter and confirmation on delete # table initially sorted by col. 1 (admin), then 0 (name) # NOTE: We distinguish between caching on page load and forced refresh refresh_call = 'ajax_resman(%s)' table_spec = {'table_id': 'resourcetable', 'sort_order': '[[1,0],[0,0]]', 'refresh_call': refresh_call % 'false'} (add_import, add_init, add_ready) = man_base_js(configuration, [table_spec]) if operation == "show": add_ready += '%s;' % refresh_call % 'true' title_entry['script']['advanced'] += add_import title_entry['script']['init'] += add_init title_entry['script']['ready'] += add_ready output_objects.append({'object_type': 'html_form', 'text': man_base_html(configuration)}) output_objects.append({'object_type': 'header', 'text': 'Available Resources'}) output_objects.append({'object_type': 'sectionheader', 'text': 'Resources available on this server'}) output_objects.append({'object_type': 'text', 'text': ''' All available resources are listed below with overall hardware specifications. Any resources that you own will have a administration icon that you can click to open resource management. ''' }) # Helper forms for requests and removes form_method = 'post' csrf_limit = get_csrf_limit(configuration) target_op = 'sendrequestaction' csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) helper = html_post_helper('reqresowner', '%s.py' % target_op, {'unique_resource_name': '__DYNAMIC__', 'request_type': 'resourceowner', 'request_text': '', csrf_field: csrf_token}) output_objects.append({'object_type': 'html_form', 'text': helper}) target_op = 'rmresowner' csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) helper = html_post_helper('rmresowner', '%s.py' % target_op, {'unique_resource_name': '__DYNAMIC__', 'cert_id': client_id, csrf_field: csrf_token}) output_objects.append({'object_type': 'html_form', 'text': helper}) output_objects.append({'object_type': 'table_pager', 'entry_name': 'resources', 'default_entries': default_pager_entries}) resources = [] if operation in list_operations: logger.info("get vgrid and resource map with caching %s" % caching) visible_res_confs = user_visible_res_confs(configuration, client_id, caching) res_map = get_resource_map(configuration, caching) anon_map = anon_to_real_res_map(configuration.resource_home) # NOTE: use simple pending check if caching to avoid lock during update if caching: pending_updates = pending_vgrids_update(configuration) or \ pending_resources_update(configuration) else: pending_updates = False if pending_updates: logger.debug("found pending cache updates: %s" % pending_updates) else: logger.debug("no pending cache updates") # Iterate through resources and show management for each one requested fields = ['PUBLICNAME', 'NODECOUNT', 'CPUCOUNT', 'MEMORY', 'DISK', 'ARCHITECTURE', 'SANDBOX', 'RUNTIMEENVIRONMENT'] # NOTE: only resources that user is allowed to access are listed. # Resource with neither exes nor stores are not shown to anyone # but the owners. Similarly resources are not shown if all # resource units solely participate in VGrids, which the user # can't access. for visible_res_name in visible_res_confs.keys(): unique_resource_name = visible_res_name if visible_res_name in anon_map.keys(): unique_resource_name = anon_map[visible_res_name] if not show_sandboxes and sandbox_resource(unique_resource_name): continue res_obj = {'object_type': 'resource', 'name': visible_res_name} # NOTE: res may not yet have been added to res_map here if not res_map.get(unique_resource_name, None): logger.info("skip %s not yet in resource map" % unique_resource_name) continue if client_id in res_map[unique_resource_name][OWNERS]: # Admin of resource when owner res_obj['resownerlink'] = { 'object_type': 'link', 'destination': "javascript: confirmDialog(%s, '%s', %s, %s);" % ('rmresowner', 'Really leave %s owners?' % unique_resource_name, 'undefined', "{unique_resource_name: '%s'}" % unique_resource_name), 'class': 'removelink iconspace', 'title': 'Leave %s owners' % unique_resource_name, 'text': ''} res_obj['resdetailslink'] = { 'object_type': 'link', 'destination': 'resadmin.py?unique_resource_name=%s' % unique_resource_name, 'class': 'adminlink iconspace', 'title': 'Administrate %s' % unique_resource_name, 'text': ''} else: # link to become owner res_obj['resownerlink'] = { 'object_type': 'link', 'destination': "javascript: confirmDialog(%s, '%s', '%s', %s);" % ('reqresowner', "Request ownership of " + visible_res_name + ":<br/>" + "\nPlease write a message to the owners (field below).", 'request_text', "{unique_resource_name: '%s'}" % visible_res_name), 'class': 'addlink iconspace', 'title': 'Request ownership of %s' % visible_res_name, 'text': ''} res_obj['resdetailslink'] = { 'object_type': 'link', 'destination': 'viewres.py?unique_resource_name=%s' % visible_res_name, 'class': 'infolink iconspace', 'title': 'View detailed %s specs' % visible_res_name, 'text': ''} # fields for everyone: public status for name in fields: res_obj[name] = res_map[unique_resource_name][CONF].get(name, '') # Use runtimeenvironment names instead of actual definitions res_obj['RUNTIMEENVIRONMENT'] = [i[0] for i in res_obj['RUNTIMEENVIRONMENT']] res_obj['RUNTIMEENVIRONMENT'].sort() resources.append(res_obj) if operation == "show": # insert dummy placeholder to build table res_obj = {'object_type': 'resource', 'name': ''} resources.append(res_obj) output_objects.append({'object_type': 'resource_list', 'pending_updates': pending_updates, 'resources': resources}) if operation in show_operations: if configuration.site_enable_sandboxes: if show_sandboxes: output_objects.append({'object_type': 'link', 'destination': '?show_sandboxes=false', 'class': 'removeitemlink iconspace', 'title': 'Hide sandbox resources', 'text': 'Exclude sandbox resources', }) else: output_objects.append({'object_type': 'link', 'destination': '?show_sandboxes=true', 'class': 'additemlink iconspace', 'title': 'Show sandbox resources', 'text': 'Include sandbox resources', }) output_objects.append( {'object_type': 'sectionheader', 'text': 'Resource Status'}) output_objects.append({'object_type': 'text', 'text': ''' Live resource status is available in the resource monitor page with all %s/resources you can access ''' % configuration.site_vgrid_label}) output_objects.append({ 'object_type': 'link', 'destination': 'showvgridmonitor.py?vgrid_name=ALL', 'class': 'monitorlink iconspace', 'title': 'Show monitor with all resources you can access', 'text': 'Global resource monitor', }) output_objects.append({'object_type': 'sectionheader', 'text': 'Additional Resources'}) output_objects.append({ 'object_type': 'text', 'text': 'You can sign up spare or dedicated resources to the grid below.' }) output_objects.append({'object_type': 'link', 'destination': 'resedit.py', 'class': 'addlink iconspace', 'title': 'Show sandbox resources', 'text': 'Create a new %s resource' % configuration.short_title, }) output_objects.append({'object_type': 'sectionheader', 'text': ''}) if configuration.site_enable_sandboxes: output_objects.append({ 'object_type': 'link', 'destination': 'ssslogin.py', 'class': 'adminlink iconspace', 'title': 'Administrate and monitor your sandbox resources', 'text': 'Administrate %s sandbox resources' % configuration.short_title}) output_objects.append({'object_type': 'sectionheader', 'text': ''}) output_objects.append({ 'object_type': 'link', 'destination': 'oneclick.py', 'class': 'sandboxlink iconspace', 'title': 'Run a One-click resource in your browser', 'text': 'Use this computer as One-click %s resource' % configuration.short_title}) logger.info("%s %s end for %s" % (op_name, operation, client_id)) return (output_objects, status)
def main(client_id, user_arguments_dict): """Main function used by front end""" (configuration, logger, output_objects, op_name) = \ initialize_main_variables(client_id) client_dir = client_id_dir(client_id) status = returnvalues.OK defaults = signature()[1] (validate_status, accepted) = validate_input_and_cert( user_arguments_dict, defaults, output_objects, client_id, configuration, allow_rejects=False, # NOTE: path can use wildcards typecheck_overrides={'path': valid_path_pattern}, ) if not validate_status: return (accepted, returnvalues.CLIENT_ERROR) flags = ''.join(accepted['flags']) patterns = accepted['path'] search = accepted['pattern'][-1] # 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 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 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: output_objects.append({'object_type': 'file_not_found', 'name': pattern}) status = returnvalues.FILE_NOT_FOUND for abs_path in match: relative_path = abs_path.replace(base_dir, '') output_lines = [] try: matching = pattern_match_file(search, abs_path) for line in matching: output_lines.append(line) except Exception 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)) status = returnvalues.SYSTEM_ERROR continue entry = {'object_type': 'file_output', 'lines': output_lines, 'wrap_binary': binary(flags), 'wrap_targets': ['lines']} if verbose(flags): entry['path'] = relative_path output_objects.append(entry) return (output_objects, status)
def main(client_id, user_arguments_dict): """Main function used by front end""" (configuration, logger, output_objects, op_name) = \ initialize_main_variables(client_id) client_dir = client_id_dir(client_id) defaults = signature()[1] (validate_status, accepted) = validate_input_and_cert( user_arguments_dict, defaults, output_objects, client_id, configuration, allow_rejects=False, # NOTE: path can use wildcards, dst and 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'] dst = accepted['dst'][-1] current_dir = accepted['current_dir'][-1].lstrip(os.sep) # All paths are relative to current_dir pattern_list = [os.path.join(current_dir, i) for i in pattern_list] if dst: dst = os.path.join(current_dir, dst) # Please note that base_dir must end in slash to avoid access to other # user dirs when own name is a prefix of another user name base_dir = os.path.abspath( os.path.join(configuration.user_home, client_dir)) + os.sep status = returnvalues.OK if verbose(flags): for flag in flags: output_objects.append({ 'object_type': 'text', 'text': '%s using flag: %s' % (op_name, flag) }) # IMPORTANT: path must be expanded to abs for proper chrooting abs_dir = os.path.abspath( os.path.join(base_dir, current_dir.lstrip(os.sep))) if not valid_user_path(configuration, abs_dir, base_dir, True): output_objects.append({ 'object_type': 'error_text', 'text': "You're not allowed to work in %s!" % current_dir }) logger.warning('%s tried to %s restricted path %s ! (%s)' % (client_id, op_name, abs_dir, current_dir)) return (output_objects, returnvalues.CLIENT_ERROR) if verbose(flags): output_objects.append({ 'object_type': 'text', 'text': "working in %s" % current_dir }) if dst: if not safe_handler(configuration, 'post', op_name, client_id, get_csrf_limit(configuration), accepted): output_objects.append({ 'object_type': 'error_text', 'text': '''Only accepting CSRF-filtered POST requests to prevent unintended updates''' }) return (output_objects, returnvalues.CLIENT_ERROR) # NOTE: dst already incorporates current_dir prefix here # IMPORTANT: path must be expanded to abs for proper chrooting abs_dest = os.path.abspath(os.path.join(base_dir, dst)) logger.info('chksum in %s' % abs_dest) # Don't use abs_path in output as it may expose underlying # fs layout. relative_dest = abs_dest.replace(base_dir, '') if not valid_user_path(configuration, abs_dest, base_dir, True): output_objects.append({ 'object_type': 'error_text', 'text': "Invalid path! (%s expands to an illegal path)" % dst }) logger.warning('%s tried to %s restricted path %s !(%s)' % (client_id, op_name, abs_dest, dst)) return (output_objects, returnvalues.CLIENT_ERROR) if not check_write_access(abs_dest, parent_dir=True): logger.warning('%s called without write access: %s' % (op_name, abs_dest)) output_objects.append({ 'object_type': 'error_text', 'text': 'cannot checksum to "%s": inside a read-only location!' % relative_dest }) return (output_objects, returnvalues.CLIENT_ERROR) all_lines = [] 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: output_objects.append({ 'object_type': 'file_not_found', 'name': pattern }) status = returnvalues.FILE_NOT_FOUND # NOTE: we produce output matching an invocation of: # du -aL --apparent-size --block-size=1 PATH [PATH ...] filedus = [] summarize_output = summarize(flags) for abs_path in match: if invisible_path(abs_path): continue relative_path = abs_path.replace(base_dir, '') # cache accumulated sub dir sizes - du sums into parent dir size dir_sizes = {} try: # Assume a directory to walk for (root, dirs, files) in walk(abs_path, topdown=False, followlinks=True): if invisible_path(root): continue dir_bytes = 0 for name in files: real_file = os.path.join(root, name) if invisible_path(real_file): continue relative_file = real_file.replace(base_dir, '') size = os.path.getsize(real_file) dir_bytes += size if not summarize_output: filedus.append({ 'object_type': 'filedu', 'name': relative_file, 'bytes': size }) for name in dirs: real_dir = os.path.join(root, name) if invisible_path(real_dir): continue dir_bytes += dir_sizes[real_dir] relative_root = root.replace(base_dir, '') dir_bytes += os.path.getsize(root) dir_sizes[root] = dir_bytes if root == abs_path or not summarize_output: filedus.append({ 'object_type': 'filedu', 'name': relative_root, 'bytes': dir_bytes }) if os.path.isfile(abs_path): # Fall back to plain file where walk is empty size = os.path.getsize(abs_path) filedus.append({ 'object_type': 'filedu', 'name': relative_path, 'bytes': size }) except Exception 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)) status = returnvalues.SYSTEM_ERROR continue if dst: all_lines += [ '%(bytes)d\t\t%(name)s\n' % entry for entry in filedus ] else: output_objects.append({ 'object_type': 'filedus', 'filedus': filedus }) if dst and not write_file(''.join(all_lines), abs_dest, logger): output_objects.append({ 'object_type': 'error_text', 'text': "failed to write disk use to %s" % relative_dest }) logger.error("writing disk use to %s for %s failed" % (abs_dest, client_id)) status = returnvalues.SYSTEM_ERROR return (output_objects, status)
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) defaults = signature()[1] title_entry = find_entry(output_objects, 'title') label = "%s" % configuration.site_vgrid_label title_entry['text'] = "Remove %s Trigger" % label output_objects.append({'object_type': 'header', 'text' : 'Remove %s Trigger' % label}) (validate_status, accepted) = validate_input_and_cert( user_arguments_dict, defaults, output_objects, client_id, configuration, allow_rejects=False, ) if not validate_status: return (accepted, returnvalues.CLIENT_ERROR) vgrid_name = accepted['vgrid_name'][-1] rule_id = accepted['rule_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) logger.info("rmvgridtrigger %s %s" % (vgrid_name, rule_id)) # Validity of user and vgrid names is checked in this init function so # no need to worry about illegal directory traversal through variables (ret_val, msg, ret_variables) = \ init_vgrid_script_add_rem(vgrid_name, client_id, rule_id, 'trigger', configuration) if not ret_val: output_objects.append({'object_type': 'error_text', 'text': msg}) return (output_objects, returnvalues.CLIENT_ERROR) elif msg: # In case of warnings, msg is non-empty while ret_val remains True output_objects.append({'object_type': 'warning', 'text': msg}) # if we get here user is either vgrid owner or has rule ownership # can't remove if not a participant if not vgrid_is_trigger(vgrid_name, rule_id, configuration, recursive=False): output_objects.append({'object_type': 'error_text', 'text': '%s is not a trigger in %s %s.' % \ (rule_id, vgrid_name, label)}) return (output_objects, returnvalues.CLIENT_ERROR) # remove (rm_status, rm_msg) = vgrid_remove_triggers(configuration, vgrid_name, [rule_id]) if not rm_status: logger.error('%s failed to remove trigger: %s' % (client_id, rm_msg)) output_objects.append({'object_type': 'error_text', 'text': rm_msg}) output_objects.append({'object_type': 'error_text', 'text': '''%(rule_id)s might be listed as a trigger of this %(vgrid_label)s because it is a trigger of a parent %(vgrid_label)s. Removal must be performed from the most significant %(vgrid_label)s possible.''' % {'rule_id': rule_id, 'vgrid_label': label}}) return (output_objects, returnvalues.SYSTEM_ERROR) logger.info('%s removed trigger: %s' % (client_id, rule_id)) output_objects.append({'object_type': 'text', 'text': 'Trigger %s successfully removed from %s %s!' % (rule_id, vgrid_name, label)}) output_objects.append({'object_type': 'link', 'destination': 'vgridworkflows.py?vgrid_name=%s' % vgrid_name, 'text': 'Back to workflows for %s' % vgrid_name}) return (output_objects, returnvalues.OK)
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) status = returnvalues.OK defaults = signature()[1] (validate_status, accepted) = validate_input_and_cert( user_arguments_dict, defaults, output_objects, client_id, configuration, allow_rejects=False, ) if not validate_status: return (accepted, returnvalues.CLIENT_ERROR) if not configuration.site_enable_jobs: output_objects.append({ 'object_type': 'error_text', 'text': '''Job execution is not enabled on this system''' }) return (output_objects, returnvalues.SYSTEM_ERROR) title_entry = find_entry(output_objects, 'title') title_entry['text'] = 'Manage jobs' output_objects.append({'object_type': 'header', 'text': 'Manage Jobs'}) output_objects.append({ 'object_type': 'sectionheader', 'text': 'View status of all submitted jobs' }) output_objects.append({ 'object_type': 'html_form', 'text': ''' <form method="get" action="jobstatus.py"> Sort by modification time: <input type="radio" name="flags" value="sv" />yes <input type="radio" name="flags" checked="checked" value="vi" />no<br /> <input type="hidden" name="job_id" value="*" /> <input type="hidden" name="output_format" value="html" /> <input type="submit" value="Show All" /> </form> ''' }) output_objects.append({ 'object_type': 'sectionheader', 'text': 'View status of individual jobs' }) output_objects.append({ 'object_type': 'html_form', 'text': ''' Filter job IDs (* and ? wildcards are supported)<br /> <form method="get" action="jobstatus.py"> Job ID: <input type="text" name="job_id" value="*" size="30" /><br /> Show only <input type="text" name="max_jobs" size="6" value=5 /> first matching jobs<br /> Sort by modification time: <input type="radio" name="flags" checked="checked" value="vsi" />yes <input type="radio" name="flags" value="vi" />no<br /> <input type="hidden" name="output_format" value="html" /> <input type="submit" value="Show" /> </form> ''' }) output_objects.append({ 'object_type': 'sectionheader', 'text': 'Resubmit job' }) form_method = 'post' csrf_limit = get_csrf_limit(configuration) fill_helpers = { 'short_title': configuration.short_title, 'form_method': form_method, 'csrf_field': csrf_field, 'csrf_limit': csrf_limit } target_op = 'resubmit' 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': 'html_form', 'text': ''' <form method="%(form_method)s" action="%(target_op)s.py"> <input type="hidden" name="%(csrf_field)s" value="%(csrf_token)s" /> Job ID: <input type="text" name="job_id" size="30" /><br /> <input type="hidden" name="output_format" value="html" /> <input type="submit" value="Submit" /> </form> ''' % fill_helpers }) output_objects.append({ 'object_type': 'sectionheader', 'text': 'Freeze pending job' }) target_op = 'jobaction' 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': 'html_form', 'text': ''' <form method="%(form_method)s" action="%(target_op)s.py"> <input type="hidden" name="%(csrf_field)s" value="%(csrf_token)s" /> Job ID: <input type="text" name="job_id" size="30" /><br /> <input type="hidden" name="action" value="freeze" /> <input type="hidden" name="output_format" value="html" /> <input type="submit" value="Freeze job" /> </form> ''' % fill_helpers }) output_objects.append({ 'object_type': 'sectionheader', 'text': 'Thaw frozen job' }) output_objects.append({ 'object_type': 'html_form', 'text': ''' <form method="%(form_method)s" action="%(target_op)s.py"> <input type="hidden" name="%(csrf_field)s" value="%(csrf_token)s" /> Job ID: <input type="text" name="job_id" size="30" /><br /> <input type="hidden" name="action" value="thaw" /> <input type="hidden" name="output_format" value="html" /> <input type="submit" value="Thaw job" /> </form> ''' % fill_helpers }) output_objects.append({ 'object_type': 'sectionheader', 'text': 'Cancel pending or executing job' }) output_objects.append({ 'object_type': 'html_form', 'text': ''' <form method="%(form_method)s" action="%(target_op)s.py"> <input type="hidden" name="%(csrf_field)s" value="%(csrf_token)s" /> Job ID: <input type="text" name="job_id" size="30" /><br /> <input type="hidden" name="action" value="cancel" /> <input type="hidden" name="output_format" value="html" /> <input type="submit" value="Cancel job" /> </form> ''' % fill_helpers }) output_objects.append({ 'object_type': 'sectionheader', 'text': 'Request live I/O' }) output_objects.append({ 'object_type': 'html_form', 'text': ''' <form method="get" action="liveio.py"> Job ID: <input type="text" name="job_id" size="30" /><br /> <input type="hidden" name="output_format" value="html" /> <input type="submit" value="Request" /> </form> <br /> ''' }) return (output_objects, status)
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)
def main(client_id, user_arguments_dict): """Main function used by front end""" (configuration, logger, output_objects, op_name) = \ initialize_main_variables(client_id) output_objects.append({ 'object_type': 'text', 'text': '--------- Trying to RESTART exe ----------' }) defaults = signature()[1] (validate_status, accepted) = validate_input_and_cert( user_arguments_dict, defaults, output_objects, client_id, configuration, allow_rejects=False, ) if not validate_status: return (accepted, returnvalues.CLIENT_ERROR) unique_resource_name = accepted['unique_resource_name'][-1] cputime = accepted['cputime'][-1] exe_name_list = accepted['exe_name'] all = accepted['all'][-1].lower() == 'true' parallel = accepted['parallel'][-1].lower() == 'true' if not configuration.site_enable_resources: output_objects.append({ 'object_type': 'error_text', 'text': '''Resources are not enabled on this system''' }) return (output_objects, returnvalues.SYSTEM_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) 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 ' + unique_resource_name + ' to restart the exe!' }) return (output_objects, returnvalues.CLIENT_ERROR) exit_status = returnvalues.OK if all: exe_name_list = get_all_exe_names(unique_resource_name) # take action based on supplied list of exes if len(exe_name_list) == 0: output_objects.append({ 'object_type': 'text', 'text': "No exes specified and 'all' argument not set to true: Nothing to do!" }) workers = [] task_list = [] for exe_name in exe_name_list: task = Worker(target=stop_resource_exe, args=(unique_resource_name, exe_name, configuration.resource_home, logger)) workers.append((exe_name, [task])) task_list.append(task) throttle_max_concurrent(task_list) task.start() if not parallel: task.join() # Complete each stop thread before launching corresponding start threads for (exe_name, task_list) in workers: # We could optimize with non-blocking join here but keep it simple for now # as final result will need to wait for slowest member anyway task_list[0].join() task = Worker(target=start_resource_exe, args=(unique_resource_name, exe_name, configuration.resource_home, int(cputime), logger)) task_list.append(task) throttle_max_concurrent(task_list) task.start() if not parallel: task.join() for (exe_name, task_list) in workers: (status, msg) = task_list[0].finish() output_objects.append({ 'object_type': 'header', 'text': 'Restart exe output:' }) if not status: output_objects.append({ 'object_type': 'error_text', 'text': 'Problems stopping exe during restart: %s' % msg }) (status2, msg2) = task_list[1].finish() if not status2: output_objects.append({ 'object_type': 'error_text', 'text': 'Problems starting exe during restart: %s' % msg2 }) exit_status = returnvalues.SYSTEM_ERROR if status and status2: output_objects.append({ 'object_type': 'text', 'text': 'Restart exe success: Stop output: %s ; Start output: %s' % (msg, msg2) }) return (output_objects, exit_status)
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)
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) status = returnvalues.OK defaults = signature()[1] (validate_status, accepted) = validate_input_and_cert( user_arguments_dict, defaults, output_objects, client_id, configuration, allow_rejects=False, ) if not validate_status: return (accepted, returnvalues.CLIENT_ERROR) output_objects.append({'object_type': 'header', 'text': 'Downloads'}) output_objects.append({ 'object_type': 'html_form', 'text': """ <div class="migcontent"> This page provides access to on-demand downloads of the %(site)s user scripts in all available formats.<br /> Simply pick your flavor of choice to generate the latest user scripts in your %(site)s home directory and as a zip file for easy download.<p> In order to use the scripts your need the interpreter of choice (bash or python at the moment) and the <a href='http://curl.haxx.se' class='urllink iconspace'>cURL</a> command line client.<br /> There's a tutorial with examples of all the commands available on the %(site)s page. The python version of the user scripts additionally includes a miglib python module, which may be used to incorporate %(site)s commands in your python applications. </div> """ % { 'site': configuration.short_title } }) output_objects.append({ 'object_type': 'sectionheader', 'text': '%s User Scripts' % configuration.short_title }) form_method = 'post' csrf_limit = get_csrf_limit(configuration) fill_helpers = { 'short_title': configuration.short_title, 'form_method': form_method, 'csrf_field': csrf_field, 'csrf_limit': csrf_limit } target_op = 'scripts' 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': 'html_form', 'text': """ <div class='migcontent'> Generate %(short_title)s user scripts to manage jobs and files:<br/> <div class='row button-grid'> <div class='col-lg-4 left'> <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='output_format' value='html' /> <input type='hidden' name='lang' value='python' /> <input type='submit' value='python version' /> </form> </div> <div class='col-lg-4 middle'> <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='output_format' value='html' /> <input type='hidden' name='lang' value='sh' /> <input type='submit' value='sh version' /> </form> </div> <div class='col-lg-4 right'> <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='output_format' value='html' /> <input type='submit' value='all versions' /> </form> </div> </div> </div> <br /> """ % fill_helpers }) output_objects.append({ 'object_type': 'sectionheader', 'text': '%s Resource Scripts' % configuration.short_title }) output_objects.append({ 'object_type': 'html_form', 'text': """ <div class='migcontent'> Generate %(short_title)s scripts to administrate resources and vgrids:<br/> <div class='row button-grid'> <div class='col-lg-4 left'> <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='output_format' value='html' /> <input type='hidden' name='lang' value='python' /> <input type='hidden' name='flavor' value='resource' /> <input type='submit' value='python version' /> </form> </div> <div class='col-lg-4 middle'> <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='output_format' value='html' /> <input type='hidden' name='lang' value='sh' /> <input type='hidden' name='flavor' value='resource' /> <input type='submit' value='sh version' /> </form> </div> <div class='col-lg-4 right'> <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='output_format' value='html' /> <input type='hidden' name='flavor' value='resource' /> <input type='submit' value='all versions' /> </form> </div> </div> </div> """ % fill_helpers }) return (output_objects, status)
def main(client_id, user_arguments_dict): """Main function used by front end""" (configuration, logger, output_objects, op_name) = \ initialize_main_variables(client_id, op_header=False) client_dir = client_id_dir(client_id) defaults = signature()[1] (validate_status, accepted) = validate_input_and_cert( user_arguments_dict, defaults, output_objects, client_id, configuration, allow_rejects=False, # NOTE: path cannot use wildcards here typecheck_overrides={}, ) if not validate_status: return (accepted, returnvalues.CLIENT_ERROR) status = returnvalues.OK chroot = '' if configuration.site_enable_gdp: chroot = get_project_from_client_id(configuration, client_id) all_paths = accepted['path'] entry_path = all_paths[-1] title_entry = find_entry(output_objects, 'title') user_settings = title_entry.get('user_settings', {}) title_entry['text'] = 'File Manager' title_entry['style'] = css_tmpl(configuration, user_settings) legacy_buttons = False if legacy_user_interface(configuration, user_settings): legacy_buttons = True logger.debug("enable legacy buttons") if configuration.site_enable_jobs and \ 'submitjob' in extract_menu(configuration, title_entry): enable_submit = 'true' else: enable_submit = 'false' csrf_map = {} method = 'post' limit = get_csrf_limit(configuration) for target_op in csrf_backends: csrf_map[target_op] = make_csrf_token(configuration, method, target_op, client_id, limit) (add_import, add_init, add_ready) = js_tmpl_parts(configuration, entry_path, enable_submit, str(configuration.site_enable_preview), legacy_buttons, csrf_map, chroot) title_entry['script']['advanced'] += add_import title_entry['script']['init'] += add_init title_entry['script']['ready'] += add_ready title_entry['container_class'] = 'fillwidth', output_objects.append({ 'object_type': 'header', 'class': 'fileman-title', 'text': 'File Manager' }) output_objects.append({ 'object_type': 'html_form', 'text': html_tmpl(configuration, client_id, title_entry, csrf_map, chroot) }) if len(all_paths) > 1: output_objects.append({ 'object_type': 'sectionheader', 'text': 'All requested paths:' }) for path in all_paths: output_objects.append({ 'object_type': 'link', 'text': path, 'destination': 'fileman.py?path=%s' % path }) output_objects.append({'object_type': 'text', 'text': ''}) return (output_objects, status)
def main(client_id, user_arguments_dict): """Main function used by front end""" (configuration, logger, output_objects, op_name) = \ initialize_main_variables(client_id) client_dir = client_id_dir(client_id) defaults = signature()[1] (validate_status, accepted) = validate_input_and_cert( user_arguments_dict, defaults, output_objects, client_id, configuration, allow_rejects=False, ) if not validate_status: return (accepted, returnvalues.CLIENT_ERROR) patterns = accepted['job_id'] 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 configuration.site_enable_jobs: output_objects.append({ 'object_type': 'error_text', 'text': '''Job execution is not enabled on this system''' }) 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(configuration.mrsl_files_dir, client_dir)) + os.sep status = returnvalues.OK filelist = [] for pattern in patterns: pattern = pattern.strip() # Backward compatibility - all_jobs keyword should match all jobs if pattern == all_jobs: pattern = '*' # 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 + '.mRSL') 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 # Insert valid job files in filelist for later treatment match.append(abs_path) # Now actually treat list of allowed matchings and notify if no # (allowed) match^I if not match: output_objects.append({ 'object_type': 'error_text', 'text': '%s: You do not have any matching job IDs!' % pattern }) status = returnvalues.CLIENT_ERROR else: filelist += match # job feasibility is hard on the server, limit if len(filelist) > 100: output_objects.append({ 'object_type': 'error_text', 'text': 'Too many matching jobs (%s)!' % len(filelist) }) return (output_objects, returnvalues.CLIENT_ERROR) checkcondjobs = [] for filepath in filelist: # Extract job_id from filepath (replace doesn't modify filepath) mrsl_file = filepath.replace(base_dir, '') job_id = mrsl_file.replace('.mRSL', '') checkcondjob = {'object_type': 'checkcondjob', 'job_id': job_id} dict = unpickle(filepath, logger) if not dict: checkcondjob['message'] = \ ('The file containing the information ' \ 'for job id %s could not be opened! ' \ 'You can only check feasibility of ' \ 'your own jobs!' ) % job_id checkcondjobs.append(checkcondjob) status = returnvalues.CLIENT_ERROR continue # Is the job status pending? possible_check_states = ['QUEUED', 'RETRY', 'FROZEN'] if not dict['STATUS'] in possible_check_states: checkcondjob['message'] = \ 'You can only check feasibility of jobs with status: %s.'\ % ' or '.join(possible_check_states) checkcondjobs.append(checkcondjob) continue # Actually check feasibility feasible_res = job_feasibility(configuration, dict) checkcondjob.update(feasible_res) checkcondjobs.append(checkcondjob) output_objects.append({ 'object_type': 'checkcondjobs', 'checkcondjobs': checkcondjobs }) return (output_objects, status)
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) defaults = signature()[1] title_entry = find_entry(output_objects, 'title') label = "%s" % configuration.site_vgrid_label title_entry['text'] = "Administrate %s" % label # NOTE: Delay header entry here to include vgrid name (validate_status, accepted) = validate_input_and_cert( user_arguments_dict, defaults, output_objects, client_id, configuration, allow_rejects=False, ) if not validate_status: return (accepted, returnvalues.CLIENT_ERROR) vgrid_name = accepted['vgrid_name'][-1] # prepare for confirm dialog, tablesort and toggling the views (css/js) # jquery support for tablesorter and confirmation on request and leave # requests table initially sorted by 0, 4, 3 (type first, then date and # with alphabetical client ID last) # sharelinks table initially sorted by 5, 4 reversed (active first and # in growing age) table_specs = [{ 'table_id': 'accessrequeststable', 'pager_id': 'accessrequests_pager', 'sort_order': '[[0,0],[4,0],[3,0]]' }, { 'table_id': 'sharelinkstable', 'pager_id': 'sharelinks_pager', 'sort_order': '[[5,1],[4,1]]' }] (add_import, add_init, add_ready) = man_base_js(configuration, table_specs, {'width': 600}) add_init += ''' var toggleHidden = function(classname) { // classname supposed to have a leading dot $(classname).toggleClass("hidden"); }; /* helpers for dynamic form input fields */ function onOwnerInputChange() { makeSpareFields("#dynownerspares", "cert_id"); } function onMemberInputChange() { makeSpareFields("#dynmemberspares", "cert_id"); } function onResourceInputChange() { makeSpareFields("#dynresourcespares", "unique_resource_name"); } ''' add_ready += ''' /* init add owners/member/resource forms with dynamic input fields */ onOwnerInputChange(); $("#dynownerspares").on("blur", "input[name=cert_id]", function(event) { //console.debug("in add owner blur handler"); onOwnerInputChange(); } ); onMemberInputChange(); $("#dynmemberspares").on("blur", "input[name=cert_id]", function(event) { //console.debug("in add member blur handler"); onMemberInputChange(); } );''' if configuration.site_enable_resources: add_ready += ''' onResourceInputChange(); $("#dynresourcespares").on("blur", "input[name=unique_resource_name]", function(event) { console.debug("in resource blur handler"); onResourceInputChange(); } ); ''' title_entry['script']['advanced'] += add_import title_entry['script']['init'] += add_init title_entry['script']['ready'] += add_ready output_objects.append({ 'object_type': 'html_form', 'text': man_base_html(configuration) }) form_method = 'post' csrf_limit = get_csrf_limit(configuration) fill_helpers = { 'short_title': configuration.short_title, 'vgrid_label': label, 'form_method': form_method, 'csrf_field': csrf_field, 'csrf_limit': csrf_limit } output_objects.append({ 'object_type': 'header', 'text': "Administrate '%s'" % vgrid_name }) if not vgrid_is_owner(vgrid_name, client_id, configuration): output_objects.append({ 'object_type': 'error_text', 'text': 'Only owners of %s can administrate it.' % vgrid_name }) target_op = "sendrequestaction" csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) js_name = 'reqvgridowner%s' % hexlify(vgrid_name) helper = html_post_helper( js_name, '%s.py' % target_op, { 'vgrid_name': vgrid_name, 'request_type': 'vgridowner', 'request_text': '', csrf_field: csrf_token }) output_objects.append({'object_type': 'html_form', 'text': helper}) output_objects.append({ 'object_type': 'link', 'destination': "javascript: confirmDialog(%s, '%s', '%s');" % (js_name, "Request ownership of " + vgrid_name + ":<br/>" + "\nPlease write a message to the owners below.", 'request_text'), 'class': 'addadminlink iconspace', 'title': 'Request ownership of %s' % vgrid_name, 'text': 'Apply to become an owner' }) return (output_objects, returnvalues.SYSTEM_ERROR) for (item, scr) in zip(['owner', 'member', 'resource'], ['vgridowner', 'vgridmember', 'vgridres']): if item == 'resource' and not configuration.site_enable_resources: continue output_objects.append({ 'object_type': 'sectionheader', 'text': "%ss" % item.title() }) (init_status, oobjs) = vgrid_add_remove_table(client_id, vgrid_name, item, scr, configuration) if not init_status: output_objects.extend(oobjs) return (output_objects, returnvalues.SYSTEM_ERROR) else: output_objects.append({ 'object_type': 'html_form', 'text': '<div class="div-%s">' % item }) output_objects.append({ 'object_type': 'link', 'destination': "javascript:toggleHidden('.div-%s');" % item, 'class': 'removeitemlink iconspace', 'title': 'Toggle view', 'text': 'Hide %ss' % item.title() }) output_objects.extend(oobjs) output_objects.append({ 'object_type': 'html_form', 'text': '</div><div class="hidden div-%s">' % item }) output_objects.append({ 'object_type': 'link', 'destination': "javascript:toggleHidden('.div-%s');" % item, 'class': 'additemlink iconspace', 'title': 'Toggle view', 'text': 'Show %ss' % item.title() }) output_objects.append({ 'object_type': 'html_form', 'text': '</div>' }) # Pending requests target_op = "addvgridowner" csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) helper = html_post_helper( "acceptvgridownerreq", "%s.py" % target_op, { 'vgrid_name': vgrid_name, 'cert_id': '__DYNAMIC__', 'request_name': '__DYNAMIC__', csrf_field: csrf_token }) output_objects.append({'object_type': 'html_form', 'text': helper}) target_op = "addvgridmember" csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) helper = html_post_helper( "acceptvgridmemberreq", "%s.py" % target_op, { 'vgrid_name': vgrid_name, 'cert_id': '__DYNAMIC__', 'request_name': '__DYNAMIC__', csrf_field: csrf_token }) output_objects.append({'object_type': 'html_form', 'text': helper}) target_op = "addvgridres" csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) helper = html_post_helper( "acceptvgridresourcereq", "%s.py" % target_op, { 'vgrid_name': vgrid_name, 'unique_resource_name': '__DYNAMIC__', 'request_name': '__DYNAMIC__', csrf_field: csrf_token }) output_objects.append({'object_type': 'html_form', 'text': helper}) target_op = "rejectvgridreq" csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) helper = html_post_helper( "rejectvgridreq", "%s.py" % target_op, { 'vgrid_name': vgrid_name, 'request_name': '__DYNAMIC__', csrf_field: csrf_token }) output_objects.append({'object_type': 'html_form', 'text': helper}) request_dir = os.path.join(configuration.vgrid_home, vgrid_name) request_list = [] for req_name in list_access_requests(configuration, request_dir): req = load_access_request(configuration, request_dir, req_name) if not req: continue if not req.get('request_type', None) in ["vgridowner", "vgridmember", "vgridresource"]: logger.error("unexpected request_type %(request_type)s" % req) continue request_item = build_accessrequestitem_object(configuration, req) # Convert filename with exotic chars into url-friendly pure hex version shared_args = {"request_name": hexlify(req["request_name"])} accept_args, reject_args = {}, {} accept_args.update(shared_args) reject_args.update(shared_args) if req['request_type'] == "vgridresource": accept_args["unique_resource_name"] = req["entity"] else: accept_args["cert_id"] = req["entity"] request_item['acceptrequestlink'] = { 'object_type': 'link', 'destination': "javascript: confirmDialog(%s, '%s', %s, %s);" % ("accept%(request_type)sreq" % req, "Accept %(target)s %(request_type)s request from %(entity)s" % req, 'undefined', "{%s}" % ', '.join(["'%s': '%s'" % pair for pair in accept_args.items()])), 'class': 'addlink iconspace', 'title': 'Accept %(target)s %(request_type)s request from %(entity)s' % req, 'text': '' } request_item['rejectrequestlink'] = { 'object_type': 'link', 'destination': "javascript: confirmDialog(%s, '%s', %s, %s);" % ("rejectvgridreq", "Reject %(target)s %(request_type)s request from %(entity)s" % req, 'undefined', "%s" % reject_args), 'class': 'removelink iconspace', 'title': 'Reject %(target)s %(request_type)s request from %(entity)s' % req, 'text': '' } request_list.append(request_item) output_objects.append({ 'object_type': 'sectionheader', 'text': "Pending Requests" }) output_objects.append({ 'object_type': 'table_pager', 'id_prefix': 'accessrequests_', 'entry_name': 'access requests', 'default_entries': default_pager_entries }) output_objects.append({ 'object_type': 'accessrequests', 'accessrequests': request_list }) # VGrid Share links # Table columns to skip skip_list = [ 'editsharelink', 'delsharelink', 'invites', 'expire', 'single_file' ] # NOTE: Inheritance is a bit tricky for sharelinks because parent shares # only have relevance if they actually share a path that is a prefix of # vgrid_name. (share_status, share_list) = vgrid_sharelinks(vgrid_name, configuration) sharelinks = [] if share_status: for share_dict in share_list: rel_path = share_dict['path'].strip(os.sep) parent_vgrids = vgrid_list_parents(vgrid_name, configuration) include_share = False # Direct sharelinks (careful not to greedy match A/B with A/BCD) if rel_path == vgrid_name or \ rel_path.startswith(vgrid_name+os.sep): include_share = True # Parent vgrid sharelinks that in effect also give access here for parent in parent_vgrids: if rel_path == parent: include_share = True if include_share: share_item = build_sharelinkitem_object( configuration, share_dict) sharelinks.append(share_item) else: logger.warning("failed to load vgrid sharelinks for %s: %s" % (vgrid_name, share_list)) output_objects.append({ 'object_type': 'sectionheader', 'text': "Share Links" }) output_objects.append({ 'object_type': 'html_form', 'text': '<p>Current share links in %s shared folder</p>' % vgrid_name }) output_objects.append({ 'object_type': 'table_pager', 'id_prefix': 'sharelinks_', 'entry_name': 'share links', 'default_entries': default_pager_entries }) output_objects.append({ 'object_type': 'sharelinks', 'sharelinks': sharelinks, 'skip_list': skip_list }) # VGrid settings output_objects.append({'object_type': 'sectionheader', 'text': "Settings"}) (direct_status, direct_dict) = vgrid_settings(vgrid_name, configuration, recursive=False, as_dict=True) if not direct_status or not direct_dict: direct_dict = {} (settings_status, settings_dict) = vgrid_settings(vgrid_name, configuration, recursive=True, as_dict=True) if not settings_status or not settings_dict: settings_dict = {} form_method = 'post' csrf_limit = get_csrf_limit(configuration) # Always set these values settings_dict.update({ 'vgrid_name': vgrid_name, 'vgrid_label': label, 'owners': keyword_owners, 'members': keyword_members, 'all': keyword_all, 'form_method': form_method, 'csrf_field': csrf_field, 'csrf_limit': csrf_limit }) target_op = 'vgridsettings' csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) settings_dict.update({'target_op': target_op, 'csrf_token': csrf_token}) settings_form = ''' <form method="%(form_method)s" action="%(target_op)s.py"> <fieldset> <legend>%(vgrid_label)s configuration</legend> <input type="hidden" name="%(csrf_field)s" value="%(csrf_token)s" /> <input type="hidden" name="vgrid_name" value="%(vgrid_name)s" /> ''' description = settings_dict.get('description', '') settings_form += ''' <h4>Public description</h4> <textarea class="fillwidth padspace" name="description" rows=10 >%s</textarea> ''' % description settings_form += '<br/>' settings_form += '''<p>All visibility options below can be set to owners, members or everyone and by default only owners can see participation. In effect setting visibility to <em>members</em> means that owners and members can see the corresponding participants. Similarly setting a visibility flag to <em>everyone</em> means that all %s users can see the participants.</p> ''' % configuration.short_title visibility_options = [("Owners are visible to", "visible_owners"), ("Members are visible to", "visible_members"), ("Resources are visible to", "visible_resources")] for (title, field) in visibility_options: settings_form += '<h4>%s</h4>' % title if direct_dict.get(field, False): choices = _valid_visible + _reset_choice else: choices = _valid_visible + _keep_choice for (key, val) in choices: checked = '' if settings_dict.get(field, keyword_owners) == val: checked = "checked" settings_form += ''' <input type="radio" name="%s" value="%s" %s/> %s ''' % (field, val, checked, key) settings_form += '<br/>' field = 'restrict_settings_adm' restrict_settings_adm = settings_dict.get(field, default_vgrid_settings_limit) if direct_dict.get(field, False): direct_note = _reset_note else: direct_note = _keep_note settings_form += ''' <h4>Restrict Settings</h4> Restrict changing of these settings to only the first <input type="number" name="restrict_settings_adm" min=0 max=999 minlength=1 maxlength=3 value=%d required /> owners %s. ''' % (restrict_settings_adm, direct_note) settings_form += '<br/>' field = 'restrict_owners_adm' restrict_owners_adm = settings_dict.get(field, default_vgrid_settings_limit) if direct_dict.get(field, False): direct_note = _reset_note else: direct_note = _keep_note settings_form += ''' <h4>Restrict Owner Administration</h4> Restrict administration of owners to only the first <input type="number" name="restrict_owners_adm" min=0 max=999 minlength=1 maxlength=3 value=%d required /> owners %s. ''' % (restrict_owners_adm, direct_note) settings_form += '<br/>' field = 'restrict_members_adm' restrict_members_adm = settings_dict.get(field, default_vgrid_settings_limit) if direct_dict.get(field, False): direct_note = _reset_note else: direct_note = _keep_note settings_form += ''' <h4>Restrict Member Administration</h4> Restrict administration of members to only the first <input type="number" name="restrict_members_adm" min=0 max=999 minlength=1 maxlength=3 value=%d required /> owners %s. ''' % (restrict_members_adm, direct_note) settings_form += '<br/>' field = 'restrict_resources_adm' restrict_resources_adm = settings_dict.get(field, default_vgrid_settings_limit) if direct_dict.get(field, False): direct_note = _reset_note else: direct_note = _keep_note settings_form += ''' <h4>Restrict Resource Administration</h4> Restrict administration of resources to only the first <input type="number" name="restrict_resources_adm" min=0 max=999 minlength=1 maxlength=3 value=%d required /> owners %s. ''' % (restrict_resources_adm, direct_note) settings_form += '<br/>' if vgrid_restrict_write_support(configuration): settings_form += '''<p>All write access options below can be set to owners, members or none. By default only owners can write web pages while owners and members can edit data in the shared folders. In effect setting write access to <em>members</em> means that owners and members have full access. Similarly setting a write access flag to <em>owners</em> means that only owners can modify the data, while members can only read and use it. Finally setting a write access flag to <em>none</em> means that neither owners nor members can modify the data there, effectively making it read-only. Some options are not yet supported and thus are disabled below. </p> ''' writable_options = [ ("Shared files write access", "write_shared_files", keyword_members), ("Private web page write access", "write_priv_web", keyword_owners), ("Public web page write access", "write_pub_web", keyword_owners), ] else: writable_options = [] for (title, field, default) in writable_options: settings_form += '<h4>%s</h4>' % title if direct_dict.get(field, False): choices = _valid_write_access + _reset_choice else: choices = _valid_write_access + _keep_choice for (key, val) in choices: disabled = '' # TODO: remove these artifical limits once we support changing # TODO: also add check for vgrid web reshare in sharelink then if field == 'write_shared_files' and val == keyword_owners: disabled = 'disabled' elif field == 'write_priv_web' and val in [ keyword_members, keyword_none ]: disabled = 'disabled' elif field == 'write_pub_web' and val in [ keyword_members, keyword_none ]: disabled = 'disabled' checked = '' if settings_dict.get(field, default) == val: checked = "checked" settings_form += ''' <input type="radio" name="%s" value="%s" %s %s /> %s ''' % (field, val, checked, disabled, key) settings_form += '<br/>' sharelink_options = [("Limit sharelink creation to", "create_sharelink")] for (title, field) in sharelink_options: settings_form += '<h4>%s</h4>' % title if direct_dict.get(field, False): choices = _valid_sharelink + _reset_choice else: choices = _valid_sharelink + _keep_choice for (key, val) in choices: checked = '' if settings_dict.get(field, keyword_owners) == val: checked = "checked" settings_form += ''' <input type="radio" name="%s" value="%s" %s/> %s ''' % (field, val, checked, key) settings_form += '<br/>' field = 'request_recipients' request_recipients = settings_dict.get(field, default_vgrid_settings_limit) if direct_dict.get(field, False): direct_note = _reset_note else: direct_note = _keep_note settings_form += ''' <h4>Request Recipients</h4> Notify only first <input type="number" name="request_recipients" min=0 max=999 minlength=1 maxlength=3 value=%d required /> owners about access requests %s. ''' % (request_recipients, direct_note) settings_form += '<br/>' bool_options = [ ("Hidden", "hidden"), ] for (title, field) in bool_options: settings_form += '<h4>%s</h4>' % title if direct_dict.get(field, False): choices = _valid_bool + _reset_choice else: choices = _valid_bool + _keep_choice for (key, val) in choices: checked, inherit_note = '', '' if settings_dict.get(field, False) == val: checked = "checked" if direct_dict.get(field, False) != \ settings_dict.get(field, False): inherit_note = ''' <span class="warningtext iconspace"> Forced by a parent %(vgrid_label)s. Please disable there first if you want to change the value here.</span>''' % settings_dict settings_form += ''' <input type="radio" name="%s" value="%s" %s /> %s ''' % (field, val, checked, key) settings_form += '%s<br/>' % inherit_note settings_form += '<br/>' settings_form += ''' <input type="submit" value="Save settings" /> </fieldset> </form> ''' output_objects.append({ 'object_type': 'html_form', 'text': settings_form % settings_dict }) # Checking/fixing of missing components output_objects.append({ 'object_type': 'sectionheader', 'text': "Repair/Add Components" }) target_op = 'updatevgrid' csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) settings_dict.update({'target_op': target_op, 'csrf_token': csrf_token}) output_objects.append({ 'object_type': 'html_form', 'text': ''' <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="vgrid_name" value="%(vgrid_name)s" /> <input type="submit" value="Repair components" /> </form> ''' % settings_dict }) (owners_status, owners_direct) = vgrid_owners(vgrid_name, configuration, False) if not owners_status: logger.error("failed to load owners for %s: %s" % (vgrid_name, owners_direct)) return (output_objects, returnvalues.SYSTEM_ERROR) (members_status, members_direct) = vgrid_members(vgrid_name, configuration, False) if not members_status: logger.error("failed to load members for %s: %s" % (vgrid_name, members_direct)) return (output_objects, returnvalues.SYSTEM_ERROR) (resources_status, resources_direct) = vgrid_resources(vgrid_name, configuration, False) if not resources_status: logger.error("failed to load resources for %s: %s" % (vgrid_name, resources_direct)) return (output_objects, returnvalues.SYSTEM_ERROR) output_objects.append({ 'object_type': 'sectionheader', 'text': "Delete %s " % vgrid_name }) if len(owners_direct) > 1 or members_direct or resources_direct: output_objects.append({ 'object_type': 'html_form', 'text': ''' To delete <b>%(vgrid)s</b> first remove all resources, members and owners ending with yourself. ''' % { 'vgrid': vgrid_name } }) else: output_objects.append({ 'object_type': 'html_form', 'text': ''' <p>As the last owner you can leave and delete <b>%(vgrid)s</b> including all associated shared files and components.<br/> </p> <p class="warningtext"> You cannot undo such delete operations, so please use with great care! </p> ''' % { 'vgrid': vgrid_name } }) target_op = "rmvgridowner" csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) js_name = 'rmlastvgridowner' helper = html_post_helper( js_name, '%s.py' % target_op, { 'vgrid_name': vgrid_name, 'cert_id': client_id, 'flags': 'f', csrf_field: csrf_token }) output_objects.append({'object_type': 'html_form', 'text': helper}) output_objects.append({ 'object_type': 'link', 'destination': "javascript: confirmDialog(%s, '%s');" % (js_name, 'Really leave and delete %s?' % vgrid_name), 'class': 'removelink iconspace', 'title': 'Leave and delete %s' % vgrid_name, 'text': 'Leave and delete %s' % vgrid_name }) # Spacing output_objects.append({ 'object_type': 'html_form', 'text': ''' <div class="vertical-spacer"></div> ''' }) return (output_objects, returnvalues.OK)
def main(client_id, user_arguments_dict): """Main function used by front end""" (configuration, logger, output_objects, op_name) = \ initialize_main_variables(client_id) client_dir = client_id_dir(client_id) defaults = signature()[1] (validate_status, accepted) = validate_input_and_cert( user_arguments_dict, defaults, output_objects, client_id, configuration, allow_rejects=False, # NOTE: path can use wildcards, dst and current_dir cannot typecheck_overrides={'path': valid_path_pattern}, ) if not validate_status: return (accepted, returnvalues.CLIENT_ERROR) flags = ''.join(accepted['flags']) algo_list = accepted['hash_algo'] max_chunks = int(accepted['max_chunks'][-1]) pattern_list = accepted['path'] dst = accepted['dst'][-1] current_dir = accepted['current_dir'][-1].lstrip(os.sep) # All paths are relative to current_dir pattern_list = [os.path.join(current_dir, i) for i in pattern_list] if dst: dst = os.path.join(current_dir, dst) # Please note that base_dir must end in slash to avoid access to other # user dirs when own name is a prefix of another user name base_dir = os.path.abspath( os.path.join(configuration.user_home, client_dir)) + os.sep status = returnvalues.OK if verbose(flags): for flag in flags: output_objects.append({ 'object_type': 'text', 'text': '%s using flag: %s' % (op_name, flag) }) # IMPORTANT: path must be expanded to abs for proper chrooting abs_dir = os.path.abspath( os.path.join(base_dir, current_dir.lstrip(os.sep))) if not valid_user_path(configuration, abs_dir, base_dir, True): output_objects.append({ 'object_type': 'error_text', 'text': "You're not allowed to work in %s!" % current_dir }) logger.warning('%s tried to %s restricted path %s ! (%s)' % (client_id, op_name, abs_dir, current_dir)) return (output_objects, returnvalues.CLIENT_ERROR) if verbose(flags): output_objects.append({ 'object_type': 'text', 'text': "working in %s" % current_dir }) if dst: if not safe_handler(configuration, 'post', op_name, client_id, get_csrf_limit(configuration), accepted): output_objects.append({ 'object_type': 'error_text', 'text': '''Only accepting CSRF-filtered POST requests to prevent unintended updates''' }) return (output_objects, returnvalues.CLIENT_ERROR) # NOTE: dst already incorporates current_dir prefix here # IMPORTANT: path must be expanded to abs for proper chrooting abs_dest = os.path.abspath(os.path.join(base_dir, dst)) logger.info('chksum in %s' % abs_dest) # Don't use abs_path in output as it may expose underlying # fs layout. relative_dest = abs_dest.replace(base_dir, '') if not valid_user_path(configuration, abs_dest, base_dir, True): output_objects.append({ 'object_type': 'error_text', 'text': "Invalid path! (%s expands to an illegal path)" % dst }) logger.warning('%s tried to %s restricted path %s !(%s)' % (client_id, op_name, abs_dest, dst)) return (output_objects, returnvalues.CLIENT_ERROR) if not check_write_access(abs_dest, parent_dir=True): logger.warning('%s called without write access: %s' % (op_name, abs_dest)) output_objects.append({ 'object_type': 'error_text', 'text': 'cannot checksum to "%s": inside a read-only location!' % relative_dest }) return (output_objects, returnvalues.CLIENT_ERROR) all_lines = [] 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: output_objects.append({ 'object_type': 'file_not_found', 'name': pattern }) status = returnvalues.FILE_NOT_FOUND for abs_path in match: relative_path = abs_path.replace(base_dir, '') output_lines = [] for hash_algo in algo_list: try: chksum_helper = _algo_map.get(hash_algo, _algo_map["md5"]) checksum = chksum_helper(abs_path, max_chunks=max_chunks) line = "%s %s\n" % (checksum, relative_path) logger.info("%s %s of %s: %s" % (op_name, hash_algo, abs_path, checksum)) output_lines.append(line) except Exception 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)) status = returnvalues.SYSTEM_ERROR continue entry = {'object_type': 'file_output', 'lines': output_lines} output_objects.append(entry) all_lines += output_lines if dst and not write_file(''.join(all_lines), abs_dest, logger): output_objects.append({ 'object_type': 'error_text', 'text': "failed to write checksums to %s" % relative_dest }) logger.error("writing checksums to %s for %s failed" % (abs_dest, client_id)) status = returnvalues.SYSTEM_ERROR return (output_objects, status)
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) defaults = signature()[1] output_objects.append({ 'object_type': 'header', 'text': 'Remove Resource Owner' }) (validate_status, accepted) = validate_input_and_cert( user_arguments_dict, defaults, output_objects, client_id, configuration, allow_rejects=False, ) if not validate_status: return (accepted, returnvalues.CLIENT_ERROR) unique_resource_name = accepted['unique_resource_name'][-1] cert_id = accepted['cert_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) if not is_owner(client_id, unique_resource_name, configuration.resource_home, logger): output_objects.append({ 'object_type': 'error_text', 'text': 'You must be an owner of %s to remove another owner!' % 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 if not is_user(cert_id, configuration.mig_server_home): output_objects.append({'object_type': 'error_text', 'text' : '%s is not a valid %s user!' % \ (cert_id, configuration.short_title) }) return (output_objects, returnvalues.CLIENT_ERROR) # reject remove if cert_id is not an owner if not resource_is_owner(unique_resource_name, cert_id, configuration): output_objects.append({ 'object_type': 'error_text', 'text': '%s is not an owner of %s.' % (cert_id, unique_resource_name) }) return (output_objects, returnvalues.CLIENT_ERROR) # Remove owner (rm_status, rm_msg) = resource_remove_owners(configuration, unique_resource_name, [cert_id]) if not rm_status: output_objects.append({ 'object_type': 'error_text', 'text': 'Could not remove owner, reason: %s' % rm_msg }) return (output_objects, returnvalues.SYSTEM_ERROR) output_objects.append({ 'object_type': 'text', 'text': '%s was successfully removed and is no longer an owner of %s!' % (cert_id, unique_resource_name) }) output_objects.append({'object_type': 'link', 'destination': 'resadmin.py?unique_resource_name=%s' % \ unique_resource_name, 'class': 'adminlink iconspace', 'title': 'Administrate resource', 'text': 'Manage resource'}) return (output_objects, returnvalues.OK)
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)