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_menu=False) defaults = signature()[1] (validate_status, accepted) = validate_input_and_cert( user_arguments_dict, defaults, output_objects, client_id, configuration, allow_rejects=False, require_user=False ) if not validate_status: return (accepted, returnvalues.CLIENT_ERROR) if not 'extcert' in configuration.site_signup_methods: output_objects.append( {'object_type': 'error_text', 'text': '''X.509 certificate login is not enabled on this site'''}) return (output_objects, returnvalues.SYSTEM_ERROR) title_entry = find_entry(output_objects, 'title') title_entry['text'] = '%s certificate account sign up' % \ configuration.short_title title_entry['skipmenu'] = True form_fields = ['cert_id', 'cert_name', 'organization', 'email', 'country', 'state', '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'" # output_objects.append({'object_type': 'html_form', # 'text': ''' # <div id="contextual_help"> # <div class="help_gfx_bubble"><!-- graphically connect field with help text--></div> # <div class="help_message"><!-- filled by js --></div> # </div> # '''}) header_entry = {'object_type': 'header', 'text': '%s account sign up - with certificate login' % configuration.short_title} output_objects.append(header_entry) user_fields = {'full_name': '', 'organization': '', 'email': '', 'state': '', 'country': '', 'comment': ''} # Redirect to reqcert page without certificate requirement but without # changing access method (CGI vs. WSGI). certreq_url = os.environ['REQUEST_URI'].replace('-bin', '-sid') certreq_url = os.path.join(os.path.dirname(certreq_url), 'reqcert.py') certreq_link = {'object_type': 'link', 'destination': certreq_url, 'text': 'Request a new %s certificate account' % configuration.short_title} id_fields = distinguished_name_to_user(client_id) user_fields.update(id_fields) # 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()) # If cert auto create is on, add user without admin interaction form_method = 'post' csrf_limit = get_csrf_limit(configuration) fill_helpers = {'valid_name_chars': valid_name_chars, 'client_id': client_id, 'cert_id': client_id, 'dn_max_len': dn_max_len, 'site': configuration.short_title, 'form_method': form_method, 'csrf_field': csrf_field, 'csrf_limit': csrf_limit} if configuration.auto_add_cert_user == False: target_op = 'extcertaction' else: target_op = 'autocreate' 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] # NOTE: lock all ID fields to current certificate here ro_fields += [i for i in id_fields if not i in ro_fields] for field in ro_fields: fill_helpers['readonly_%s' % field] = 'readonly' fill_helpers.update(user_fields) html = """This page is used to sign up for %(site)s with an existing certificate from a Certificate Authority (CA) allowed for %(site)s. You can use it if you already have a x509 certificate from another accepted CA. In this way you can simply use your existing certificate for %(site)s access instead of requesting a new one. <br /> The page tries to auto load any certificate your browser provides and fill in the fields accordingly, but in case it can't guess all <span class=highlight_required>mandatory</span> fields, you still need to fill in those.<br /> Please enter any missing information below and press the Send button to submit the external certificate sign up request to the %(site)s administrators. <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, password=False, default_values=fill_helpers) # TODO : remove this legacy version? html += """ <div style="height: 0; visibility: hidden; display: none;"> <!--OLD FORM--> <div class=form_container> <!-- use post here to avoid field contents in URL --> <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'>Certificate DN</td><td><input id='cert_id_field' type=text size=%(dn_max_len)s maxlength=%(dn_max_len)s name=cert_id value='%(client_id)s' required pattern='(/[a-zA-Z]+=[^/ ]+([ ][^/ ]+)*)+' title='The Distinguished Name field of your certificate, i.e. key=value pairs separated by slashes' /></td><td class=fill_space></td></tr> <tr><td class='mandatory label'>Full name</td><td><input id='cert_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></td></tr> <tr><td class='mandatory label'>Email address</td><td><input id='email_field' type=email name=email value='%(email)s' title='A valid email address that you read' /></td><td class=fill_space></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></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></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></td></tr> <tr><td class='optional label'>Comment or reason why you should<br />be granted a %(site)s certificate:</td><td><textarea id='comment_field' rows=4 name=comment title='A free-form comment where you can explain what you need the certificate for'></textarea></td><td class=fill_space></td></tr> <tr><td class='label'><!-- empty area --></td><td><input id='submit_button' type='submit' value='Send' /></td><td class=fill_space></td></tr> </table> </form> </div> <!-- Hidden help text --> <div id='help_text'> <div id='cert_id_help'>Must be the exact Distinguished Name (DN) of your certificate</div> <div id='cert_name_help'>Your full name, restricted to the characters in '%(valid_name_chars)s'</div> <div id='organization_help'>Organization name or acronym matching email</div> <div id='email_help'>Email address associated with your organization if at all possible</div> <div id='country_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='state_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='comment_help'>Optional, but a short informative comment may help us verify your certificate needs and thus speed up our response.</div> </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) 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, 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) 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, 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] 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, op_header=False) defaults = signature()[1] output_objects.append({ 'object_type': 'header', 'text': 'Create runtime environment' }) (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) re_template = accepted['re_template'][-1].upper().strip() software_entries = int(accepted['software_entries'][-1]) environment_entries = int(accepted['environment_entries'][-1]) testprocedure_entry = int(accepted['testprocedure_entry'][-1]) template = {} if re_template: if not is_runtime_environment(re_template, configuration): output_objects.append({ 'object_type': 'error_text', 'text': "re_template ('%s') is not a valid existing runtime env!" % re_template }) return (output_objects, returnvalues.CLIENT_ERROR) (template, msg) = get_re_dict(re_template, configuration) if not template: output_objects.append({ 'object_type': 'error_text', 'text': 'Could not read re_template %s. %s' % (re_template, msg) }) return (output_objects, returnvalues.SYSTEM_ERROR) # Override template fields if user loaded a template and modified the # required entries and chose update. # Use default of 1 sw, 1 env and 0 test or template setting otherwise if software_entries < 0: software_entries = len(template.get('SOFTWARE', [None])) if environment_entries < 0: environment_entries = len(template.get('ENVIRONMENTVARIABLE', [None])) if testprocedure_entry < 0: testprocedure_entry = len(template.get('TESTPROCEDURE', [])) if 'SOFTWARE' in template: new_sw = template['SOFTWARE'][:software_entries] template['SOFTWARE'] = new_sw if 'ENVIRONMENTVARIABLE' in template: new_env = template['ENVIRONMENTVARIABLE'][:environment_entries] template['ENVIRONMENTVARIABLE'] = new_env if 'TESTPROCEDURE' in template: new_test = template['TESTPROCEDURE'][:testprocedure_entry] template['TESTPROCEDURE'] = new_test # Avoid DoS, limit number of software_entries if software_entries > max_software_entries: output_objects.append( {'object_type': 'error_text', 'text' : 'Maximum number of software_entries %s exceeded (%s)' % \ (max_software_entries, software_entries)}) return (output_objects, returnvalues.CLIENT_ERROR) # Avoid DoS, limit number of environment_entries if environment_entries > max_environment_entries: output_objects.append( {'object_type': 'error_text', 'text' : 'Maximum number of environment_entries %s exceeded (%s)' % \ (max_environment_entries, environment_entries)}) return (output_objects, returnvalues.CLIENT_ERROR) rekeywords_dict = get_keywords_dict() (list_status, ret) = list_runtime_environments(configuration) if not list_status: output_objects.append({'object_type': 'error_text', 'text': ret}) return (output_objects, returnvalues.SYSTEM_ERROR) output_objects.append({ 'object_type': 'text', 'text': 'Use existing Runtime Environment as template' }) html_form = \ """<form method='get' action='adminre.py'> <select name='re_template'> <option value=''>None</option> """ for existing_re in ret: html_form += " <option value='%s'>%s</option>\n" % \ (existing_re, existing_re) html_form += """ </select> <input type='submit' value='Get' /> </form>""" output_objects.append({'object_type': 'html_form', 'text': html_form}) output_objects.append({ 'object_type': 'text', 'text': '''Note that a runtime environment can not be changed after creation and it can only be removed if not in use by any resources, so please be careful when filling in the details''' }) output_objects.append({ 'object_type': 'text', 'text': '''Changing the number of software and environment entries removes all data in the form, so please enter the correct values before entering any information.''' }) html_form = \ """<form method='get' action='adminre.py'> <table> """ html_form += """ <tr> <td>Number of needed software entries</td> <td><input type='number' name='software_entries' min=0 max=99 minlength=1 maxlength=2 value='%s' required pattern='[0-9]{1,2}' title='number of software entries needed in runtime environment' /></td> </tr>""" % software_entries html_form += """ <tr> <td>Number of environment entries</td> <td> <input type='number' name='environment_entries' min=0 max=99 minlength=1 maxlength=2 value='%s' required pattern='[0-9]{1,2}' title='number of environment variables provided by runtime environment' /> </td> </tr>""" % environment_entries output_objects.append({'object_type': 'html_form', 'text': html_form}) if testprocedure_entry == 0: select_string = """<option value='0' selected>No</option> <option value=1>Yes</option>""" elif testprocedure_entry == 1: select_string = """<option value='0'>No</option> <option value='1' selected>Yes</option>""" else: output_objects.append({ 'object_type': 'error_text', 'text': 'testprocedure_entry should be 0 or 1, you specified %s' % testprocedure_entry }) return (output_objects, returnvalues.CLIENT_ERROR) html_form = """ <tr> <td>Runtime environment has a testprocedure</td> <td><select name='testprocedure_entry'>%s</select></td> </tr> <tr> <td colspan=2> <input type='hidden' name='re_template' value='%s' /> <input type='submit' value='Update fields' /> </td> </tr> </table> </form><br /> """ % (select_string, re_template) 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 = 'createre' 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}) html_form += """ <form method='%(form_method)s' action='%(target_op)s.py'> <input type='hidden' name='%(csrf_field)s' value='%(csrf_token)s' /> <b>Runtime Environment Name</b><br /> <small>(eg. BASH-2.X-1, must be unique):</small><br /> <input class='p80width' type='text' name='re_name' required pattern='[a-zA-Z0-9_.-]+' title='unique name of ASCII letters and digits separated only by underscores, periods and hyphens' /> <br /> <br /><b>Description:</b><br /> <textarea class='p80width' rows='4' name='redescription'> """ if template: html_form += template['DESCRIPTION'].replace('<br />', '\n') html_form += '</textarea><br />' soft_list = [] if software_entries > 0: html_form += '<br /><b>Needed Software:</b><br />' if template: if 'SOFTWARE' in template: soft_list = template['SOFTWARE'] for soft in soft_list: html_form += """ <textarea class='p80width' rows='6' name='software'>""" for keyname in soft.keys(): if keyname != '': html_form += '%s=%s\n' % (keyname, soft[keyname]) html_form += '</textarea><br />' # loop and create textareas for any missing software entries software = rekeywords_dict['SOFTWARE'] sublevel_required = [] sublevel_optional = [] if 'Sublevel' in software and software['Sublevel']: sublevel_required = software['Sublevel_required'] sublevel_optional = software['Sublevel_optional'] for _ in range(len(soft_list), software_entries): html_form += """ <textarea class='p80width' rows='6' name='software'>""" for sub_req in sublevel_required: html_form += '%s= # required\n' % sub_req for sub_opt in sublevel_optional: html_form += '%s= # optional\n' % sub_opt html_form += '</textarea><br />' if template and testprocedure_entry == 1: if 'TESTPROCEDURE' in template: html_form += """ <br /><b>Testprocedure</b> (in mRSL format):<br /> <textarea class='p80width' rows='15' name='testprocedure'>""" base64string = '' for stringpart in template['TESTPROCEDURE']: base64string += stringpart decodedstring = base64.decodestring(base64string) html_form += decodedstring html_form += '</textarea>' output_objects.append({ 'object_type': 'html_form', 'text': html_form }) html_form = """ <br /><b>Expected .stdout file if testprocedure is executed</b><br /> <textarea class='p80width' rows='10' name='verifystdout'>""" if 'VERIFYSTDOUT' in template: for line in template['VERIFYSTDOUT']: html_form += line html_form += '</textarea>' html_form += """ <br /><b>Expected .stderr file if testprocedure is executed</b><br /> <textarea cols='50' rows='10' name='verifystderr'>""" if 'VERIFYSTDERR' in template: for line in template['VERIFYSTDERR']: html_form += line html_form += '</textarea>' html_form += """ <br /><b>Expected .status file if testprocedure is executed</b><br /> <textarea cols='50' rows='10' name='verifystatus'>""" if 'VERIFYSTATUS' in template: for line in template['VERIFYSTATUS']: html_form += line html_form += '</textarea>' elif testprocedure_entry == 1: html_form += """ <br /><b>Testprocedure</b> (in mRSL format):<br /> <textarea class='p80width' rows='15' name='testprocedure'>""" html_form += \ """::EXECUTE:: ls </textarea> <br /><b>Expected .stdout file if testprocedure is executed</b><br /> <textarea class='p80width' rows='10' name='verifystdout'></textarea> <br /><b>Expected .stderr file if testprocedure is executed</b><br /> <textarea class='p80width' rows='10' name='verifystderr'></textarea> <br /><b>Expected .status file if testprocedure is executed</b><br /> <textarea class='p80width' rows='10' name='verifystatus'></textarea> """ environmentvariable = rekeywords_dict['ENVIRONMENTVARIABLE'] sublevel_required = [] sublevel_optional = [] if 'Sublevel' in environmentvariable\ and environmentvariable['Sublevel']: sublevel_required = environmentvariable['Sublevel_required'] sublevel_optional = environmentvariable['Sublevel_optional'] env_list = [] if environment_entries > 0: html_form += '<br /><b>Environments:</b><br />' if template: if 'ENVIRONMENTVARIABLE' in template: env_list = template['ENVIRONMENTVARIABLE'] for env in env_list: html_form += """ <textarea class='p80width' rows='4' name='environment'>""" for keyname in env.keys(): if keyname != '': html_form += '%s=%s\n' % (keyname, env[keyname]) html_form += '</textarea><br />' # loop and create textareas for any missing environment entries for _ in range(len(env_list), environment_entries): html_form += """ <textarea class='p80width' rows='4' name='environment'>""" for sub_req in sublevel_required: html_form += '%s= # required\n' % sub_req for sub_opt in sublevel_optional: html_form += '%s= # optional\n' % sub_opt html_form += '</textarea><br />' html_form += """<br /><br /><input type='submit' value='Create' /> </form> """ output_objects.append({ 'object_type': 'html_form', 'text': html_form % 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) defaults = signature()[1] title_entry = find_entry(output_objects, 'title') label = "%s" % configuration.site_vgrid_label title_entry['text'] = "Add %s Owner" % label output_objects.append({ 'object_type': 'header', 'text': 'Add %s Owner(s)' % label }) status = returnvalues.OK (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() cert_id_list = accepted['cert_id'] request_name = unhexlify(accepted['request_name'][-1]) rank_list = accepted['rank'] + ['' for _ in cert_id_list] # inherited vgrid membership inherit_vgrid_member = False 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) user_map = get_full_user_map(configuration) user_dict = user_map.get(client_id, None) # Optional site-wide limitation of manage vgrid permission if not user_dict or \ not vgrid_manage_allowed(configuration, user_dict): logger.warning("user %s is not allowed to manage vgrids!" % client_id) output_objects.append({ 'object_type': 'error_text', 'text': 'Only privileged users can manage %ss' % label }) return (output_objects, returnvalues.CLIENT_ERROR) # make sure vgrid settings allow this owner to edit owners (allow_status, allow_msg) = allow_owners_adm(configuration, vgrid_name, client_id) if not allow_status: output_objects.append({'object_type': 'error_text', 'text': allow_msg}) return (output_objects, returnvalues.CLIENT_ERROR) cert_id_added = [] for (cert_id, rank_str) in zip(cert_id_list, rank_list): cert_id = cert_id.strip() cert_dir = client_id_dir(cert_id) try: rank = int(rank_str) except ValueError: rank = None # Allow openid alias as subject if openid with alias is enabled if configuration.user_openid_providers and configuration.user_openid_alias: cert_id = expand_openid_alias(cert_id, configuration) # 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, _) = \ init_vgrid_script_add_rem(vgrid_name, client_id, cert_id, 'owner', configuration) if not ret_val: output_objects.append({'object_type': 'error_text', 'text': msg}) status = returnvalues.CLIENT_ERROR continue # don't add if already an owner unless rank is given if rank is None and vgrid_is_owner(vgrid_name, cert_id, configuration): output_objects.append({ 'object_type': 'error_text', 'text': '%s is already an owner of %s or a parent %s.' % (cert_id, vgrid_name, label) }) status = returnvalues.CLIENT_ERROR continue # don't add if already a direct member if vgrid_is_member(vgrid_name, cert_id, configuration, recursive=False): output_objects.append({ 'object_type': 'error_text', 'text': '%s is already a member of %s - please remove first.' % (cert_id, vgrid_name) }) status = returnvalues.CLIENT_ERROR continue # owner of subvgrid? (list_status, subvgrids) = vgrid_list_subvgrids(vgrid_name, configuration) if not list_status: output_objects.append({ 'object_type': 'error_text', 'text': 'Error getting list of sub%ss: %s' % (label, subvgrids) }) status = returnvalues.SYSTEM_ERROR continue skip_entity = False for subvgrid in subvgrids: if vgrid_is_owner(subvgrid, cert_id, configuration, recursive=False): output_objects.append({ 'object_type': 'error_text', 'text': """%s is already an owner of a sub-%s ('%s'). Please remove the person first and then try this operation again.""" % (cert_id, label, subvgrid) }) status = returnvalues.CLIENT_ERROR skip_entity = True break if vgrid_is_member(subvgrid, cert_id, configuration, recursive=False): output_objects.append({ 'object_type': 'error_text', 'text': """%s is already a member of a sub-%s ('%s'). Please remove the person first and then try this operation again.""" % (cert_id, label, subvgrid) }) status = returnvalues.CLIENT_ERROR skip_entity = True break if skip_entity: continue # we DO allow ownership if member of parent vgrid - only handle with care if vgrid_is_member(vgrid_name, cert_id, configuration): # list is in top-down order parent_vgrids = vgrid_list_parents(vgrid_name, configuration) inherit_vgrid_member = vgrid_name for parent in parent_vgrids: if vgrid_is_member(parent, cert_id, configuration, recursive=False): inherit_vgrid_member = parent break output_objects.append({ 'object_type': 'text', 'text': '''NOTE: %s is already a member of parent %s %s.''' % (cert_id, label, inherit_vgrid_member) }) # Check if only rank change was requested and apply if so if rank is not None: (add_status, add_msg) = vgrid_add_owners(configuration, vgrid_name, [cert_id], rank=rank) if not add_status: output_objects.append({ 'object_type': 'error_text', 'text': add_msg }) status = returnvalues.SYSTEM_ERROR else: output_objects.append({ 'object_type': 'text', 'text': 'changed %s to owner %d' % (cert_id, rank) }) # No further action after rank change as everything else exists continue # Getting here means cert_id is not owner of any parent or child vgrids. # may still be member of a parent grid but not a child vgrid. public_base_dir = \ os.path.abspath(os.path.join(configuration.vgrid_public_base, vgrid_name)) + os.sep private_base_dir = \ os.path.abspath(os.path.join(configuration.vgrid_private_base, vgrid_name)) + os.sep # 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 user_dir = os.path.abspath( os.path.join(configuration.user_home, cert_dir)) + os.sep user_public_base = os.path.abspath( os.path.join(user_dir, 'public_base')) + os.sep user_private_base = os.path.abspath( os.path.join(user_dir, 'private_base')) + os.sep # make sure all dirs can be created (that a file or directory with the same # name do not exist prior to adding the owner) if os.path.exists(user_public_base + vgrid_name): output_objects.append({ 'object_type': 'error_text', 'text': '''Could not add owner, a file or directory in public_base exists with the same name! %s''' % user_dir + vgrid_name }) status = returnvalues.CLIENT_ERROR continue if os.path.exists(user_private_base + vgrid_name): output_objects.append({ 'object_type': 'error_text', 'text': '''Could not add owner, a file or directory in private_base exists with the same name!''' }) status = returnvalues.CLIENT_ERROR continue # vgrid share already exists if user is a member of parent vgrid if not inherit_vgrid_member and os.path.exists(user_dir + vgrid_name): output_objects.append({ 'object_type': 'error_text', 'text': '''Could not add owner, a file or directory in the home directory exists with the same name!''' }) status = returnvalues.CLIENT_ERROR continue # Add (add_status, add_msg) = vgrid_add_owners(configuration, vgrid_name, [cert_id]) if not add_status: output_objects.append({ 'object_type': 'error_text', 'text': add_msg }) status = returnvalues.SYSTEM_ERROR continue vgrid_name_parts = vgrid_name.split('/') is_subvgrid = len(vgrid_name_parts) > 1 # create public_base in cert_ids home dir if it does not exists try: os.mkdir(user_public_base) except Exception as exc: pass # create private_base in cert_ids home dir if it does not exists try: os.mkdir(user_private_base) except Exception as exc: pass if is_subvgrid: share_dir = None try: # Example: # vgrid_name = IMADA/STUD/BACH # vgrid_name_last_fragment = BACH # vgrid_name_without_last_fragment = IMADA/STUD/ vgrid_name_last_fragment = \ vgrid_name_parts[len(vgrid_name_parts) - 1].strip() vgrid_name_without_last_fragment = \ ('/'.join(vgrid_name_parts[0:len(vgrid_name_parts) - 1]) + os.sep).strip() # create dirs if they do not exist share_dir = user_dir + vgrid_name_without_last_fragment if not os.path.isdir(share_dir): os.makedirs(share_dir) pub_dir = user_public_base + vgrid_name_without_last_fragment if not os.path.isdir(pub_dir): os.makedirs(pub_dir) priv_dir = user_private_base + vgrid_name_without_last_fragment if not os.path.isdir(priv_dir): os.makedirs(priv_dir) except Exception as exc: # out of range? should not be possible due to is_subvgrid check output_objects.append({ 'object_type': 'error_text', 'text': ('Could not create needed dirs on %s server! %s' % (configuration.short_title, exc)) }) logger.error('%s when looking for dir %s.' % (exc, share_dir)) status = returnvalues.SYSTEM_ERROR continue # create symlink from users home directory to vgrid file directory # unless member of parent vgrid so that it is included already link_src = os.path.abspath(configuration.vgrid_files_home + os.sep + vgrid_name) + os.sep link_dst = user_dir + vgrid_name if not inherit_vgrid_member and \ not make_symlink(link_src, link_dst, logger): output_objects.append({ 'object_type': 'error_text', 'text': 'Could not create link to %s share!' % label }) logger.error('Could not create link to %s files (%s -> %s)' % (label, link_src, link_dst)) status = returnvalues.SYSTEM_ERROR continue public_base_dst = user_public_base + vgrid_name # create symlink for public_base files if not make_symlink(public_base_dir, public_base_dst, logger): output_objects.append({ 'object_type': 'error_text', 'text': 'Could not create link to public_base dir!' }) logger.error( 'Could not create link to public_base dir (%s -> %s)' % (public_base_dir, public_base_dst)) status = returnvalues.SYSTEM_ERROR continue private_base_dst = user_private_base + vgrid_name # create symlink for private_base files if not make_symlink(private_base_dir, private_base_dst, logger): output_objects.append({ 'object_type': 'error_text', 'text': 'Could not create link to private_base dir!' }) status = returnvalues.SYSTEM_ERROR continue if configuration.trac_admin_path: public_tracker_dir = \ os.path.abspath(os.path.join( configuration.vgrid_public_base, vgrid_name, '.vgridtracker')) private_tracker_dir = \ os.path.abspath(os.path.join( configuration.vgrid_private_base, vgrid_name, '.vgridtracker')) vgrid_tracker_dir = \ os.path.abspath(os.path.join( configuration.vgrid_files_home, vgrid_name, '.vgridtracker')) for tracker_dir in [ public_tracker_dir, private_tracker_dir, vgrid_tracker_dir ]: if not add_tracker_admin(configuration, cert_id, vgrid_name, tracker_dir, output_objects): status = returnvalues.SYSTEM_ERROR continue cert_id_added.append(cert_id) if request_name: request_dir = os.path.join(configuration.vgrid_home, vgrid_name) if 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 request for %s in %s!' % (vgrid_name, request_name) }) if cert_id_added: output_objects.append({ 'object_type': 'html_form', 'text': 'New owner(s)<br />%s<br />successfully added to %s %s!' '' % ('<br />'.join(cert_id_added), vgrid_name, label) }) cert_id_fields = '' for cert_id in cert_id_added: cert_id_fields += """<input type=hidden name=cert_id value='%s' /> """ % cert_id form_method = 'post' csrf_limit = get_csrf_limit(configuration) fill_helpers = { 'vgrid_name': vgrid_name, 'cert_id': cert_id, 'protocol': any_protocol, 'short_title': configuration.short_title, 'vgrid_label': label, 'cert_id_fields': cert_id_fields, 'form_method': form_method, 'csrf_field': csrf_field, 'csrf_limit': csrf_limit } 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': """ <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='vgridaccept' /> <input type=hidden name=vgrid_name value='%(vgrid_name)s' /> %(cert_id_fields)s <input type=hidden name=protocol value='%(protocol)s' /> <table> <tr> <td class='title'>Custom message to user(s)</td> </tr><tr> <td><textarea name=request_text cols=72 rows=10> We have granted you ownership access to our %(vgrid_name)s %(vgrid_label)s. You can access the %(vgrid_label)s administration page from the %(vgrid_label)ss page on %(short_title)s. Regards, the %(vgrid_name)s %(vgrid_label)s owners </textarea></td> </tr> <tr> <td><input type='submit' value='Inform user(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, status)
def edit_file(configuration, client_id, path, abs_path, output_format='html', includes=edit_includes): """Format and return the contents of a given file""" text = '' if os.path.isfile(abs_path): try: src_fd = open(abs_path, 'rb') text = src_fd.read() src_fd.close() except Exception as exc: return 'Failed to open file %s: %s' % (path, exc) form_method = 'post' csrf_limit = get_csrf_limit(configuration) fill_helpers = { 'path': path, 'lock_suffix': edit_lock_suffix, 'output_format': output_format, 'text': text, 'form_method': form_method, 'csrf_field': csrf_field, 'csrf_limit': csrf_limit } target_op = 'editfile' 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}) html = '''Select file:<br /> <form id="editor_form" enctype="multipart/form-data" method="%(form_method)s" action="%(target_op)s.py"> <input type="hidden" name="%(csrf_field)s" value="%(csrf_token)s" /> <input type="hidden" name="output_format" value="%(output_format)s" /> <input id="editorpath" class="fillwidth padspace" type="text" name="path" value="%(path)s" /> <br /><br /> Edit contents:<br /> <textarea id="editorarea" class="fillwidth padspace" rows="25" name="editarea">%(text)s</textarea> ''' % fill_helpers if 'switcher' in includes: html += ''' <ul id="switcher"> <li class="html currentSet"><a href="#">HTML/Text Editor</a></li> <li class="codemirror"><a href="#">Code Editor</a></li> <!-- <li class="txt2tags"><a href="#">Txt2Tags Editor</a></li> --> <li class="remove"><a href="#">Raw text field</a></li> </ul> ''' if 'newline' in includes: html += ''' <br /> Newline mode: <select name="newline"> <option selected value="unix">UNIX</option> <option value="mac">Mac OS (pre OS X)</option> <option value="windows">DOS / Windows</option> </select> <a class="infolink iconspace" href="http://en.wikipedia.org/wiki/Newline">help </a> ''' if configuration.site_enable_jobs and 'submit' in includes: html += ''' <br /> Submit file as job after saving <input type=checkbox name="submitjob" /> ''' if 'save' in includes: html += ''' <br /> "Save changes" stores the edited contents in the selected file.<br /> "Forget changes" reloads the last saved version of the selected file.<br /> <input type="submit" value="Save" /> ---------- <input type="reset" value="Forget changes" /> ''' html += ''' </form> ''' if 'discard' in includes: target_op = 'rm' 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}) html += ''' <form id="discard_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="%(output_format)s" /> <input type="hidden" name="flags" value="rf" /> <input type="hidden" name="path" value="%(path)s%(lock_suffix)s" /> <input type="submit" value="Discard changes" /> </form> ''' % fill_helpers if 'spellcheck' in includes: html += ''' <p> <form id="spell_form" method="%(form_method)s" action="spell.py"> <input type="hidden" name="output_format" value="%(output_format)s" /> Spell check (last saved) contents:<br /> <input type="hidden" name="path" value="%(path)s" /> Language: <select name="lang"> <option value="da">Danish</option> <option value="da_dk">Danish - DK</option> <option selected value="en">English</option> <option value="en_gb">English - GB</option> <option value="en_us">English - US</option> </select> Type: <select name="mode"> <option selected value="none">Text</option> <option value="url">URL</option> <option value="email">Email</option> <option value="sgml">SGML</option> <option value="tex">LaTeX</option> </select> <input type="submit" value="Check" /> </form> <p> ''' return html % fill_helpers
def main(client_id, user_arguments_dict, environ=None): """Main function used by front end""" if environ is None: environ = os.environ (configuration, logger, output_objects, op_name) = \ initialize_main_variables(client_id, op_header=False, op_menu=client_id) defaults = signature()[1] (validate_status, accepted) = validate_input( user_arguments_dict, defaults, output_objects, allow_rejects=False, # NOTE: path can use wildcards, current_dir cannot typecheck_overrides={'path': valid_path_pattern}, ) if not validate_status: return (accepted, returnvalues.CLIENT_ERROR) flags = ''.join(accepted['flags']) pattern_list = accepted['path'] current_dir = accepted['current_dir'][-1].lstrip('/') share_id = accepted['share_id'][-1] status = returnvalues.OK read_mode, write_mode = True, True # Either authenticated user client_id set or sharelink ID if client_id: user_id = client_id target_dir = client_id_dir(client_id) base_dir = configuration.user_home redirect_name = configuration.site_user_redirect redirect_path = redirect_name id_args = '' root_link_name = 'USER HOME' main_class = "user_ls" page_title = 'User Files' userstyle = True widgets = True visibility_mods = ''' .%(main_class)s .disable_read { display: none; } .%(main_class)s .disable_write { display: none; } ''' elif share_id: try: (share_mode, _) = extract_mode_id(configuration, share_id) except ValueError as err: logger.error('%s called with invalid share_id %s: %s' % (op_name, share_id, err)) output_objects.append({ 'object_type': 'error_text', 'text': 'Invalid sharelink ID: %s' % share_id }) return (output_objects, returnvalues.CLIENT_ERROR) # TODO: load and check sharelink pickle (currently requires client_id) # then include shared by %(owner)s on page header user_id = 'anonymous user through share ID %s' % share_id target_dir = os.path.join(share_mode, share_id) base_dir = configuration.sharelink_home redirect_name = 'share_redirect' redirect_path = os.path.join(redirect_name, share_id) id_args = 'share_id=%s;' % share_id root_link_name = '%s' % share_id main_class = "sharelink_ls" page_title = 'Shared Files' userstyle = False widgets = False # default to include file info if flags == '': flags += 'f' if share_mode == 'read-only': write_mode = False visibility_mods = ''' .%(main_class)s .enable_write { display: none; } .%(main_class)s .disable_read { display: none; } ''' elif share_mode == 'write-only': read_mode = False visibility_mods = ''' .%(main_class)s .enable_read { display: none; } .%(main_class)s .disable_write { display: none; } ''' else: visibility_mods = ''' .%(main_class)s .disable_read { display: none; } .%(main_class)s .disable_write { display: none; } ''' else: logger.error('%s called without proper auth: %s' % (op_name, accepted)) output_objects.append({ 'object_type': 'error_text', 'text': 'Authentication is missing!' }) return (output_objects, returnvalues.SYSTEM_ERROR) visibility_toggle = ''' <style> %s </style> ''' % (visibility_mods % { 'main_class': main_class }) # Please note that base_dir must end in slash to avoid access to other # user dirs when own name is a prefix of another user name base_dir = os.path.abspath(os.path.join(base_dir, target_dir)) + os.sep if not os.path.isdir(base_dir): logger.error('%s called on missing base_dir: %s' % (op_name, base_dir)) output_objects.append({ 'object_type': 'error_text', 'text': 'No such %s!' % page_title.lower() }) return (output_objects, returnvalues.CLIENT_ERROR) title_entry = find_entry(output_objects, 'title') title_entry['text'] = page_title title_entry['skipwidgets'] = not widgets title_entry['skipuserstyle'] = not userstyle user_settings = title_entry.get('user_settings', {}) open_button_id = 'open_fancy_upload' form_method = 'post' csrf_limit = get_csrf_limit(configuration) fill_helpers = { 'dest_dir': current_dir + os.sep, 'share_id': share_id, 'flags': flags, 'tmp_flags': flags, 'long_set': long_list(flags), 'recursive_set': recursive(flags), 'all_set': all(flags), 'fancy_open': open_button_id, 'fancy_dialog': fancy_upload_html(configuration), 'form_method': form_method, 'csrf_field': csrf_field, 'csrf_limit': csrf_limit } target_op = 'uploadchunked' csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) fill_helpers.update({'target_op': target_op, 'csrf_token': csrf_token}) (cf_import, cf_init, cf_ready) = confirm_js(configuration) (fu_import, fu_init, fu_ready) = fancy_upload_js(configuration, 'function() { location.reload(); }', share_id, csrf_token) add_import = ''' %s %s ''' % (cf_import, fu_import) add_init = ''' %s %s %s %s ''' % (cf_init, fu_init, select_all_javascript(), selected_file_actions_javascript()) add_ready = ''' %s %s /* wrap openFancyUpload in function to avoid event data as argument */ $("#%s").click(function() { openFancyUpload(); }); $("#checkall_box").click(toggleChecked); ''' % (cf_ready, fu_ready, open_button_id) # TODO: can we update style inline to avoid explicit themed_styles? styles = themed_styles( configuration, advanced=['jquery.fileupload.css', 'jquery.fileupload-ui.css'], skin=['fileupload-ui.custom.css'], user_settings=user_settings) styles['advanced'] += ''' %s ''' % visibility_toggle title_entry['style'] = styles title_entry['script']['advanced'] += add_import title_entry['script']['init'] += add_init title_entry['script']['ready'] += add_ready title_entry['script']['body'] = ' class="%s"' % main_class output_objects.append({'object_type': 'header', 'text': page_title}) # TODO: move to output html handler output_objects.append({ 'object_type': 'html_form', 'text': confirm_html(configuration) }) # Shared URL helpers ls_url_template = 'ls.py?%scurrent_dir=%%(rel_dir_enc)s;flags=%s' % \ (id_args, flags) csrf_token = make_csrf_token(configuration, form_method, 'rm', client_id, csrf_limit) rm_url_template = 'rm.py?%spath=%%(rel_path_enc)s;%s=%s' % \ (id_args, csrf_field, csrf_token) rmdir_url_template = 'rm.py?%spath=%%(rel_path_enc)s;flags=r;%s=%s' % \ (id_args, csrf_field, csrf_token) editor_url_template = 'editor.py?%spath=%%(rel_path_enc)s' % id_args redirect_url_template = '/%s/%%(rel_path_enc)s' % redirect_path location_pre_html = """ <div class='files'> <table class='files'> <tr class=title><td class=centertext> Working directory: </td></tr> <tr><td class='centertext'> """ output_objects.append({ 'object_type': 'html_form', 'text': location_pre_html }) # Use current_dir nav location links for pattern in pattern_list[:1]: links = [] links.append({ 'object_type': 'link', 'text': root_link_name, 'destination': ls_url_template % { 'rel_dir_enc': '.' } }) prefix = '' parts = os.path.normpath(current_dir).split(os.sep) for i in parts: if i == ".": continue prefix = os.path.join(prefix, i) links.append({ 'object_type': 'link', 'text': i, 'destination': ls_url_template % { 'rel_dir_enc': quote(prefix) } }) output_objects.append({ 'object_type': 'multilinkline', 'links': links, 'sep': ' %s ' % os.sep }) location_post_html = """ </td></tr> </table> </div> <br /> """ output_objects.append({ 'object_type': 'html_form', 'text': location_post_html }) more_html = """ <div class='files if_full'> <form method='%(form_method)s' name='fileform' onSubmit='return selectedFilesAction();'> <table class='files'> <tr class=title><td class=centertext colspan=2> Advanced file actions </td></tr> <tr><td> Action on paths selected below (please hold mouse cursor over button for a description): </td> <td class=centertext> <input type='hidden' name='output_format' value='html' /> <input type='hidden' name='flags' value='v' /> <input type='submit' title='Show concatenated contents (cat)' onClick='document.pressed=this.value' value='cat' /> <input type='submit' onClick='document.pressed=this.value' value='head' title='Show first lines (head)' /> <input type='submit' onClick='document.pressed=this.value' value='tail' title='Show last lines (tail)' /> <input type='submit' onClick='document.pressed=this.value' value='wc' title='Count lines/words/chars (wc)' /> <input type='submit' onClick='document.pressed=this.value' value='stat' title='Show details (stat)' /> <input type='submit' onClick='document.pressed=this.value' value='touch' title='Update timestamp (touch)' /> <input type='submit' onClick='document.pressed=this.value' value='truncate' title='truncate! (truncate)' /> <input type='submit' onClick='document.pressed=this.value' value='rm' title='delete! (rm)' /> <input type='submit' onClick='document.pressed=this.value' value='rmdir' title='Remove directory (rmdir)' /> <input type='submit' onClick='document.pressed=this.value' value='submit' title='Submit file (submit)' /> </td></tr> </table> </form> </div> """ % { 'form_method': form_method } output_objects.append({'object_type': 'html_form', 'text': more_html}) dir_listings = [] output_objects.append({ 'object_type': 'dir_listings', 'dir_listings': dir_listings, 'flags': flags, 'redirect_name': redirect_name, 'redirect_path': redirect_path, 'share_id': share_id, 'ls_url_template': ls_url_template, 'rm_url_template': rm_url_template, 'rmdir_url_template': rmdir_url_template, 'editor_url_template': editor_url_template, 'redirect_url_template': redirect_url_template, }) first_match = None for pattern in pattern_list: # Check directory traversal attempts before actual handling to avoid # leaking information about file system layout while allowing # consistent error messages current_path = os.path.normpath(os.path.join(base_dir, current_dir)) unfiltered_match = glob.glob(current_path + os.sep + pattern) match = [] for server_path in unfiltered_match: # IMPORTANT: path must be expanded to abs for proper chrooting abs_path = os.path.abspath(server_path) if not valid_user_path(configuration, abs_path, base_dir, True): logger.warning('%s tried to %s restricted path %s ! (%s)' % (user_id, op_name, abs_path, pattern)) continue match.append(abs_path) if not first_match: first_match = abs_path # Now actually treat list of allowed matchings and notify if no # (allowed) match if not match: output_objects.append({ 'object_type': 'file_not_found', 'name': pattern }) status = returnvalues.FILE_NOT_FOUND # Never show any ls output in write-only mode (css hide is not enough!) if not read_mode: continue for abs_path in match: if abs_path + os.sep == base_dir: relative_path = '.' else: relative_path = abs_path.replace(base_dir, '') entries = [] dir_listing = { 'object_type': 'dir_listing', 'relative_path': relative_path, 'entries': entries, 'flags': flags, } try: gdp_iolog(configuration, client_id, environ['REMOTE_ADDR'], 'accessed', [relative_path]) except GDPIOLogError as exc: output_objects.append({ 'object_type': 'error_text', 'text': "%s: '%s': %s" % (op_name, relative_path, exc) }) logger.error("%s: failed on '%s': %s" % (op_name, relative_path, exc)) continue handle_ls(configuration, output_objects, entries, base_dir, abs_path, flags, 0) dir_listings.append(dir_listing) output_objects.append({ 'object_type': 'html_form', 'text': """<br/> <div class='files disable_read'> <p class='info icon'>""" }) # Shared message for text (e.g. user scripts) and html-format if not read_mode: # Please note that we use verbatim to get info icon right in html output_objects.append({ 'object_type': 'verbatim', 'text': """ This is a write-only share so you do not have access to see the files, only upload data and create directories. """ }) output_objects.append({ 'object_type': 'html_form', 'text': """ </p> </div> <div class='files enable_read'> <form method='get' action='ls.py'> <table class='files'> <tr class=title><td class=centertext> Filter paths (wildcards like * and ? are allowed) <input type='hidden' name='output_format' value='html' /> <input type='hidden' name='flags' value='%(flags)s' /> <input type='hidden' name='share_id' value='%(share_id)s' /> <input name='current_dir' type='hidden' value='%(dest_dir)s' /> <input type='text' name='path' value='' /> <input type='submit' value='Filter' /> </td></tr> </table> </form> </div> """ % fill_helpers }) # Short/long format buttons fill_helpers['tmp_flags'] = flags + 'l' htmlform = """ <table class='files if_full'> <tr class=title><td class=centertext colspan=4> File view options </td></tr> <tr><td colspan=4><br /></td></tr> <tr class=title><td>Parameter</td><td>Setting</td><td>Enable</td><td>Disable</td></tr> <tr><td>Long format</td><td> %(long_set)s</td><td> <form method='get' action='ls.py'> <input type='hidden' name='output_format' value='html' /> <input type='hidden' name='flags' value='%(tmp_flags)s' /> <input type='hidden' name='share_id' value='%(share_id)s' /> <input name='current_dir' type='hidden' value='%(dest_dir)s' /> """ % fill_helpers for entry in pattern_list: htmlform += "<input type='hidden' name='path' value='%s' />" % entry fill_helpers['tmp_flags'] = flags.replace('l', '') htmlform += """ <input type='submit' value='On' /><br /> </form> </td><td> <form method='get' action='ls.py'> <input type='hidden' name='output_format' value='html' /> <input type='hidden' name='flags' value='%(tmp_flags)s' /> <input type='hidden' name='share_id' value='%(share_id)s' /> <input name='current_dir' type='hidden' value='%(dest_dir)s' /> """ % fill_helpers for entry in pattern_list: htmlform += "<input type='hidden' name='path' value='%s' />" % entry htmlform += """ <input type='submit' value='Off' /><br /> </form> </td></tr> """ # Recursive output fill_helpers['tmp_flags'] = flags + 'r' htmlform += """ <!-- Non-/recursive list buttons --> <tr><td>Recursion</td><td> %(recursive_set)s</td><td>""" % fill_helpers htmlform += """ <form method='get' action='ls.py'> <input type='hidden' name='output_format' value='html' /> <input type='hidden' name='flags' value='%(tmp_flags)s' /> <input type='hidden' name='share_id' value='%(share_id)s' /> <input name='current_dir' type='hidden' value='%(dest_dir)s' /> """ % fill_helpers for entry in pattern_list: htmlform += "<input type='hidden' name='path' value='%s' />" % entry fill_helpers['tmp_flags'] = flags.replace('r', '') htmlform += """ <input type='submit' value='On' /><br /> </form> </td><td> <form method='get' action='ls.py'> <input type='hidden' name='output_format' value='html' /> <input type='hidden' name='flags' value='%(tmp_flags)s' /> <input type='hidden' name='share_id' value='%(share_id)s' /> <input name='current_dir' type='hidden' value='%(dest_dir)s' /> """ % fill_helpers for entry in pattern_list: htmlform += "<input type='hidden' name='path' value='%s' />"\ % entry htmlform += """ <input type='submit' value='Off' /><br /> </form> </td></tr> """ htmlform += """ <!-- Show dot files buttons --> <tr><td>Show hidden files</td><td> %(all_set)s</td><td>""" % fill_helpers fill_helpers['tmp_flags'] = flags + 'a' htmlform += """ <form method='get' action='ls.py'> <input type='hidden' name='output_format' value='html' /> <input type='hidden' name='flags' value='%(tmp_flags)s' /> <input type='hidden' name='share_id' value='%(share_id)s' /> <input name='current_dir' type='hidden' value='%(dest_dir)s' /> """ % fill_helpers for entry in pattern_list: htmlform += "<input type='hidden' name='path' value='%s' />" % entry fill_helpers['tmp_flags'] = flags.replace('a', '') htmlform += """ <input type='submit' value='On' /><br /> </form> </td><td> <form method='get' action='ls.py'> <input type='hidden' name='output_format' value='html' /> <input type='hidden' name='flags' value='%(tmp_flags)s' /> <input type='hidden' name='share_id' value='%(share_id)s' /> <input name='current_dir' type='hidden' value='%(dest_dir)s' /> """ % fill_helpers for entry in pattern_list: htmlform += "<input type='hidden' name='path' value='%s' />" % entry htmlform += """ <input type='submit' value='Off' /><br /> </form> </td></tr> </table> """ # show flag buttons after contents to limit clutter output_objects.append({'object_type': 'html_form', 'text': htmlform}) # create additional action forms if first_match: htmlform = """ <br /> <div class='files disable_write'> <p class='info icon'> This is a read-only share so you do not have access to edit or add files, only view data. </p> </div> <table class='files enable_write if_full'> <tr class=title><td class=centertext colspan=3> Edit file </td></tr> <tr><td> Fill in the path of a file to edit and press 'edit' to open that file in the<br /> online file editor. Alternatively a file can be selected for editing through<br /> the listing of personal files. </td><td colspan=2 class=righttext> <form name='editor' method='get' action='editor.py'> <input type='hidden' name='output_format' value='html' /> <input type='hidden' name='share_id' value='%(share_id)s' /> <input name='current_dir' type='hidden' value='%(dest_dir)s' /> <input type='text' name='path' size=50 value='' required /> <input type='submit' value='edit' /> </form> </td></tr> </table> <br />""" % fill_helpers target_op = 'mkdir' csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) fill_helpers.update({'target_op': target_op, 'csrf_token': csrf_token}) htmlform += """ <table class='files enable_write'> <tr class=title><td class=centertext colspan=4> Create directory </td></tr> <tr><td> Name of new directory to be created in current directory (%(dest_dir)s) </td><td class=righttext colspan=3> <form method='%(form_method)s' action='%(target_op)s.py'> <input type='hidden' name='%(csrf_field)s' value='%(csrf_token)s' /> <input type='hidden' name='share_id' value='%(share_id)s' /> <input name='current_dir' type='hidden' value='%(dest_dir)s' /> <input name='path' size=50 required /> <input type='submit' value='Create' name='mkdirbutton' /> </form> </td></tr> </table> <br /> """ % fill_helpers target_op = 'textarea' csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) fill_helpers.update({'target_op': target_op, 'csrf_token': csrf_token}) htmlform += """ <form enctype='multipart/form-data' method='%(form_method)s' action='%(target_op)s.py'> <input type='hidden' name='%(csrf_field)s' value='%(csrf_token)s' /> <input type='hidden' name='share_id' value='%(share_id)s' /> <table class='files enable_write if_full'> <tr class='title'><td class=centertext colspan=4> Upload file </td></tr> <tr><td colspan=4> Upload file to current directory (%(dest_dir)s) </td></tr> <tr class='if_full'><td colspan=2> Extract package files (.zip, .tar.gz, .tar.bz2) </td><td colspan=2> <input type=checkbox name='extract_0' /> </td></tr> <tr class='if_full'><td colspan=2> Submit mRSL files (also .mRSL files included in packages) </td><td colspan=2> <input type=checkbox name='submitmrsl_0' /> </td></tr> <tr><td> File to upload </td><td class=righttext colspan=3> <input name='fileupload_0_0_0' type='file'/> </td></tr> <tr><td> Optional remote filename (extra useful in windows) </td><td class=righttext colspan=3> <input name='default_remotefilename_0' type='hidden' value='%(dest_dir)s'/> <input name='remotefilename_0' type='text' size='50' value='%(dest_dir)s'/> <input type='submit' value='Upload' name='sendfile'/> </td></tr> </table> </form> %(fancy_dialog)s <table class='files enable_write'> <tr class='title'><td class='centertext'> Upload files efficiently (using chunking). </td></tr> <tr><td class='centertext'> <button id='%(fancy_open)s'>Open Upload dialog</button> </td></tr> </table> <script type='text/javascript' > setUploadDest('%(dest_dir)s'); </script> """ % fill_helpers output_objects.append({'object_type': 'html_form', 'text': htmlform}) return (output_objects, status)
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') title_entry['text'] = 'People' (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) operation = accepted['operation'][-1] caching = (accepted['caching'][-1].lower() in ('true', 'yes')) 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 "send" # table initially sorted by 0 (name) # NOTE: We distinguish between caching on page load and forced refresh refresh_helper = 'ajax_people(%s, %%s)' refresh_call = refresh_helper % configuration.notify_protocols table_spec = { 'table_id': 'usertable', 'sort_order': '[[0,0]]', 'refresh_call': refresh_call % 'false' } (add_import, add_init, add_ready) = man_base_js(configuration, [table_spec], {'width': 640}) 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': 'People'}) output_objects.append({ 'object_type': 'text', 'text': 'View and communicate with other users.' }) output_objects.append({ 'object_type': 'sectionheader', 'text': 'All users' }) # Helper form for sends 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( 'sendmsg', '%s.py' % target_op, { 'cert_id': '__DYNAMIC__', 'protocol': '__DYNAMIC__', 'request_type': 'plain', 'request_text': '', csrf_field: csrf_token }) output_objects.append({'object_type': 'html_form', 'text': helper}) output_objects.append({ 'object_type': 'table_pager', 'entry_name': 'people', 'default_entries': default_pager_entries }) users = [] if operation in list_operations: logger.info("get vgrid and user map with caching %s" % caching) visible_user = user_visible_user_confs(configuration, client_id, caching) vgrid_access = user_vgrid_access(configuration, client_id, caching=caching) anon_map = anon_to_real_user_map(configuration) if not visible_user: output_objects.append({ 'object_type': 'error_text', 'text': 'no users found!' }) return (output_objects, returnvalues.SYSTEM_ERROR) # NOTE: use simple pending check if caching to avoid lock during update if caching: pending_updates = pending_vgrids_update(configuration) or \ pending_users_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") for (visible_user_id, user_dict) in visible_user.items(): user_id = visible_user_id if visible_user_id in anon_map.keys(): # Maintain user anonymity pretty_id = 'Anonymous user with unique ID %s' % visible_user_id user_id = anon_map[visible_user_id] else: # Show user-friendly version of user ID hide_email = user_dict.get(CONF, {}).get('HIDE_EMAIL_ADDRESS', True) pretty_id = pretty_format_user(user_id, hide_email) anon_img = "%s/anonymous.png" % configuration.site_images avatar_size = 32 user_obj = { 'object_type': 'user', 'name': visible_user_id, 'pretty_id': pretty_id, 'avatar_url': anon_img } user_obj.update(user_dict) # NOTE: datetime is not json-serializable so we force to string created = user_obj.get(CONF, {}).get('CREATED_TIMESTAMP', '') if created: user_obj[CONF]['CREATED_TIMESTAMP'] = str(created) # TODO: consider ALWAYS using anon_id format in link here user_obj['userdetailslink'] = \ {'object_type': 'link', 'destination': 'viewuser.py?cert_id=%s' % quote(visible_user_id), 'class': 'infolink iconspace', 'title': 'View details for %s' % visible_user_id, 'text': ''} vgrids_allow_email = user_dict[CONF].get('VGRIDS_ALLOW_EMAIL', []) vgrids_allow_im = user_dict[CONF].get('VGRIDS_ALLOW_IM', []) if any_vgrid in vgrids_allow_email: email_vgrids = vgrid_access else: email_vgrids = set(vgrids_allow_email).intersection( vgrid_access) if email_vgrids and configuration.site_enable_gravatars: visible_email = extract_field(user_id, 'email') user_obj['avatar_url'] = user_gravatar_url( configuration, visible_email, avatar_size) if any_vgrid in vgrids_allow_im: im_vgrids = vgrid_access else: im_vgrids = set(vgrids_allow_im).intersection(vgrid_access) for proto in configuration.notify_protocols: if not email_vgrids and proto == 'email': continue if not im_vgrids and proto != 'email': continue if user_obj[CONF].get(proto.upper(), None): link = 'send%slink' % proto user_obj[link] = { 'object_type': 'link', 'destination': "javascript: confirmDialog(%s, '%s', '%s', %s);" % ('sendmsg', 'Really send %s message to %s?' % (proto, visible_user_id), 'request_text', "{cert_id: '%s', 'protocol': '%s'}" % (visible_user_id, proto)), 'class': "%s iconspace" % link, 'title': 'Send %s message to %s' % (proto, visible_user_id), 'text': '' } logger.debug("append user %s" % user_obj) users.append(user_obj) if operation == "show": # insert dummy placeholder to build table user_obj = {'object_type': 'user', 'name': ''} users.append(user_obj) output_objects.append({ 'object_type': 'user_list', 'pending_updates': pending_updates, 'users': users }) logger.info("%s %s end for %s" % (op_name, operation, client_id)) 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] client_dir = client_id_dir(client_id) title_entry = find_entry(output_objects, 'title') title_entry['text'] = 'Peers' (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) logger.info("%s begin for %s" % (op_name, client_id)) # IMPORTANT: single line here to avoid breaking javascript inlining expire_help = "For security reasons peer accounts should be closed when no longer required. Expire is used to limit account access time for that purpose, and you can always extend it later if needed. For courses and workshops a few weeks or months should usually suffice, while projects and long-term collaboration often extend to months or years. Peer accounts still need to be renewed at least annually, but the peer users can do so themselves without your repeated explicit acceptance, as long as it does not exceed your provided expire date." # jquery support for tablesorter and confirmation on delete # table initially sorted by col. 5 (kind), then 0 (name) refresh_call = 'ajax_peers()' table_spec = {'table_id': 'peers', 'sort_order': '[[5,0],[0,0]]', 'refresh_call': refresh_call} (add_import, add_init, add_ready) = man_base_js(configuration, [table_spec]) add_init += ''' /* Helper to define countries for which State field makes sense */ var enable_state = ["US", "CA", "AU"]; function show_info(title, msg) { $("#info_dialog").dialog("option", "title", title); $("#info_dialog").html("<p>"+msg+"</p>"); $("#info_dialog").dialog("open"); } function toggle_state() { $("#fields-tab .save_peers .field_group").each(function() { var country = $(this).find(".entry-field.country").val(); if (country && enable_state.indexOf(country) > -1) { //console.debug("unlock state for "+country); $(this).find("input.entry-field.state").prop("readonly", false); } else { //console.debug("lock state for "+country); $(this).find("input.entry-field.state").prop("readonly", true); /* NOTE: reset state on change to other country */ $(this).find("input.entry-field.state").val(""); } } ); } function transfer_id_fields() { //console.log("in transfer_id_fields"); var peer_count = 0; var peer_id; $("#fields-tab .save_peers .field_group").each(function() { var group = $(this); peer_id = ''; var full_name = $(group).find("input.entry-field.full_name").val(); var organization = $(group).find("input.entry-field.organization").val(); var email = $(group).find("input.entry-field.email").val(); var country = $(group).find(".entry-field.country").val(); var state = $(group).find("input.entry-field.state").val(); if (!state) { state = "NA" } if (full_name && organization && email && country && state) { peer_id = "/C="+country+"/ST="+state+"/L=NA/O="+ \ organization+"/OU=NA/CN="+full_name+"/emailAddress="+email; //console.debug("built peer_id: "+peer_id); peer_count += 1; } /* Always set peer_id to reset empty rows */ $(group).find("input.id-collector").val(peer_id); console.log("set collected peer_id: "+$(group).find("input.id-collector").val()); }); if (peer_count > 0) { return true; } else { return false; } } ''' add_ready += ''' $(".peers-tabs").tabs(); $(".peers-tabs .accordion").accordion({ collapsible: true, active: false, icons: {"header": "ui-icon-plus", "activeHeader": "ui-icon-minus"}, heightStyle: "content" }); /* fix and reduce accordion spacing */ $(".peers-tabs .accordion .ui-accordion-header").css("padding-top", 0).css("padding-bottom", 0).css("margin", 0); $(".peers-tabs .init-expanded.accordion ").accordion("option", "active", 0); $("#fields-tab .save_peers").on("submit", transfer_id_fields); $("#info_dialog").dialog( { autoOpen: false, width: 500, modal: true, closeOnEscape: true, buttons: { "Ok": function() { $(this).dialog("close"); }} }); ''' 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': 'Peers'}) user_map = get_full_user_map(configuration) user_dict = user_map.get(client_id, None) # Optional site-wide limitation of peers permission peers_permit_class = 'hidden' if user_dict and peers_permit_allowed(configuration, user_dict): peers_permit_class = '' form_method = 'post' csrf_limit = get_csrf_limit(configuration) target_op = 'peersaction' csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) fill_helpers = {'peers_notice': configuration.site_peers_notice, 'site': configuration.short_title, 'peers_permit_class': peers_permit_class, 'form_method': form_method, 'csrf_field': csrf_field, 'csrf_limit': csrf_limit, 'target_op': target_op, 'csrf_token': csrf_token, 'expire_help': expire_help, 'csv_header': csv_sep.join([i for i in peers_fields])} form_prefix_html = ''' <form class="save_peers save_general" method="%(form_method)s" action="%(target_op)s.py"> ''' form_suffix_html = ''' <input type="submit" value="Save Peers" /><br/> </form> ''' form_accept_html = ''' <input type="submit" value="Apply" /><br/> </form> ''' shared_peer_html = ''' <input type="hidden" name="%(csrf_field)s" value="%(csrf_token)s" /> <div class="form-row three-col-grid"> <div class="col-md-4 mb-3 form-cell"> <label for="peers_label">Label</label> <input class="form-control fill-width" type="text" name="peers_label" value="" pattern="[^ ]*" title="Label for peers" placeholder="Peers name or label" /> </div> <div class="col-md-4 mb-3 form-cell"> <label for="peers_kind">Kind</label> <select class="form-control themed-select html-select" name="peers_kind"> ''' for name in peer_kinds: shared_peer_html += ''' <option value="%s">%s ''' % (name, name.capitalize()) shared_peer_html += ''' </select> </div> <div class="col-md-4 mb-3 form-cell"> <label for="peers_expire">Expire <span class="info leftpad iconspace" title="%(expire_help)s" onClick="show_info(\'Expire Help\', \'%(expire_help)s\');"/> </label> <input class="form-control themed-select html-select fill-width" type="date" name="peers_expire" required pattern="[0-9/-]+" title="Access expiry date" /> </div> </div> ''' fill_helpers['form_prefix_html'] = form_prefix_html % fill_helpers fill_helpers['form_suffix_html'] = form_suffix_html % fill_helpers fill_helpers['form_accept_html'] = form_accept_html % fill_helpers fill_helpers['shared_peer_html'] = shared_peer_html % fill_helpers peers_path = os.path.join(configuration.user_settings, client_dir, peers_filename) try: all_peers = load(peers_path) except Exception as exc: logger.warning("could not load peers from %s: %s" % (peers_path, exc)) all_peers = {} pending_peers_path = os.path.join(configuration.user_settings, client_dir, pending_peers_filename) try: pending_peers = load(pending_peers_path) except Exception as exc: logger.warning("could not load pending peers from %s: %s" % (pending_peers_path, exc)) pending_peers = [] tabs_html = ''' <div id="info_dialog" class="hidden"></div> <div id="wrap-tabs" class="peers-tabs"> <ul> <li><a href="#show-tab">Show Peers</a></li> <li class="%(peers_permit_class)s"><a href="#requests-tab">Requested Peers</a></li> <li class="%(peers_permit_class)s"><a href="#fields-tab">Enter Peers</a></li> <li class="%(peers_permit_class)s"><a href="#import-tab">Import Peers</a></li> <!-- TODO: enable additional methods when ready? --> <li class="%(peers_permit_class)s hidden"><a href="#urlfetch-tab">Fetch Peers From URL</a></li> </ul> <div id="show-tab"> <p> %(peers_notice)s This is an overview of your registered peers. That is, people that you have vouched for to get an account on %(site)s because they need it for a particular course/workshop, research project or for general long term collaboration with you. The site admins will use this information to accept account requests and extensions from your peers until the given time of expiry. </p> <div class="peer_entries"> ''' output_objects.append( {'object_type': 'html_form', 'text': tabs_html % fill_helpers}) output_objects.append({'object_type': 'table_pager', 'entry_name': 'peers', 'default_entries': default_pager_entries}) peers = [] for (peer_id, entry) in all_peers.items(): filled_entry = dict([(field, '') for field in ('label', 'kind', 'expire')]) fill_distinguished_name(entry) filled_entry.update(entry) filled_entry['anon_peer_id'] = anon_user_id(peer_id) filled_entry['object_type'] = 'peer' # NOTE: very simple edit dialog to change only expire through confirm. # We could add similar buttons for kind and label fields but they can # be edited with Update in Edit Peers until we make a dedicated dialog filled_entry['editpeerlink'] = { 'object_type': 'link', 'destination': "javascript: confirmDialog(%s, '%s', '%s', %s);" % ('peer_action', 'Update %(full_name)s (%(email)s) expire date (YYYY-MM-DD)?' % filled_entry, 'peers_expire', "{action: 'update', peers_label: '%(label)s', peers_kind: '%(kind)s', peers_content: '%(distinguished_name)s', peers_invite: false}" % filled_entry), 'class': 'editlink iconspace', 'title': 'Update %(distinguished_name)s Expire value in peers' % filled_entry, 'text': ''} filled_entry['invitepeerlink'] = { 'object_type': 'link', 'destination': "javascript: confirmDialog(%s, '%s', %s, %s);" % ('peer_action', 'Send invitation email to %(distinguished_name)s?' % filled_entry, 'undefined', "{action: 'update', peers_label: '%(label)s', peers_kind: '%(kind)s', peers_expire:'%(expire)s', peers_content: '%(distinguished_name)s', peers_invite: true}" % filled_entry), 'class': 'invitelink iconspace', 'title': 'Invite %(distinguished_name)s as peer' % filled_entry, 'text': ''} filled_entry['viewpeerlink'] = { 'object_type': 'link', 'destination': 'viewuser.py?cert_id=%(anon_peer_id)s' % filled_entry, 'class': 'userlink iconspace', 'title': 'Lookup %(distinguished_name)s user details' % filled_entry, 'text': ''} filled_entry['delpeerlink'] = { 'object_type': 'link', 'destination': "javascript: confirmDialog(%s, '%s', %s, %s);" % ('peer_action', 'Really remove %(distinguished_name)s?' % filled_entry, 'undefined', "{action: 'remove', peers_label: '%(label)s', peers_kind: '%(kind)s', peers_expire:'%(expire)s', peers_content: '%(distinguished_name)s', peers_invite: false}" % filled_entry), 'class': 'removelink iconspace', 'title': 'Remove %(distinguished_name)s from peers' % filled_entry, 'text': ''} peers.append(filled_entry) output_objects.append({'object_type': 'peers', 'peers': peers}) # NOTE: reset tabs_html here tabs_html = ''' </div> </div> <div id="fields-tab"> <p> You may enter your individual peers in the form fields below and assign a shared kind and account expiry time for all entries. Just leave the Action field to <em>Add</em> unless you want to <em>Update</em> or <em>Remove</em> existing peers. You are free to leave rows empty, but each field in a peer row MUST be filled for the row to be treated. </p> <div class="enter-form form_container"> %(form_prefix_html)s %(shared_peer_html)s <input type="hidden" name="peers_format" value="userid" /> <div class="form-row one-col-grid"> <div class="col-md-12 mb-1 form-cell"> <label for="action">Action</label> <select class="form-control themed-select html-select fill-width" name="action"> <option value="add">Add</option> <option value="update">Update</option> <option value="remove">Remove</option> </select> </div> </div> ''' sorted_countries = list_country_codes(configuration) # TODO: switch to JS rows with automagic addition to always keep spare row? for index in range(edit_entries): # NOTE: we arrange each entry into a field_group_N div with a hidden # user ID collector where the field values are merged on submit # and the actual fields are not passed to the backend. tabs_html += ''' <div id="field_group_%s" class="field_group"> <input class="id-collector" type="hidden" name="peers_content" value="" /> <div class="form-row five-col-grid"> ''' % index for field in peers_fields: title = ' '.join([i.capitalize() for i in field.split('_')]) placeholder = title field_extras = 'type="text"' # Lock state field until applicable (JS) locked = "" cols = "col-md-3 mb-3" if field.lower() == 'full_name': field_extras = 'minlength=3' elif field.lower() == 'organization': field_extras = 'minlength=2' elif field.lower() == 'email': placeholder = "Email at organization" field_extras = 'type="email" minlength=5' cols = "col-md-2 mb-2" elif field.lower() == 'country': # NOTE: use country drop-down if available title = "Country (ISO 3166)" placeholder = "2-Letter country code" field_extras = 'minlength=2 maxlength=2' cols = "col-md-2 mb-2" elif field.lower() == 'state': title = "State (if applicable)" placeholder = "2-Letter state code" field_extras += ' minlength=0 maxlength=2' locked = "readonly" cols = "col-md-2 mb-2" entry_fill = {'field': field, 'title': title, 'placeholder': placeholder, 'extras': field_extras, 'locked': locked, 'cols': cols} tabs_html += ''' <div class="%(cols)s form-cell %(field)s-cell"> <label for="%(field)s">%(title)s</label><br/> ''' % entry_fill if field == 'country' and sorted_countries: # Generate drop-down of countries and codes if available, else # simple input tabs_html += ''' <select class="form-control %(field)s themed-select html-select entry-field fill-with" %(extras)s placeholder="%(placeholder)s" %(locked)s onChange="toggle_state();"> ''' % entry_fill for (name, code) in [('', '')] + sorted_countries: tabs_html += " <option value='%s'>%s</option>\n" % \ (code, name) tabs_html += """ </select> """ else: tabs_html += ''' <input class="form-control %(field)s entry-field fill-width" %(extras)s placeholder="%(placeholder)s" %(locked)s onBlur="toggle_state();" /> ''' % entry_fill tabs_html += ''' </div> ''' % entry_fill tabs_html += ''' </div> </div> ''' tabs_html += ''' <p> <span class="switch-label">Invite on email</span> <label class="switch" for="fields_invite"> <input id="fields_invite" type="checkbox" name="peers_invite"> <span class="slider round small" title="Optional email invitation"></span> </label> </p> %(form_suffix_html)s </div> </div> ''' tabs_html += ''' <div id="import-tab" > <p> You can paste or enter a CSV-formatted list below to create or update your existing peers. The contents must start with a single header line to define the field order in the following individual user lines as shown in the example at the bottom. </p> <div class="import-form form_container"> <h2>Import/Update Peers</h2> %(form_prefix_html)s %(shared_peer_html)s <input type="hidden" name="peers_format" value="csvform" /> <input type="hidden" name="action" value="import" /> <textarea class="fillwidth" name="peers_content" rows=10 title="CSV list of peers" placeholder="Paste or enter CSV-formatted list of peers ..."></textarea> <p> <span class="switch-label">Invite on email</span> <label class="switch" for="import_invite"> <input id="import_invite" type="checkbox" name="peers_invite"> <span class="slider round small" title="Optional email invitation"></span> </label> </p> %(form_suffix_html)s </div> ''' tabs_html += ''' <br/> <div class="peers_export init-collapsed accordion invert-theme"> <h4>Example Peers</h4> <p class="verbatim">%s %s ... %s </p> </div> </div> ''' % (fill_helpers['csv_header'], csv_sep.join([sample_users[0].get(i, '') for i in peers_fields]), csv_sep.join([sample_users[-1].get(i, '') for i in peers_fields])) tabs_html += ''' <div id="requests-tab" > <p> If someone requests an external user account on %(site)s and explicitly references you as sponsor or contact person the site admins will generally forward the request, so that it shows up here for you to confirm. You can then accept or reject the individual requests below to let the site admins proceed with account creation or rejection. Please select an expire date to provide limited but sufficiently long account access - it can always be extended later. </p> ''' pending_count = 0 for (peer_id, user) in pending_peers: # TODO: consider still showing if expired? # Skip already accepted request if peer_id in all_peers: continue pending_count += 1 tabs_html += ''' <div class="requests-form form_container"> %(form_prefix_html)s %(shared_peer_html)s <br/> <input type="hidden" name="peers_format" value="userid" /> <div class="form-row six-col-grid"> <div class="col-md-2 mb-6 form-cell"> <label for="action">Action</label> <select class="form-control themed-select html-select fill-width" name="action"> <option value="accept">Accept</option> <option value="reject">Reject</option> </select> </div> ''' tabs_html += ''' <input type="hidden" name="peers_content" value="%(distinguished_name)s" /> ''' % user for field in peers_fields: title = ' '.join([i.capitalize() for i in field.split('_')]) tabs_html += ''' <div class="col-md-2 mb-6 form-cell"> <label for="%(field)s">%(title)s</label> <input class="form-control fill-width" type="text" value="%(value)s" readonly /> </div>''' % {'field': field, 'title': title, 'value': user.get(field, '')} tabs_html += ''' </div> %(form_accept_html)s </div> ''' if pending_count < 1: tabs_html += ''' <p class="info icon iconpadding"> No pending requests ... </p> ''' tabs_html += ''' </div> ''' tabs_html += ''' <div id="urlfetch-tab" > <p> In case you have a general project participation list online you can specify the URL here to fetch and parse the list into a peers list. Please note that this memberlist should still be on the machine readbale format described on the upload tab. </p> <div class="urlfetch-form form_container"> %(form_prefix_html)s %(shared_peer_html)s <br/> <input type="hidden" name="action" value="import" /> <input type="hidden" name="peers_format" value="csvurl" /> <input class="fillwidth" type="text" name="peers_content" value="" placeholder="URL to fetch CSV-formatted list of peers from ..." /><br/> %(form_suffix_html)s </div> </div> ''' # End wrap-tabs div tabs_html += ''' </div > ''' output_objects.append( {'object_type': 'html_form', 'text': tabs_html % fill_helpers}) # Helper form for post helper = html_post_helper('peer_action', '%s.py' % target_op, {'action': '__DYNAMIC__', 'peers_label': '__DYNAMIC__', 'peers_kind': '__DYNAMIC__', 'peers_expire': '__DYNAMIC__', 'peers_content': '__DYNAMIC__', 'peers_invite': '__DYNAMIC__', 'peers_format': 'userid', csrf_field: csrf_token}) output_objects.append({'object_type': 'html_form', 'text': helper}) logger.info("%s end for %s" % (op_name, client_id)) 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) client_dir = client_id_dir(client_id) status = returnvalues.OK defaults = signature()[1] (validate_status, accepted) = validate_input_and_cert( user_arguments_dict, defaults, output_objects, client_id, configuration, allow_rejects=False, ) if not validate_status: logger.error("jobstatus input validation failed: %s" % accepted) return (accepted, returnvalues.CLIENT_ERROR) flags = ''.join(accepted['flags']) max_jobs = int(accepted['max_jobs'][-1]) order = 'unsorted ' if sorted(flags): order = 'sorted ' patterns = accepted['job_id'] project_names = accepted['project_name'] if len(project_names) > 0: for project_name in project_names: project_name_job_ids = \ get_job_ids_with_specified_project_name(client_id, project_name, configuration.mrsl_files_dir, logger) patterns.extend(project_name_job_ids) 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 output_objects.append({'object_type': 'header', 'text': '%s %s job status' % (configuration.short_title, order)}) if not patterns: output_objects.append( {'object_type': 'error_text', 'text': 'No job_id specified!'}) return (output_objects, returnvalues.NO_SUCH_JOB_ID) if verbose(flags): for flag in flags: output_objects.append({'object_type': 'text', 'text': '%s using flag: %s' % (op_name, flag)}) if not os.path.isdir(base_dir): output_objects.append( {'object_type': 'error_text', 'text': ('You have not been created as a user on the %s server! ' 'Please contact the %s team.') % (configuration.short_title, configuration.short_title)}) return (output_objects, returnvalues.CLIENT_ERROR) 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.... 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 if sorted(flags): sort(filelist) if max_jobs > 0 and max_jobs < len(filelist): output_objects.append( {'object_type': 'text', 'text': 'Only showing first %d of the %d matching jobs as requested' % (max_jobs, len(filelist))}) filelist = filelist[:max_jobs] # Iterate through jobs and list details for each job_list = {'object_type': 'job_list', 'jobs': []} 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', '') job_dict = unpickle(filepath, logger) if not job_dict: status = returnvalues.CLIENT_ERROR output_objects.append( {'object_type': 'error_text', 'text': 'No such job: %s (could not load mRSL file %s)' % (job_id, filepath)}) continue # Expand any job variables before use job_dict = expand_variables(job_dict) job_obj = {'object_type': 'job', 'job_id': job_id} job_obj['status'] = job_dict['STATUS'] time_fields = [ 'VERIFIED', 'VERIFIED_TIMESTAMP', 'RECEIVED_TIMESTAMP', 'QUEUED_TIMESTAMP', 'SCHEDULE_TIMESTAMP', 'EXECUTING_TIMESTAMP', 'FINISHED_TIMESTAMP', 'FAILED_TIMESTAMP', 'CANCELED_TIMESTAMP', ] for name in time_fields: if name in job_dict: # time objects cannot be marshalled, asctime if timestamp try: job_obj[name.lower()] = time.asctime(job_dict[name]) except Exception as exc: # not a time object, just add job_obj[name.lower()] = job_dict[name] ########################################### # ARC job status retrieval on demand: # But we should _not_ update the status in the mRSL files, since # other MiG code might rely on finding only valid "MiG" states. if configuration.arc_clusters and \ job_dict.get('UNIQUE_RESOURCE_NAME', 'unset') == 'ARC' \ and job_dict['STATUS'] == 'EXECUTING': try: home = os.path.join(configuration.user_home, client_dir) arcsession = arcwrapper.Ui(home) arcstatus = arcsession.jobStatus(job_dict['EXE']) job_obj['status'] = arcstatus['status'] except arcwrapper.ARCWrapperError as err: logger.error('Error retrieving ARC job status: %s' % err.what()) job_obj['status'] += '(Error: ' + err.what() + ')' except arcwrapper.NoProxyError as err: logger.error('While retrieving ARC job status: %s' % err.what()) job_obj['status'] += '(Error: ' + err.what() + ')' except Exception as err: logger.error('Error retrieving ARC job status: %s' % err) job_obj['status'] += '(Error during retrieval)' exec_histories = [] if verbose(flags): if 'EXECUTE' in job_dict: command_line = '; '.join(job_dict['EXECUTE']) if len(command_line) > 256: job_obj['execute'] = '%s ...' % command_line[:252] else: job_obj['execute'] = command_line res_conf = job_dict.get('RESOURCE_CONFIG', {}) if 'RESOURCE_ID' in res_conf: public_id = res_conf['RESOURCE_ID'] if res_conf.get('ANONYMOUS', True): public_id = anon_resource_id(public_id) job_obj['resource'] = public_id if job_dict.get('PUBLICNAME', False): job_obj['resource'] += ' (alias %(PUBLICNAME)s)' % job_dict if 'RESOURCE_VGRID' in job_dict: job_obj['vgrid'] = job_dict['RESOURCE_VGRID'] if 'EXECUTION_HISTORY' in job_dict: counter = 0 for history_dict in job_dict['EXECUTION_HISTORY']: exec_history = \ {'object_type': 'execution_history'} if 'QUEUED_TIMESTAMP' in history_dict: exec_history['queued'] = \ time.asctime(history_dict['QUEUED_TIMESTAMP' ]) if 'EXECUTING_TIMESTAMP' in history_dict: exec_history['executing'] = \ time.asctime(history_dict['EXECUTING_TIMESTAMP' ]) if 'PUBLICNAME' in history_dict: if history_dict['PUBLICNAME']: exec_history['resource'] = \ history_dict['PUBLICNAME'] else: exec_history['resource'] = 'HIDDEN' if 'RESOURCE_VGRID' in history_dict: exec_history['vgrid'] = \ history_dict['RESOURCE_VGRID'] if 'FAILED_TIMESTAMP' in history_dict: exec_history['failed'] = \ time.asctime(history_dict['FAILED_TIMESTAMP' ]) if 'FAILED_MESSAGE' in history_dict: exec_history['failed_message'] = \ history_dict['FAILED_MESSAGE'] exec_histories.append( {'execution_history': exec_history, 'count': counter}) counter += 1 if 'SCHEDULE_HINT' in job_dict: job_obj['schedule_hint'] = job_dict['SCHEDULE_HINT'] # We should not show raw schedule_targets due to lack of anonymization if 'SCHEDULE_TARGETS' in job_dict: job_obj['schedule_hits'] = len(job_dict['SCHEDULE_TARGETS']) if 'EXPECTED_DELAY' in job_dict: # Catch None value if not job_dict['EXPECTED_DELAY']: job_obj['expected_delay'] = 0 else: job_obj['expected_delay'] = int(job_dict['EXPECTED_DELAY']) job_obj['execution_histories'] = exec_histories if interactive(flags): job_obj['statuslink'] = {'object_type': 'link', 'destination': 'fileman.py?path=%s/%s/' % (job_output_dir, job_id), 'text': 'View status files'} job_obj['mrsllink'] = {'object_type': 'link', 'destination': 'mrslview.py?job_id=%s' % job_id, 'text': 'View parsed mRSL contents'} if 'OUTPUTFILES' in job_dict and job_dict['OUTPUTFILES']: # Create a single ls link with all supplied outputfiles path_string = '' for path in job_dict['OUTPUTFILES']: # OUTPUTFILES is either just combo path or src dst paths parts = path.split() # Always take last part as destination path_string += 'path=%s;' % parts[-1] job_obj['outputfileslink'] = {'object_type': 'link', 'destination': 'ls.py?%s' % path_string, 'text': 'View output files'} form_method = 'post' csrf_limit = get_csrf_limit(configuration) target_op = 'resubmit' csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) js_name = 'resubmit%s' % hexlify(job_id) helper = html_post_helper(js_name, '%s.py' % target_op, {'job_id': job_id, csrf_field: csrf_token}) output_objects.append({'object_type': 'html_form', 'text': helper}) job_obj['resubmitlink'] = {'object_type': 'link', 'destination': "javascript: %s();" % js_name, 'text': 'Resubmit job'} target_op = 'jobaction' csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) js_name = 'freeze%s' % hexlify(job_id) helper = html_post_helper(js_name, '%s.py' % target_op, {'action': 'freeze', 'job_id': job_id, csrf_field: csrf_token}) output_objects.append({'object_type': 'html_form', 'text': helper}) job_obj['freezelink'] = {'object_type': 'link', 'destination': "javascript: %s();" % js_name, 'text': 'Freeze job in queue'} js_name = 'thaw%s' % hexlify(job_id) helper = html_post_helper(js_name, '%s.py' % target_op, {'action': 'thaw', 'job_id': job_id, csrf_field: csrf_token}) output_objects.append({'object_type': 'html_form', 'text': helper}) job_obj['thawlink'] = {'object_type': 'link', 'destination': "javascript: %s();" % js_name, 'text': 'Thaw job in queue'} js_name = 'cancel%s' % hexlify(job_id) helper = html_post_helper(js_name, '%s.py' % target_op, {'action': 'cancel', 'job_id': job_id, csrf_field: csrf_token}) output_objects.append({'object_type': 'html_form', 'text': helper}) job_obj['cancellink'] = {'object_type': 'link', 'destination': "javascript: %s();" % js_name, 'text': 'Cancel job'} target_op = 'jobschedule' csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) js_name = 'jobschedule%s' % hexlify(job_id) helper = html_post_helper(js_name, '%s.py' % target_op, {'job_id': job_id, csrf_field: csrf_token}) output_objects.append({'object_type': 'html_form', 'text': helper}) job_obj['jobschedulelink'] = {'object_type': 'link', 'destination': "javascript: %s();" % js_name, 'text': 'Request schedule information'} target_op = 'jobfeasible' csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) js_name = 'jobfeasible%s' % hexlify(job_id) helper = html_post_helper(js_name, '%s.py' % target_op, {'job_id': job_id, csrf_field: csrf_token}) output_objects.append({'object_type': 'html_form', 'text': helper}) job_obj['jobfeasiblelink'] = {'object_type': 'link', 'destination': "javascript: %s();" % js_name, 'text': 'Check job feasibility'} job_obj['liveiolink'] = {'object_type': 'link', 'destination': 'liveio.py?job_id=%s' % job_id, 'text': 'Request live I/O'} job_list['jobs'].append(job_obj) output_objects.append(job_list) 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_menu=client_id) defaults = signature()[1] (validate_status, accepted) = validate_input(user_arguments_dict, defaults, output_objects, allow_rejects=False) if not validate_status: return (accepted, returnvalues.CLIENT_ERROR) queue = accepted['queue'][-1] action = accepted['action'][-1] iosessionid = accepted['iosessionid'][-1] msg = accepted['msg'][-1] msg_id = accepted['msg_id'][-1] # Web format for cert access and no header for SID access if client_id: output_objects.append({ 'object_type': 'header', 'text': 'Message queue %s' % action }) else: output_objects.append({'object_type': 'start'}) # Always return at least a basic file_output entry file_entry = { 'object_type': 'file_output', 'lines': [], 'wrap_binary': True, 'wrap_targets': ['lines'] } if not action in valid_actions: output_objects.append({'object_type': 'error_text', 'text' : 'Invalid action "%s" (supported: %s)' % \ (action, ', '.join(valid_actions))}) output_objects.append(file_entry) return (output_objects, returnvalues.CLIENT_ERROR) if action in post_actions: if not safe_handler(configuration, 'post', op_name, client_id, get_csrf_limit(configuration), accepted): output_objects.append({ 'object_type': 'error_text', 'text': '''Only accepting CSRF-filtered POST requests to prevent unintended updates''' }) return (output_objects, returnvalues.CLIENT_ERROR) # Find user home from session or certificate if iosessionid: client_home = os.path.realpath( os.path.join(configuration.webserver_home, iosessionid)) client_dir = os.path.basename(client_home) elif client_id: client_dir = client_id_dir(client_id) else: output_objects.append({ 'object_type': 'error_text', 'text': 'Either certificate or session ID is required' }) output_objects.append(file_entry) return (output_objects, returnvalues.CLIENT_ERROR) # Please note that base_dir must end in slash to avoid access to other # user dirs when own name is a prefix of another user name base_dir = os.path.abspath( os.path.join(configuration.user_home, client_dir)) + os.sep if not os.path.isdir(base_dir): output_objects.append({ 'object_type': 'error_text', 'text': 'No matching session or user home!' }) output_objects.append(file_entry) return (output_objects, returnvalues.CLIENT_ERROR) mqueue_base = os.path.join(base_dir, mqueue_prefix) + os.sep default_queue_dir = os.path.join(mqueue_base, default_mqueue) # Create mqueue base and default queue dir if missing if not os.path.exists(default_queue_dir): try: os.makedirs(default_queue_dir) except: pass # IMPORTANT: path must be expanded to abs for proper chrooting queue_path = os.path.abspath(os.path.join(mqueue_base, queue)) if not valid_user_path(configuration, queue_path, mqueue_base): output_objects.append({ 'object_type': 'error_text', 'text': 'Invalid queue name: "%s"' % queue }) output_objects.append(file_entry) return (output_objects, returnvalues.CLIENT_ERROR) lock_path = os.path.join(mqueue_base, lock_name) lock_handle = open(lock_path, 'a') fcntl.flock(lock_handle.fileno(), fcntl.LOCK_EX) status = returnvalues.OK if action == "interactive": form_method = 'post' csrf_limit = get_csrf_limit(configuration) fill_helpers = { 'queue': queue, 'msg': msg, 'form_method': form_method, 'csrf_field': csrf_field, 'csrf_limit': csrf_limit, } target_op = 'mqueue' csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) fill_helpers.update({'target_op': target_op, 'csrf_token': csrf_token}) output_objects.append({ 'object_type': 'text', 'text': ''' Fill in the fields below to control and access your personal message queues. Jobs can receive from and send to the message queues during execution, and use them as a means of job inter-communication. Expect message queue operations to take several seconds on the resources, however. That is, use it for tasks like orchestrating long running jobs, and not for low latency communication. ''' }) html = ''' <form name="mqueueform" method="%(form_method)s" action="%(target_op)s.py"> <table class="mqueue"> <tr><td class=centertext> </td></tr> <tr><td> Action:<br /> <input type="hidden" name="%(csrf_field)s" value="%(csrf_token)s" /> <input type=radio name=action value="create" onclick="javascript: document.mqueueform.queue.disabled=false; document.mqueueform.msg.disabled=true;" />create queue <input type=radio name=action checked value="send" onclick="javascript: document.mqueueform.queue.disabled=false; document.mqueueform.msg.disabled=false;" />send message to queue <input type=radio name=action value="receive" onclick="javascript: document.mqueueform.queue.disabled=false; document.mqueueform.msg.disabled=true;" />receive message from queue <input type=radio name=action value="remove" onclick="javascript: document.mqueueform.queue.disabled=false; document.mqueueform.msg.disabled=true;" />remove queue <input type=radio name=action value="listqueues" onclick="javascript: document.mqueueform.queue.disabled=true; document.mqueueform.msg.disabled=true;" />list queues <input type=radio name=action value="listmessages" onclick="javascript: document.mqueueform.queue.disabled=false; document.mqueueform.msg.disabled=true;" />list messages <input type=radio name=action value="show" onclick="javascript: document.mqueueform.queue.disabled=false; document.mqueueform.msg.disabled=true;" />show message </td></tr> <tr><td> Queue:<br /> <input class="fillwidth" type=text name=queue value="%(queue)s" /> </td></tr> <tr><td> <div id="msgfieldf"> <input class="fillwidth" type=text name=msg value="%(msg)s" /><br /> </div> </td></tr> <tr><td> <input type="submit" value="Apply" /> </td></tr> </table> </form> ''' % fill_helpers output_objects.append({'object_type': 'html_form', 'text': html}) output_objects.append({ 'object_type': 'text', 'text': ''' Further live job control is avalable through the live I/O interface. They provide a basic interface for centrally managing input and output files for active jobs. ''' }) output_objects.append({ 'object_type': 'link', 'destination': 'liveio.py', 'text': 'Live I/O interface' }) return (output_objects, returnvalues.OK) elif action == 'create': try: os.mkdir(queue_path) output_objects.append({ 'object_type': 'text', 'text': 'New "%s" queue created' % queue }) except Exception as err: output_objects.append({'object_type': 'error_text', 'text' : 'Could not create "%s" queue: "%s"' % \ (queue, err)}) status = returnvalues.CLIENT_ERROR elif action == 'remove': try: for entry in os.listdir(queue_path): os.remove(os.path.join(queue_path, entry)) os.rmdir(queue_path) output_objects.append({ 'object_type': 'text', 'text': 'Existing "%s" queue removed' % queue }) except Exception as err: output_objects.append({'object_type': 'error_text', 'text' : 'Could not remove "%s" queue: "%s"' % \ (queue, err)}) status = returnvalues.CLIENT_ERROR elif action == 'send': try: if not msg_id: msg_id = "%.0f" % time.time() msg_path = os.path.join(queue_path, msg_id) msg_fd = open(msg_path, 'w') msg_fd.write(msg) msg_fd.close() output_objects.append({ 'object_type': 'text', 'text': 'Message sent to "%s" queue' % queue }) except Exception as err: output_objects.append({'object_type': 'error_text', 'text' : 'Could not send to "%s" queue: "%s"' % \ (queue, err)}) status = returnvalues.CLIENT_ERROR elif action == 'receive': try: if not msg_id: messages = os.listdir(queue_path) messages.sort() if messages: msg_id = messages[0] if msg_id: message_path = os.path.join(queue_path, msg_id) message_fd = open(message_path, 'r') message = message_fd.readlines() message_fd.close() os.remove(message_path) file_entry['path'] = os.path.basename(message_path) else: message = [mqueue_empty] # Update file_output entry for raw data with output_format=file file_entry['lines'] = message except Exception as err: output_objects.append({ 'object_type': 'error_text', 'text': 'Could not receive from "%s" queue: "%s"' % (queue, err) }) status = returnvalues.CLIENT_ERROR elif action == 'show': try: if not msg_id: messages = os.listdir(queue_path) messages.sort() if messages: msg_id = messages[0] if msg_id: message_path = os.path.join(queue_path, msg_id) message_fd = open(message_path, 'r') message = message_fd.readlines() message_fd.close() file_entry['path'] = os.path.basename(message_path) else: message = [mqueue_empty] # Update file_output entry for raw data with output_format=file file_entry['lines'] = message except Exception as err: output_objects.append({ 'object_type': 'error_text', 'text': 'Could not show %s from "%s" queue: "%s"' % (msg_id, queue, err) }) status = returnvalues.CLIENT_ERROR elif action == 'listmessages': try: messages = os.listdir(queue_path) messages.sort() output_objects.append({'object_type': 'list', 'list': messages}) except Exception as err: output_objects.append({'object_type': 'error_text', 'text' : 'Could not list "%s" queue: "%s"' % \ (queue, err)}) status = returnvalues.CLIENT_ERROR elif action == 'listqueues': try: queues = [i for i in os.listdir(mqueue_base) if \ os.path.isdir(os.path.join(mqueue_base, i))] queues.sort() output_objects.append({'object_type': 'list', 'list': queues}) except Exception as err: output_objects.append({ 'object_type': 'error_text', 'text': 'Could not list queues: "%s"' % err }) status = returnvalues.CLIENT_ERROR else: output_objects.append({ 'object_type': 'error_text', 'text': 'Unexpected mqueue action: "%s"' % action }) status = returnvalues.SYSTEM_ERROR lock_handle.close() output_objects.append(file_entry) output_objects.append({ 'object_type': 'link', 'destination': 'mqueue.py?queue=%s;msg=%s' % (queue, msg), 'text': 'Back to message queue interaction' }) return (output_objects, returnvalues.OK)
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) logger.debug("User: %s executing %s", client_id, op_name) if not configuration.site_enable_cloud: output_objects.append( {'object_type': 'error_text', 'text': 'The cloud service is not enabled on the system'}) return (output_objects, returnvalues.SYSTEM_ERROR) status = returnvalues.OK user_map = get_full_user_map(configuration) user_dict = user_map.get(client_id, None) # Optional limitation of cloud access permission if not user_dict or not cloud_access_allowed(configuration, user_dict): output_objects.append( {'object_type': 'error_text', 'text': "You don't have permission to access the cloud facilities on " "this site"}) return (output_objects, returnvalues.CLIENT_ERROR) services = configuration.cloud_services # Show cloud services menu (add_import, add_init, add_ready) = man_base_js(configuration, []) add_init += ''' function get_instance_id() { console.log("in get_instance_id"); console.log("found val: "+$("#select-instance-id").val()); return $("#select-instance-id").val(); } function get_instance_label() { console.log("in get_instance_label"); console.log("found val: "+$("#select-instance-id > option:selected").text()); return $("#select-instance-id > option:selected").text(); } ''' add_ready += ''' /* NOTE: requires managers CSS fix for proper tab bar height */ $(".cloud-tabs").tabs(); ''' title_entry = find_entry(output_objects, 'title') 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': 'Select a Cloud Service'}) fill_helpers = { 'cloud_tabs': ''.join(['<li><a href="#%s-tab">%s</a></li>' % (service['service_name'], service['service_title']) for service in services]) } output_objects.append({'object_type': 'html_form', 'text': ''' <div id="wrap-tabs" class="cloud-tabs"> <ul> %(cloud_tabs)s </ul> ''' % fill_helpers}) form_method = 'post' csrf_limit = get_csrf_limit(configuration) fill_helpers = {'site': configuration.short_title, 'form_method': form_method, 'csrf_field': csrf_field, 'csrf_limit': csrf_limit} target_op = 'reqcloudservice' 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}) action_list = [('start', 'Start'), ('stop', 'Stop'), ('softrestart', 'Soft boot'), ('hardrestart', 'Hard boot'), ('status', 'Status'), # NOTE: expose console on status page #('webaccess', 'Console'), ('updatekeys', 'Set keys on'), ('create', 'Create'), ('delete', 'Delete')] # Delete instance form helper shared for all cloud services helper = html_post_helper("%s" % target_op, '%s.py' % target_op, {'instance_id': '__DYNAMIC__', 'service': '__DYNAMIC__', 'action': 'delete', csrf_field: csrf_token}) output_objects.append({'object_type': 'html_form', 'text': helper}) for service in services: logger.debug("service: %s" % service) cloud_id = service['service_name'] cloud_title = service['service_title'] rules_of_conduct = service['service_rules_of_conduct'] cloud_flavor = service.get("service_provider_flavor", "openstack") output_objects.append({'object_type': 'html_form', 'text': ''' <div id="%s-tab"> ''' % cloud_id}) if service['service_desc']: output_objects.append({'object_type': 'sectionheader', 'text': 'Service Description'}) output_objects.append({'object_type': 'html_form', 'text': ''' <div class="cloud-description"> <span>%s</span> </div> ''' % service['service_desc']}) output_objects.append({'object_type': 'html_form', 'text': ''' <br/> '''}) if not check_cloud_available(configuration, client_id, cloud_id, cloud_flavor): logger.error("Failed to connect to cloud: %s" % cloud_id) output_objects.append( {'object_type': 'error_text', 'text': 'The %s cloud service is currently unavailable' % \ cloud_title}) output_objects.append({'object_type': 'html_form', 'text': ''' </div> '''}) status = returnvalues.SYSTEM_ERROR continue # Lookup user-specific allowed images (colon-separated image names) allowed_images = allowed_cloud_images(configuration, client_id, cloud_id, cloud_flavor) if not allowed_images: output_objects.append({ 'object_type': 'error_text', 'text': "No valid instance images for %s" % cloud_title}) output_objects.append({'object_type': 'html_form', 'text': ''' </div> '''}) continue fill_helpers.update({'cloud_id': cloud_id, 'cloud_title': cloud_title, 'target_op': target_op, 'rules_of_conduct': rules_of_conduct}) delete_html = "" # Manage existing instances saved_instances = cloud_load_instance(configuration, client_id, cloud_id, keyword_all) saved_fields = ['INSTANCE_IMAGE'] instance_fields = ['public_fqdn', 'status'] status_map = status_all_cloud_instances( configuration, client_id, cloud_id, cloud_flavor, saved_instances.keys(), instance_fields) # TODO: halfwidth styling does not really work on select elements delete_html += """ <div class='cloud-instance-delete fillwidth'> <h3>Permanently delete a %(cloud_title)s cloud instance</h3> <form class='delete-cloud-instance' target='#'> <p class='cloud-instance-input fillwidth'> <label class='fieldlabel halfwidth'>Instance</label> <span class='halfwidth'> <select id='select-instance-id' class='styled-select html-select halfwidth padspace' name='instance_id'> """ % fill_helpers output_objects.append({'object_type': 'html_form', 'text': """ <div class='cloud-management fillwidth'> <h3>Manage %(cloud_title)s instances</h3> <br/> <div class='cloud-instance-grid'> <div class='cloud-instance-grid-left'> <label class='fieldlabel fieldheader'>Name</label> </div> <div class='cloud-instance-grid-middle'> <label class='fieldlabel fieldheader'>Instance Details</label> </div> <div class='cloud-instance-grid-right'> <label class='fieldlabel fieldheader'>Actions</label> </div> """ % fill_helpers}) for (instance_id, instance_dict) in saved_instances.items(): instance_label = instance_dict.get('INSTANCE_LABEL', instance_id) logger.debug("Management entries for %s %s cloud instance %s" % (client_id, cloud_id, instance_id)) instance_html = """ <div class='cloud-instance-grid-left'> <label class='fieldlabel'>%s</label> </div> <div class='cloud-instance-grid-middle'> """ % instance_label for field in saved_fields: field_val = saved_instances[instance_id].get(field, "-") if field == 'INSTANCE_IMAGE': for (img_name, _, img_alias) in allowed_images: if img_name == field_val: field_val = img_alias instance_html += """ <span class='fieldstatus entry leftpad'>%s</span> """ % field_val for field in instance_fields: field_val = status_map[instance_id].get(field, "-") instance_html += """ <span class='fieldstatus entry leftpad'>%s</span> """ % field_val instance_html += """ </div> <div class='cloud-instance-grid-right'> """ output_objects.append({'object_type': 'html_form', 'text': instance_html}) for (action, title) in action_list: if action in cloud_edit_actions: continue query = 'action=%s;service=%s;instance_id=%s' % \ (action, cloud_id, instance_id) url = 'reqcloudservice.py?%s' % query #output_service = { # 'object_type': 'service', # 'name': "%s" % title, # 'targetlink': url #} #output_objects.append(output_service) output_objects.append({ 'object_type': 'link', 'destination': url, 'text': title, 'class': 'ui-button', 'title': '%s %s' % (title, instance_label)}) output_objects.append({'object_type': 'html_form', 'text': """ </div> """}) delete_html += """<option value='%s'>%s</option> """ % (instance_id, instance_label) output_objects.append({'object_type': 'html_form', 'text': """ </div> </div> """}) delete_html += """ </select> </span> </p> <p class='fillwidth'> <input type='submit' value='Delete Instance' onClick='javascript:confirmDialog(%(target_op)s, \"Really permanently delete your %(cloud_title)s \"+get_instance_label()+\" instance including all local data?\", undefined, {instance_id: get_instance_id(), service: \"%(cloud_id)s\"}); return false;' /> </p> </form> </div> """ % fill_helpers # Create new instance create_html = """ <div class='cloud-instance-create fillwidth'> <h3>Create a new %(cloud_title)s cloud instance</h3> <form class='create_cloud_instance' method='%(form_method)s' action='%(target_op)s.py'> <input type='hidden' name='%(csrf_field)s' value='%(csrf_token)s' /> <input type='hidden' name='service' value='%(cloud_id)s' /> <input type='hidden' name='action' value='create' /> <p class='cloud-instance-input fillwidth'> <label class='fieldlabel halfwidth'>Label</label> <span class='halfwidth'> <input class='halfwidth padspace' type='text' name='instance_label' value='' /> </span> </p> <p class='cloud-instance-input fillwidth'> <label class='fieldlabel halfwidth'>Image</label> <span class='halfwidth'> <select class='styled-select html-select halfwidth padspace' name='instance_image'> """ for (image_name, _, image_alias) in allowed_images: create_html += """<option value='%s'>%s</option> """ % (image_name, image_alias) create_html += """ </select> </span> </p> <p class='cloud-instance-input fillwidth'> <label class='fieldlabel halfwidth'> Accept <a href='%(rules_of_conduct)s'>Cloud Rules of Conduct</a> </label> <span class='halfwidth'> <label class='switch'> <input type='checkbox' mandatory name='accept_terms'> <span class='slider round'></span></label> </span> </p> <p class='fillwidth'> <input type='submit' value='Create Instance' /> </p> </form> </div> """ output_objects.append({'object_type': 'html_form', 'text': create_html % fill_helpers}) if saved_instances: output_objects.append({'object_type': 'html_form', 'text': delete_html % fill_helpers}) output_objects.append({'object_type': 'html_form', 'text': ''' </div> '''}) output_objects.append({'object_type': 'html_form', 'text': ''' </div> '''}) 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] status = returnvalues.OK output_objects.append({ 'object_type': 'header', 'text': 'Add Resource Owner(s)' }) (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].strip() cert_id_list = accepted['cert_id'] 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) 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 add a new 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 cert_id_added = [] for cert_id in cert_id_list: cert_id = cert_id.strip() if not cert_id: continue 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) }) status = returnvalues.CLIENT_ERROR continue # don't add if already an owner if resource_is_owner(unique_resource_name, cert_id, configuration): output_objects.append({ 'object_type': 'error_text', 'text': '%s is already an owner of %s.' % (cert_id, unique_resource_name) }) status = returnvalues.CLIENT_ERROR continue # Add owner (add_status, add_msg) = resource_add_owners(configuration, unique_resource_name, [cert_id]) if not add_status: output_objects.append({ 'object_type': 'error_text', 'text': 'Could not add new owner, reason: %s' % add_msg }) status = returnvalues.SYSTEM_ERROR continue cert_id_added.append(cert_id) if request_name: request_dir = os.path.join(configuration.resource_home, unique_resource_name) if not delete_access_request(configuration, request_dir, request_name): logger.error("failed to delete owner request for %s in %s" % \ (unique_resource_name, request_name)) output_objects.append({ 'object_type': 'error_text', 'text': 'Failed to remove saved request for %s in %s!' % \ (unique_resource_name, request_name)}) if cert_id_added: output_objects.append({ 'object_type': 'html_form', 'text': 'New owner(s)<br/>%s<br/>successfully added to %s!' % ('<br />'.join(cert_id_added), unique_resource_name) }) cert_id_fields = '' for cert_id in cert_id_added: cert_id_fields += """<input type=hidden name=cert_id value='%s' /> """ % cert_id form_method = 'post' csrf_limit = get_csrf_limit(configuration) fill_helpers = { 'res_id': unique_resource_name, 'cert_id_fields': cert_id_fields, 'any_protocol': any_protocol, 'form_method': form_method, 'csrf_field': csrf_field, 'csrf_limit': csrf_limit } 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': """ <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='resourceaccept' /> <input type=hidden name=unique_resource_name value='%(res_id)s' /> %(cert_id_fields)s <input type=hidden name=protocol value='%(any_protocol)s' /> <table> <tr> <td class='title'>Custom message to user(s)</td> </tr> <tr> <td><textarea name=request_text cols=72 rows=10> We have granted you ownership access to our %(res_id)s resource. You can access the resource administration page from the Resources page. Regards, the %(res_id)s resource owners </textarea></td> </tr> <tr> <td><input type='submit' value='Inform user(s)' /></td> </tr> </table> </form> <br /> """ % fill_helpers }) 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, 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) flavor = accepted['flavor'][-1].strip() freeze_id = accepted['freeze_id'][-1].strip() if not flavor in freeze_flavors.keys(): output_objects.append({ 'object_type': 'error_text', 'text': 'Invalid freeze flavor: %s' % flavor }) return (output_objects, returnvalues.CLIENT_ERROR) title = freeze_flavors[flavor]['adminfreeze_title'] output_objects.append({'object_type': 'header', 'text': title}) title_entry = find_entry(output_objects, 'title') title_entry['text'] = title if not configuration.site_enable_freeze: output_objects.append({ 'object_type': 'text', 'text': '''Freezing archives is disabled on this site. Please contact the site admins %s if you think it should be enabled. ''' % configuration.admin_email }) return (output_objects, returnvalues.OK) # Load existing freeze for stepwise construction if requested freeze_dict = {'ID': freeze_id, 'FLAVOR': flavor} if freeze_id != keyword_auto: (load_status, freeze_dict) = get_frozen_archive(client_id, freeze_id, configuration, checksum_list=[]) if not load_status: logger.error("%s: load failed for '%s': %s" % (op_name, freeze_id, brief_freeze(freeze_dict))) output_objects.append({ 'object_type': 'error_text', 'text': 'Could not read details for "%s"' % freeze_id }) return (output_objects, returnvalues.SYSTEM_ERROR) logger.debug("%s: loaded freeze: %s" % (op_name, brief_freeze(freeze_dict))) # Preserve already saved flavor flavor = freeze_dict.get('FLAVOR', 'freeze') freeze_state = freeze_dict.get('STATE', keyword_final) if freeze_state == keyword_final: logger.error("%s tried to edit finalized %s archive %s" % (client_id, flavor, freeze_id)) output_objects.append({ 'object_type': 'error_text', 'text': 'You cannot edit finalized %s archive %s' % (flavor, freeze_id) }) output_objects.append({ 'object_type': 'link', 'destination': 'showfreeze.py?freeze_id=%s;flavor=%s' % (freeze_id, flavor), 'class': 'viewarchivelink iconspace genericbutton', 'title': 'View details about your %s archive' % flavor, 'text': 'View details', }) return (output_objects, returnvalues.CLIENT_ERROR) elif freeze_state == keyword_updating: logger.error("%s tried to edit %s archive %s under update" % (client_id, flavor, freeze_id)) output_objects.append({ 'object_type': 'error_text', 'text': 'You cannot edit %s archive %s until active update completes' % (flavor, freeze_id) }) output_objects.append({ 'object_type': 'link', 'destination': 'showfreeze.py?freeze_id=%s;flavor=%s' % (freeze_id, flavor), 'class': 'viewarchivelink iconspace genericbutton', 'title': 'View details about your %s archive' % flavor, 'text': 'View details', }) return (output_objects, returnvalues.CLIENT_ERROR) form_method = 'post' csrf_limit = get_csrf_limit(configuration) fill_helpers = { 'flavor': flavor, 'form_method': form_method, 'csrf_field': csrf_field, 'csrf_limit': csrf_limit } target_op = 'uploadchunked' csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) fill_helpers.update({'target_op': target_op, 'csrf_token': csrf_token}) lookup_map = { 'freeze_id': 'ID', 'freeze_name': 'NAME', 'freeze_author': 'AUTHOR', 'freeze_description': 'DESCRIPTION' } for (key, val) in lookup_map.items(): fill_helpers[key] = freeze_dict.get(val, '') fill_helpers['publish_value'] = 'no' if freeze_dict.get('PUBLISH', False): fill_helpers['publish_value'] = 'yes' # jquery fancy upload (add_import, add_init, add_ready) = fancy_upload_js(configuration, csrf_token=csrf_token) # We need filechooser deps and dynamic addition of copy/upload fields add_import += ''' <script type="text/javascript" src="/images/js/jquery.tablesorter.js"></script> <script type="text/javascript" src="/images/js/jquery.prettyprint.js"></script> <script type="text/javascript" src="/images/js/jquery.contextmenu.js"></script> <script type="text/javascript" src="/images/js/jquery.xbreadcrumbs.js"></script> <!-- The preview image plugin --> <script type="text/javascript" src="/images/js/preview.js"></script> <!-- The image manipulation CamanJS plugin used by the preview image plugin --> <script type="text/javascript" src="/images/lib/CamanJS/dist/caman.full.js"></script> <!-- The nouislider plugin used by the preview image plugin --> <script type="text/javascript" src="/images/lib/noUiSlider/jquery.nouislider.all.js"></script> ''' add_init += ''' Caman.DEBUG = false var copy_fields = 0; var upload_fields = 0; var open_file_chooser; var open_upload_dialog; /* default upload destination */ var remote_path = "%s"; var trash_linkname = "%s"; var copy_div_id = "copyfiles"; var upload_div_id = "uploadfiles"; /* for upload_callback */ var field_id, field_name, wrap_id, upload_path, on_remove; function add_copy() { var div_id = copy_div_id; var field_id = "freeze_copy_"+copy_fields; var field_name = "freeze_copy_"+copy_fields; var wrap_id = field_id+"_wrap"; var browse_id = field_id+"_browse"; copy_entry = "<span id=\'"+wrap_id+"\'>"; copy_entry += "<input type=\'button\' value=\'Remove\' "; copy_entry += " onClick=\'remove_field(\"+wrap_id+\");\'/>"; // add browse button to mimic upload field copy_entry += "<input type=\'button\' id=\'"+browse_id+"\' "; copy_entry += " value=\'Browse...\' />"; copy_entry += "<input type=\'text\' id=\'"+field_id+"\' "; copy_entry += " name=\'" + field_name + "\' size=80 /><br / >"; copy_entry += "</span>"; $("#"+div_id).append(copy_entry); $("#"+field_id).click(function() { open_file_chooser("Add file or directory (right click to select)", function(file) { $("#"+field_id).val(file); }); }); $("#"+browse_id).click(function() { $("#"+field_id).click(); }); $("#"+field_id).click(); copy_fields += 1; } function upload_callback() { var div_id = upload_div_id; console.log("in upload callback"); $(".uploadfileslist > tr > td > p.name > a").each( function(index) { console.log("callback for upload item no. "+index); upload_path = $(this).text(); if ($(this).attr("href") == "") { console.log("skipping empty (error) upload: "+upload_path); // Continue to next iteration on errors return true; } console.log("callback for upload path "+upload_path); field_id = "freeze_move_"+upload_fields; field_name = "freeze_move_"+upload_fields; wrap_id = field_id+"_wrap"; if ($("#"+div_id+" > span > input[value=\\""+upload_path+"\\"]").length) { console.log("skipping duplicate path: "+upload_path); // Continue to next iteration on errors return true; } else { console.log("adding new path: "+upload_path); } on_remove = ""; on_remove += "remove_field("+wrap_id+");"; on_remove += "$.fn.delete_upload(\\""+upload_path+"\\");"; upload_entry = "<span id=\'"+wrap_id+"\'>"; upload_entry += "<input type=\'button\' value=\'Remove\' "; upload_entry += " onClick=\'"+on_remove+"\'/>"; upload_entry += "<input type=\'text\' id=\'"+field_id+"\' "; upload_entry += " name=\'" + field_name + "\' size=50 "; upload_entry += "value=\\""+upload_path+"\\" /><br / >"; upload_entry += "</span>"; $("#"+div_id).append(upload_entry); console.log("callback added upload: "+upload_entry); upload_fields += 1; }); console.log("callback done"); } function add_upload() { openFancyUpload("Upload Files", upload_callback, "", remote_path, true, "", "%s"); } function remove_field(field_id) { $(field_id).remove(); } // init file chooser dialogs with directory selection support function init_dialogs() { open_file_chooser = mig_filechooser_init("fm_filechooser", function(file) { return; }, false, "/"); /* We reuse shared init function but use custom actual opener above to mangle resulting files into archive form. */ initFancyUpload(); /* Alias open_dialog to open_upload_dialog for clarity */ open_upload_dialog = open_dialog; $("#addfilebutton").click(function() { add_copy(); }); $("#adduploadbutton").click(function() { add_upload(); }); } function init_page() { init_dialogs(); } ''' % (upload_tmp_dir, trash_linkname, csrf_token) add_ready += ''' // do sequenced initialisation (separate function) init_page(); ''' # TODO: can we update style inline to avoid explicit themed_styles? title_entry['style'] = themed_styles( configuration, base=[ 'jquery.contextmenu.css', 'jquery.managers.contextmenu.css', 'jquery.xbreadcrumbs.css', 'jquery.fmbreadcrumbs.css', 'jquery.fileupload.css', 'jquery.fileupload-ui.css' ], skin=['fileupload-ui.custom.css', 'xbreadcrumbs.custom.css'], user_settings=title_entry.get('user_settings', {})) title_entry['script']['advanced'] += add_import title_entry['script']['init'] += add_init title_entry['script']['ready'] += add_ready if flavor == 'freeze': fill_helpers['freeze_name'] = fill_helpers.get('freeze_name', '') fill_helpers["archive_header"] = "Freeze Archive Files" fill_helpers["button_label"] = "Save and Preview" intro_text = """ Please enter your archive details below and select any files to be included in the archive. """ elif flavor == 'phd': fill_helpers['freeze_name'] = fill_helpers.get('freeze_name', '') fill_helpers[ "archive_header"] = "Thesis and Associated Files to Archive" fill_helpers["button_label"] = "Save and Preview" intro_text = """ Please enter your PhD details below and select any files associated with your thesis. """ elif flavor == 'backup': fill_helpers['freeze_name'] = fill_helpers.get('freeze_name', '') if not fill_helpers['freeze_name']: now = datetime.datetime.now().isoformat().replace(':', '') fill_helpers['freeze_name'] = 'backup-%s' % now fill_helpers["archive_header"] = "Files and folders to Archive" fill_helpers["button_label"] = "Save and Preview" intro_text = """ Please select the files and folders to backup below. """ else: output_objects.append({ 'object_type': 'error_text', 'text': "unknown flavor: %s" % flavor }) return (output_objects, returnvalues.SYSTEM_ERROR) # Only warn here if default state is final and persistent if freeze_flavors[flavor]['states'][0] == keyword_final and \ flavor in configuration.site_permanent_freeze: intro_text += """ <p class='warn_message'>Note that frozen archives <em>cannot</em> be changed after final creation and then they can only be manually removed by management, so please be careful when filling in the details. </p> """ fill_helpers["fancy_dialog"] = fancy_upload_html(configuration) files_form = """ <!-- and now this... we do not want to see it, except in a dialog: --> <div id='fm_filechooser' style='display:none'> <div class='tree-container container-fluid'> <div class='tree-row row'> <div class='tree-header col-3'></div> <div class='fm_path_breadcrumbs col-6'> <ul id='fm_xbreadcrumbs' class='xbreadcrumbs'><!-- dynamic --></ul> </div> <div class='fm_buttonbar col-3 d-none d-lg-block'> <ul id='fm_buttons' class='buttonbar'> <!-- dynamically modified by js to show optional buttons --> <li class='datatransfersbutton hidden' title='Manage Data Transfers'> </li> <li class='sharelinksbutton hidden' title='Manage Share Links'> </li> <li class='parentdirbutton' title='Open Parent Directory'> </li> <li class='refreshbutton' title='Refresh'> </li> </ul> </div> <div class='fm_buttonbar-big col-6 d-block d-lg-none hidden'> <ul id='fm_buttons' class='buttonbar'> <!-- dynamically modified by js to show optional buttons --> <li class='datatransfersbutton hidden' title='Manage Data Transfers'> </li> <li class='sharelinksbutton hidden' title='Manage Share Links'> </li> <li class='parentdirbutton' title='Open Parent Directory'> </li> <li class='refreshbutton' title='Refresh'> </li> </ul> </div> </div> </div> <div class='fm_addressbar'> <input type='hidden' value='/' name='fm_current_path' /> </div> <div class='fm_folders'> <ul class='jqueryFileTree'> <li class='directory expanded'> <a>...</a> </li> </ul> </div> <div class='fm_files'> <table id='fm_filelisting' style='font-size:13px; border-spacing=0;'> <thead> <tr> <th class='fm_name'>Name</th> <th class='fm_size'>Size</th> <th class='fm_type'>Type</th> <th class='fm_date'>Date Modified</th> <th class='fm_toolbox'>...</th> </tr> </thead> <tbody> <!-- this is a placeholder for contents: do not remove! --> </tbody> </table> </div> <div id='fm_statusbar' class='col-lg-12'> <div id='fm_statusprogress' class=' col-lg-3'> <div class='progress-label'>Loading...</div> </div> <div id='fm_options' class='col-lg-2'> <label id='fm_toggle_touchscreen' class='switch' for='fm_touchscreen' > <input id='fm_touchscreen' type='checkbox' /> <span class='slider round' title='all clicks trigger menu'></span> </label><span id='fm_touchscreen_label'>Touch mode</span> <label id='fm_toggle_dotfiles' class='switch'> <input id='fm_dotfiles' type='checkbox' /> <span class='slider round' title='Show hidden files and folders'></span> </label><span id='fm_dotfiles_label'>Hidden files</span> </div> <div id='fm_statusinfo' class='col-lg-9'> </div> </div> </div> <div id='cmd_dialog' title='Command output' style='display: none;'></div> %(fancy_dialog)s """ % fill_helpers target_op = 'createfreeze' 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}) freeze_form = """ <form enctype='multipart/form-data' method='%(form_method)s' action='%(target_op)s.py'> <input type='hidden' name='%(csrf_field)s' value='%(csrf_token)s' /> <input type='hidden' name='flavor' value='%(flavor)s' /> <input type='hidden' name='freeze_id' value='%(freeze_id)s' /> <b>Name:</b><br /> <input class='fillwidth padspace' type='text' name='freeze_name' value='%(freeze_name)s' autofocus required pattern='[a-zA-Z0-9_. -]+' title='unique name for the freeze archive. I.e. letters and digits separated only by spaces, underscores, periods and hyphens' /> """ if flavor != 'backup': # TODO: do these make sense to have here or just forced in backend? freeze_form += """ <input type='hidden' name='freeze_department' value='' /> <input type='hidden' name='freeze_organization' value='' /> <br><b>Author(s):</b><br /> <input class='fillwidth padspace' type='text' name='freeze_author' value='%(freeze_author)s' title='optional archive author(s) in case archive is created on behalf of one or more people' /> <br /><b>Description:</b><br /> <textarea class='fillwidth padspace' rows='20' name='freeze_description'>%(freeze_description)s</textarea> <br /> """ freeze_form += """ <br /> <div id='freezefiles'> <b>%(archive_header)s:</b><br/> """ freeze_form += """ <input type='button' id='addfilebutton' value='Add file/directory' /> """ if flavor != 'backup': freeze_form += """ <input type='button' id='adduploadbutton' value='Add upload' /> """ freeze_form += """ <div id='copyfiles'> <!-- Dynamically filled --> </div> """ if flavor != 'backup': freeze_form += """ <div id='uploadfiles'> <!-- Dynamically filled --> </div> """ freeze_form += """ </div> <br /> """ if flavor != 'backup': freeze_form += """ <div id='freezepublish'> <b>Make Dataset Publicly Available</b> """ for val in ('yes', 'no'): checked = '' if fill_helpers['publish_value'] == val: checked = 'checked="checked"' freeze_form += """ <input type='radio' name='freeze_publish' value='%s' %s />%s """ % (val, checked, val) freeze_form += """ </div> <br /> """ freeze_form += """ <input type='submit' value='%(button_label)s' /> </form> """ # TODO: consider in-lining showfreeze file table for direct modify instead # probably requires more AJAX handling of actions. # for rel_path in frozen_files: # freeze_form += """%s<br/>""" % rel_path output_objects.append({'object_type': 'html_form', 'text': intro_text}) output_objects.append({'object_type': 'html_form', 'text': files_form}) output_objects.append({ 'object_type': 'html_form', 'text': freeze_form % fill_helpers }) # Link to view if archive already exists if freeze_id != keyword_auto: frozen_files = [ i['name'] for i in freeze_dict.get('FILES', []) if i['name'] != public_archive_index ] # Info about contents and spacing before view button otherwise if frozen_files: output_objects.append({ 'object_type': 'html_form', 'text': """ <h3>Previously Added Files</h3> <p>There are already %d file(s) saved in the archive and you can view and manage those through the link below e.g. in case you change your mind about including any of them. </p>""" % len(frozen_files) }) else: output_objects.append({'object_type': 'text', 'text': ''}) output_objects.append({ 'object_type': 'link', 'destination': 'showfreeze.py?freeze_id=%s;flavor=%s' % (freeze_id, flavor), 'class': 'viewarchivelink iconspace genericbutton', 'title': 'View details about your %s archive' % flavor, 'text': 'View details', 'target': '_blank', }) # 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, op_header=False) client_dir = client_id_dir(client_id) defaults = signature()[1] (validate_status, accepted) = validate_input_and_cert( user_arguments_dict, defaults, output_objects, client_id, configuration, allow_rejects=False, ) if not validate_status: return (accepted, returnvalues.CLIENT_ERROR) action = accepted['action'][-1] transfer_id = accepted['transfer_id'][-1] protocol = accepted['protocol'][-1] fqdn = accepted['fqdn'][-1] port = accepted['port'][-1] src_list = accepted['transfer_src'] dst = accepted['transfer_dst'][-1] username = accepted['username'][-1] password = accepted['transfer_pw'][-1] key_id = accepted['key_id'][-1] # Skip empty exclude entries as they break backend calls exclude_list = [i for i in accepted['exclude'] if i] notify = accepted['notify'][-1] compress = accepted['compress'][-1] flags = accepted['flags'] anon_checked, pw_checked, key_checked = '', '', '' if username: if key_id: key_checked = 'checked' init_login = "******" else: pw_checked = 'checked' init_login = "******" else: anon_checked = 'checked' init_login = "******" use_compress = False if compress.lower() in ("true", "1", "yes", "on"): use_compress = True title_entry = find_entry(output_objects, 'title') title_entry['text'] = 'Background Data Transfers' title_entry['container_class'] = 'fillwidth', # jquery support for tablesorter and confirmation on delete/redo: # datatransfer and key tables initially sorted by 0 (id) */ datatransfer_spec = { 'table_id': 'datatransferstable', 'pager_id': 'datatransfers_pager', 'sort_order': '[[0,0]]' } transferkey_spec = { 'table_id': 'transferkeystable', 'pager_id': 'transferkeys_pager', 'sort_order': '[[0,0]]' } (add_import, add_init, add_ready) = man_base_js(configuration, [datatransfer_spec, transferkey_spec]) add_init += ''' var fields = 0; var max_fields = 20; var src_input = "<label for=\'transfer_src\'>Source path(s)</label>"; src_input += "<input id=\'src_FIELD\' type=text size=60 name=transfer_src value=\'PATH\' title=\'relative source path: local for exports and remote for imports\' />"; src_input += "<input id=\'src_file_FIELD\' type=radio onclick=\'setSrcDir(FIELD, false);\' checked />Source file"; src_input += "<input id=\'src_dir_FIELD\' type=radio onclick=\'setSrcDir(FIELD, true);\' />Source directory (recursive)"; src_input += "<br />"; var exclude_input = "<label for=\'exclude\'>Exclude path(s)</label>"; exclude_input += "<input type=text size=60 name=exclude value=\'PATH\' title=\'relative path or regular expression to exclude\' />"; exclude_input += "<br />"; function addSource(path, is_dir) { if (path === undefined) { path = ""; } if (is_dir === undefined) { is_dir = false; } if (fields < max_fields) { $("#srcfields").append(src_input.replace(/FIELD/g, fields).replace(/PATH/g, path)); setSrcDir(fields, is_dir); fields += 1; } else { alert("Maximum " + max_fields + " source fields allowed!"); } } function addExclude(path) { if (path === undefined) { path = ""; } $("#excludefields").append(exclude_input.replace(/PATH/g, path)); } function setDir(target, field_no, is_dir) { var id_prefix = "#"+target+"_"; var input_id = id_prefix+field_no; var file_id = id_prefix+"file_"+field_no; var dir_id = id_prefix+"dir_"+field_no; var value = $(input_id).val(); $(file_id).prop("checked", ""); $(dir_id).prop("checked", ""); if (is_dir) { $(dir_id).prop("checked", "checked"); if(value.substr(-1) != "/") { value += "/"; } } else { $(file_id).prop("checked", "checked"); if(value.substr(-1) == "/") { value = value.substr(0, value.length - 1); } } $(input_id).val(value); return false; } function setSrcDir(field_no, is_dir) { return setDir("src", field_no, is_dir); } function setDstDir(field_no, is_dir) { return setDir("dst", field_no, is_dir); } function refreshSrcDir(field_no) { var dir_id = "#src_dir_"+field_no; var is_dir = $(dir_id).prop("checked"); return setSrcDir(field_no, is_dir); } function refreshDstDir(field_no) { var dir_id = "#dst_dir_"+field_no; var is_dir = $(dir_id).prop("checked"); return setDstDir(field_no, is_dir); } function setDefaultPort() { port_map = {"http": 80, "https": 443, "sftp": 22, "scp": 22, "ftp": 21, "ftps": 21, "webdav": 80, "webdavs": 443, "rsyncssh": 22, "rsyncd": 873}; var protocol = $("#protocol_select").val(); var port = port_map[protocol]; if (port != undefined) { $("#port_input").val(port); } else { alert("no default port provided for "+protocol); } } function beforeSubmit() { for(var i=0; i < fields; i++) { refreshSrcDir(i); } refreshDstDir(0); // Proceed with submit return true; } function doSubmit() { $("#submit-request-transfer").click(); } function enableLogin(method) { $("#anonymous_choice").prop("checked", ""); $("#userpassword_choice").prop("checked", ""); $("#userkey_choice").prop("checked", ""); $("#username").prop("disabled", false); $("#password").prop("disabled", true); $("#key").prop("disabled", true); $("#login_fields").show(); $("#password_entry").hide(); $("#key_entry").hide(); if (method == "password") { $("#userpassword_choice").prop("checked", "checked"); $("#password").prop("disabled", false); $("#password_entry").show(); } else if (method == "key") { $("#userkey_choice").prop("checked", "checked"); $("#key").prop("disabled", false); $("#key_entry").show(); } else { $("#anonymous_choice").prop("checked", "checked"); $("#username").prop("disabled", true); $("#login_fields").hide(); } } ''' # Mangle ready handling to begin with dynamic init and end with tab init pre_ready = ''' enableLogin("%s"); ''' % init_login for src in src_list or ['']: pre_ready += ''' addSource("%s", %s); ''' % (src, ("%s" % src.endswith('/')).lower()) for exclude in exclude_list or ['']: pre_ready += ''' addExclude("%s"); ''' % exclude add_ready = ''' %s %s /* NOTE: requires managers CSS fix for proper tab bar height */ $(".datatransfer-tabs").tabs(); $("#logarea").scrollTop($("#logarea")[0].scrollHeight); ''' % (pre_ready, add_ready) 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': 'Manage background data transfers' }) if not configuration.site_enable_transfers: output_objects.append({ 'object_type': 'text', 'text': '''Backgroung data transfers are disabled on this site. Please contact the site admins %s if you think they should be enabled. ''' % configuration.admin_email }) return (output_objects, returnvalues.OK) logger.info('datatransfer %s from %s' % (action, client_id)) if not action in valid_actions: output_objects.append({ 'object_type': 'error_text', 'text': 'Invalid action "%s" (supported: %s)' % (action, ', '.join(valid_actions)) }) return (output_objects, returnvalues.CLIENT_ERROR) if action in post_actions: if not safe_handler(configuration, 'post', op_name, client_id, get_csrf_limit(configuration), accepted): output_objects.append({ 'object_type': 'error_text', 'text': '''Only accepting CSRF-filtered POST requests to prevent unintended updates''' }) return (output_objects, returnvalues.CLIENT_ERROR) (load_status, transfer_map) = load_data_transfers(configuration, client_id) if not load_status: transfer_map = {} restrict_list = [] for from_fqdn in configuration.site_transfers_from: restrict_list += [from_fqdn, socket.gethostbyname(from_fqdn)] restrict_str = 'from="%s",no-pty,' % ','.join(restrict_list) restrict_str += 'no-port-forwarding,no-agent-forwarding,no-X11-forwarding' restrict_template = ''' As usual it is a good security measure to prepend a <em>from</em> restriction when you know the key will only be used from a single location.<br/> In this case the keys will only ever be used from %s and will not need much else, so the public key can be inserted in your authorized_keys file as: <br/> <p> <textarea class="publickey" rows="5" readonly="readonly">%s %%s</textarea> </p> ''' % (configuration.short_title, restrict_str) form_method = 'post' csrf_limit = get_csrf_limit(configuration) target_op = 'datatransfer' csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) if action in get_actions: datatransfers = [] for (saved_id, transfer_dict) in transfer_map.items(): transfer_item = build_transferitem_object(configuration, transfer_dict) transfer_item['status'] = transfer_item.get('status', 'NEW') data_url = '' # NOTE: we need to urlencode any exotic chars in paths here if transfer_item['action'] == 'import': enc_path = quote(("%(dst)s" % transfer_item)) data_url = "fileman.py?path=%s" % enc_path elif transfer_item['action'] == 'export': enc_paths = [quote(i) for i in transfer_item['src']] data_url = "fileman.py?path=" + ';path='.join(enc_paths) if data_url: transfer_item['viewdatalink'] = { 'object_type': 'link', 'destination': data_url, 'class': 'viewlink iconspace', 'title': 'View local component of %s' % saved_id, 'text': '' } transfer_item['viewoutputlink'] = { 'object_type': 'link', 'destination': "fileman.py?path=transfer_output/%s/" % saved_id, 'class': 'infolink iconspace', 'title': 'View status files for %s' % saved_id, 'text': '' } # Edit is just a call to self with fillimport set args = [('action', 'fill%(action)s' % transfer_dict), ('key_id', '%(key)s' % transfer_dict), ('transfer_dst', '%(dst)s' % transfer_dict)] for src in transfer_dict['src']: args.append(('transfer_src', src)) for exclude in transfer_dict.get('exclude', []): args.append(('exclude', exclude)) for field in edit_fields: val = transfer_dict.get(field, '') args.append((field, val)) transfer_args = urlencode(args, True) transfer_item['edittransferlink'] = { 'object_type': 'link', 'destination': "%s.py?%s" % (target_op, transfer_args), 'class': 'editlink iconspace', 'title': 'Edit or duplicate transfer %s' % saved_id, 'text': '' } js_name = 'delete%s' % hexlify(saved_id) helper = html_post_helper( js_name, '%s.py' % target_op, { 'transfer_id': saved_id, 'action': 'deltransfer', csrf_field: csrf_token }) output_objects.append({'object_type': 'html_form', 'text': helper}) transfer_item['deltransferlink'] = { 'object_type': 'link', 'destination': "javascript: confirmDialog(%s, '%s');" % (js_name, 'Really remove %s?' % saved_id), 'class': 'removelink iconspace', 'title': 'Remove %s' % saved_id, 'text': '' } js_name = 'redo%s' % hexlify(saved_id) helper = html_post_helper( js_name, '%s.py' % target_op, { 'transfer_id': saved_id, 'action': 'redotransfer', csrf_field: csrf_token }) output_objects.append({'object_type': 'html_form', 'text': helper}) transfer_item['redotransferlink'] = { 'object_type': 'link', 'destination': "javascript: confirmDialog(%s, '%s');" % (js_name, 'Really reschedule %s?' % saved_id), 'class': 'refreshlink iconspace', 'title': 'Reschedule %s' % saved_id, 'text': '' } datatransfers.append(transfer_item) #logger.debug("found datatransfers: %s" % datatransfers) log_path = os.path.join(configuration.user_home, client_id_dir(client_id), "transfer_output", configuration.site_transfer_log) show_lines = 40 log_lines = read_tail(log_path, show_lines, logger) available_keys = load_user_keys(configuration, client_id) if available_keys: key_note = '' else: key_note = '''No keys available - you can add a key for use in transfers below.''' if action.endswith('import'): transfer_action = 'import' elif action.endswith('export'): transfer_action = 'export' else: transfer_action = 'unknown' import_checked, export_checked = 'checked', '' toggle_quiet, scroll_to_create = '', '' if action in ['fillimport', 'fillexport']: if quiet(flags): toggle_quiet = ''' <script> $("#wrap-tabs").hide(); $("#quiet-mode-content").show(); </script> ''' scroll_to_create = ''' <script> $("html, body").animate({ scrollTop: $("#createtransfer").offset().top }, 2000); </script> ''' if action == 'fillimport': import_checked = 'checked' elif action == 'fillexport': export_checked = 'checked' import_checked = '' fill_helpers = { 'import_checked': import_checked, 'export_checked': export_checked, 'anon_checked': anon_checked, 'pw_checked': pw_checked, 'key_checked': key_checked, 'transfer_id': transfer_id, 'protocol': protocol, 'fqdn': fqdn, 'port': port, 'username': username, 'password': password, 'key_id': key_id, 'transfer_src_string': ', '.join(src_list), 'transfer_src': src_list, 'transfer_dst': dst, 'exclude': exclude_list, 'compress': use_compress, 'notify': notify, 'toggle_quiet': toggle_quiet, 'scroll_to_create': scroll_to_create, 'transfer_action': transfer_action, 'form_method': form_method, 'csrf_field': csrf_field, 'csrf_limit': csrf_limit, 'target_op': target_op, 'csrf_token': csrf_token } # Make page with manage transfers tab and manage keys tab output_objects.append({ 'object_type': 'html_form', 'text': ''' <div id="quiet-mode-content" class="hidden"> <p> Accept data %(transfer_action)s of %(transfer_src_string)s from %(protocol)s://%(fqdn)s:%(port)s/ into %(transfer_dst)s ? </p> <p> <input type=button onClick="doSubmit();" value="Accept %(transfer_action)s" /> </p> </div> <div id="wrap-tabs" class="datatransfer-tabs"> <ul> <li><a href="#transfer-tab">Manage Data Transfers</a></li> <li><a href="#keys-tab">Manage Transfer Keys</a></li> </ul> ''' % fill_helpers }) # Display external transfers, log and form to add new ones output_objects.append({ 'object_type': 'html_form', 'text': ''' <div id="transfer-tab"> ''' }) output_objects.append({ 'object_type': 'sectionheader', 'text': 'External Data Transfers' }) output_objects.append({ 'object_type': 'table_pager', 'id_prefix': 'datatransfers_', 'entry_name': 'transfers', 'default_entries': default_pager_entries }) output_objects.append({ 'object_type': 'datatransfers', 'datatransfers': datatransfers }) output_objects.append({ 'object_type': 'sectionheader', 'text': 'Latest Transfer Results' }) output_objects.append({ 'object_type': 'html_form', 'text': ''' <textarea id="logarea" class="fillwidth" rows=5 readonly="readonly">%s</textarea> ''' % (''.join(log_lines)) }) output_objects.append({ 'object_type': 'sectionheader', 'text': 'Create External Data Transfer' }) transfer_html = ''' <table class="addexttransfer"> <tr><td> Fill in the import/export data transfer details below to request a new background data transfer task.<br/> Source must be a path without wildcard characters and it must be specifically pointed out if the src is a directory. In that case recursive transfer will automatically be used and otherwise the src is considered a single file, so it will fail if that is not the case.<br/> Destination is a single location directory to transfer the data to. It is considered in relation to your user home for <em>import</em> requests. Source is similarly considered in relation to your user home in <em>export</em> requests.<br/> Destination is a always handled as a directory path to transfer source files into.<br/> <form method="%(form_method)s" action="%(target_op)s.py" onSubmit="return beforeSubmit();"> <input type="hidden" name="%(csrf_field)s" value="%(csrf_token)s" /> <fieldset id="transferbox"> <table id="createtransfer" class="addexttransfer"> <tr><td> <label for="action">Action</label> <input type=radio name=action %(import_checked)s value="import" />import data <input type=radio name=action %(export_checked)s value="export" />export data </td></tr> <tr><td> <label for="transfer_id">Optional Transfer ID / Name </label> <input type=text size=60 name=transfer_id value="%(transfer_id)s" pattern="[a-zA-Z0-9._-]*" title="Optional ID string containing only ASCII letters and digits possibly with separators like hyphen, underscore and period" /> </td></tr> <tr><td> <label for="protocol">Protocol</label> <select id="protocol_select" class="protocol-select themed-select html-select" name="protocol" onblur="setDefaultPort();"> ''' # select requested protocol for (key, val) in valid_proto: if protocol == key: selected = 'selected' else: selected = '' transfer_html += '<option %s value="%s">%s</option>' % \ (selected, key, val) transfer_html += ''' </select> </td></tr> <tr><td> <label for="fqdn">Host and port</label> <input type=text size=37 name=fqdn value="%(fqdn)s" required pattern="[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+" title="A fully qualified domain name or Internet IP address for the remote location"/> <input id="port_input" type=number step=1 min=1 max=65535 name=port value="%(port)s" required /> </td></tr> <tr><td> <label for="">Login method</label> <input id="anonymous_choice" type=radio %(anon_checked)s onclick="enableLogin(\'anonymous\');" /> anonymous access <input id="userpassword_choice" type=radio %(pw_checked)s onclick="enableLogin(\'password\');" /> login with password <input id="userkey_choice" type=radio %(key_checked)s onclick="enableLogin(\'key\');" /> login with key </td></tr> <tr id="login_fields" style="display: none;"><td> <label for="username">Username</label> <input id="username" type=text size=60 name=username value="%(username)s" pattern="[a-zA-Z0-9._-]*" title="Optional username used to login on the remote site, if required" /> <br/> <span id="password_entry"> <label for="transfer_pw">Password</label> <input id="password" type=password size=60 name=transfer_pw value="" /> </span> <span id="key_entry"> <label for="key_id">Key</label> <select id="key" class="key-select themed-select html-select" name=key_id /> ''' # select requested key for key_dict in available_keys: if key_dict['key_id'] == key_id: selected = 'selected' else: selected = '' transfer_html += '<option %s value="%s">%s</option>' % \ (selected, key_dict['key_id'], key_dict['key_id']) selected = '' transfer_html += ''' </select> %s ''' % key_note transfer_html += ''' </span> </td></tr> <tr><td> <div id="srcfields"> <!-- NOTE: automatically filled by addSource function --> </div> <input id="addsrcbutton" type="button" onclick="addSource(); return false;" value="Add another source field" /> </td></tr> <tr><td> <label for="transfer_dst">Destination path</label> <input id=\'dst_0\' type=text size=60 name=transfer_dst value="%(transfer_dst)s" required title="relative destination path: local for imports and remote for exports" /> <input id=\'dst_dir_0\' type=radio checked />Destination directory <input id=\'dst_file_0\' type=radio disabled />Destination file<br /> </td></tr> <tr><td> <div id="excludefields"> <!-- NOTE: automatically filled by addExclude function --> </div> <input id="addexcludebutton" type="button" onclick="addExclude(); return false;" value="Add another exclude field" /> </td></tr> <tr><td> <label for="compress">Enable compression (leave unset except for <em>slow</em> sites)</label> <input type=checkbox name=compress> </td></tr> <tr><td> <label for="notify">Optional notify on completion (e.g. email address)</label> <input type=text size=60 name=notify value=\'%(notify)s\'> </td></tr> <tr><td> <span> <input id="submit-request-transfer" type=submit value="Request transfer" /> <input type=reset value="Clear" /> </span> </td></tr> </table> </fieldset> </form> </td> </tr> </table> %(toggle_quiet)s %(scroll_to_create)s ''' output_objects.append({ 'object_type': 'html_form', 'text': transfer_html % fill_helpers }) output_objects.append({ 'object_type': 'html_form', 'text': ''' </div> ''' }) # Display key management output_objects.append({ 'object_type': 'html_form', 'text': ''' <div id="keys-tab"> ''' }) output_objects.append({ 'object_type': 'sectionheader', 'text': 'Manage Data Transfer Keys' }) key_html = ''' <form method="%(form_method)s" action="%(target_op)s.py"> <input type="hidden" name="%(csrf_field)s" value="%(csrf_token)s" /> <table class="managetransferkeys"> <tr><td> ''' transferkeys = [] for key_dict in available_keys: key_item = build_keyitem_object(configuration, key_dict) saved_id = key_item['key_id'] js_name = 'delete%s' % hexlify(saved_id) helper = html_post_helper(js_name, '%s.py' % target_op, { 'key_id': saved_id, 'action': 'delkey', csrf_field: csrf_token }) output_objects.append({'object_type': 'html_form', 'text': helper}) key_item['delkeylink'] = { 'object_type': 'link', 'destination': "javascript: confirmDialog(%s, '%s');" % (js_name, 'Really remove %s?' % saved_id), 'class': 'removelink iconspace', 'title': 'Remove %s' % saved_id, 'text': '' } transferkeys.append(key_item) output_objects.append({ 'object_type': 'table_pager', 'id_prefix': 'transferkeys_', 'entry_name': 'keys', 'default_entries': default_pager_entries }) output_objects.append({ 'object_type': 'transferkeys', 'transferkeys': transferkeys }) key_html += ''' Please copy the public key to your ~/.ssh/authorized_keys or ~/.ssh/authorized_keys2 file on systems where you want to login with the corresponding key.<br/> %s </td></tr> <tr><td> Select a name below to create a new key for use in future transfers. The key is generated and stored in a private storage area on %s, so that only the transfer service can access and use it for your transfers. </td></tr> <tr><td> <input type=hidden name=action value="generatekey" /> Key name:<br/> <input type=text size=60 name=key_id value="" required pattern="[a-zA-Z0-9._-]+" title="internal name for the key when used in transfers. I.e. letters and digits separated only by underscores, periods and hyphens" /> <br/> <input type=submit value="Generate key" /> </td></tr> </table> </form> ''' % (restrict_template % 'ssh-rsa AAAAB3NzaC...', configuration.short_title) output_objects.append({ 'object_type': 'html_form', 'text': key_html % fill_helpers }) output_objects.append({ 'object_type': 'html_form', 'text': ''' </div> ''' }) output_objects.append({ 'object_type': 'html_form', 'text': ''' </div> ''' }) return (output_objects, returnvalues.OK) elif action in transfer_actions: # NOTE: all path validation is done at run-time in grid_transfers transfer_dict = transfer_map.get(transfer_id, {}) if action == 'deltransfer': if transfer_dict is None: output_objects.append({ 'object_type': 'error_text', 'text': 'existing transfer_id is required for delete' }) return (output_objects, returnvalues.CLIENT_ERROR) (save_status, _) = delete_data_transfer(configuration, client_id, transfer_id, transfer_map) desc = "delete" elif action == 'redotransfer': if transfer_dict is None: output_objects.append({ 'object_type': 'error_text', 'text': 'existing transfer_id is required for reschedule' }) return (output_objects, returnvalues.CLIENT_ERROR) transfer_dict['status'] = 'NEW' (save_status, _) = update_data_transfer(configuration, client_id, transfer_dict, transfer_map) desc = "reschedule" else: if not fqdn: output_objects.append({ 'object_type': 'error_text', 'text': 'No host address provided!' }) return (output_objects, returnvalues.CLIENT_ERROR) if not [src for src in src_list if src] or not dst: output_objects.append({ 'object_type': 'error_text', 'text': 'transfer_src and transfer_dst parameters ' 'required for all data transfers!' }) return (output_objects, returnvalues.CLIENT_ERROR) if protocol == "rsyncssh" and not key_id: output_objects.append({ 'object_type': 'error_text', 'text': 'RSYNC over SSH is only supported with key!' }) return (output_objects, returnvalues.CLIENT_ERROR) if not password and not key_id and protocol in warn_anon: output_objects.append({ 'object_type': 'warning', 'text': ''' %s transfers usually require explicit authentication with your credentials. Proceeding as requested with anonymous login, but the transfer is likely to fail.''' % valid_proto_map[protocol] }) if key_id and protocol in warn_key: output_objects.append({ 'object_type': 'warning', 'text': ''' %s transfers usually only support authentication with username and password rather than key. Proceeding as requested, but the transfer is likely to fail if it really requires login.''' % valid_proto_map[protocol] }) # Make pseudo-unique ID based on msec time since epoch if not given if not transfer_id: transfer_id = "transfer-%d" % (time.time() * 1000) if transfer_dict: desc = "update" else: desc = "create" if password: # We don't want to store password in plain text on disk password_digest = make_digest('datatransfer', client_id, password, configuration.site_digest_salt) else: password_digest = '' transfer_dict.update({ 'transfer_id': transfer_id, 'action': action, 'protocol': protocol, 'fqdn': fqdn, 'port': port, 'username': username, 'password_digest': password_digest, 'key': key_id, 'src': src_list, 'dst': dst, 'exclude': exclude_list, 'compress': use_compress, 'notify': notify, 'status': 'NEW' }) (save_status, _) = create_data_transfer(configuration, client_id, transfer_dict, transfer_map) if not save_status: output_objects.append({ 'object_type': 'error_text', 'text': 'Error in %s data transfer %s: ' % (desc, transfer_id) + 'save updated transfers failed!' }) return (output_objects, returnvalues.CLIENT_ERROR) output_objects.append({ 'object_type': 'text', 'text': '%sd transfer request %s.' % (desc.title(), transfer_id) }) if action != 'deltransfer': output_objects.append({ 'object_type': 'link', 'destination': "fileman.py?path=transfer_output/%s/" % transfer_id, 'title': 'Transfer status and output', 'text': 'Transfer status and output folder' }) output_objects.append({ 'object_type': 'text', 'text': ''' Please note that the status files only appear after the transfer starts, so it may be empty now. ''' }) logger.debug('datatransfer %s from %s done: %s' % (action, client_id, transfer_dict)) elif action in key_actions: if action == 'generatekey': (gen_status, pub) = generate_user_key(configuration, client_id, key_id) if gen_status: output_objects.append({ 'object_type': 'html_form', 'text': ''' Generated new key with name %s and associated public key:<br/> <textarea class="publickey" rows="5" readonly="readonly">%s</textarea> <p> Please copy it to your ~/.ssh/authorized_keys or ~/.ssh/authorized_keys2 file on the host(s) where you want to use this key for background transfer login. <br/> %s </p> ''' % (key_id, pub, restrict_template % pub) }) else: output_objects.append({ 'object_type': 'error_text', 'text': ''' Key generation for name %s failed with error: %s''' % (key_id, pub) }) return (output_objects, returnvalues.CLIENT_ERROR) elif action == 'delkey': pubkey = '[unknown]' available_keys = load_user_keys(configuration, client_id) for key_dict in available_keys: if key_dict['key_id'] == key_id: pubkey = key_dict.get('public_key', pubkey) (del_status, msg) = delete_user_key(configuration, client_id, key_id) if del_status: output_objects.append({ 'object_type': 'html_form', 'text': ''' <p> Deleted the key "%s" and the associated public key:<br/> </p> <textarea class="publickey" rows="5" readonly="readonly">%s</textarea> <p> You will no longer be able to use it in your data transfers and can safely remove the public key from your ~/.ssh/authorized_keys* files on any hosts where you may have previously added it. </p> ''' % (key_id, pubkey) }) else: output_objects.append({ 'object_type': 'error_text', 'text': ''' Key removal for name %s failed with error: %s''' % (key_id, msg) }) return (output_objects, returnvalues.CLIENT_ERROR) else: output_objects.append({ 'object_type': 'error_text', 'text': 'Invalid data transfer action: %s' % action }) return (output_objects, returnvalues.CLIENT_ERROR) output_objects.append({ 'object_type': 'link', 'destination': 'datatransfer.py', 'text': 'Return to data transfers overview' }) 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') title_entry['text'] = 'Frozen Archives' (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) operation = accepted['operation'][-1] caching = (accepted['caching'][-1].lower() in ('true', 'yes')) if not configuration.site_enable_freeze: output_objects.append({ 'object_type': 'text', 'text': '''Freezing archives is disabled on this site. Please contact the site admins %s if you think it should be enabled. ''' % configuration.admin_email }) return (output_objects, returnvalues.OK) 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)) if operation in show_operations: # jquery support for tablesorter and confirmation on delete # table initially sorted by col. 5 (State), 3 (Created date), 2 (name) if client_id in configuration.site_freeze_admins: permanent_flavors = [] else: permanent_flavors = configuration.site_permanent_freeze # NOTE: We distinguish between caching on page load and forced refresh refresh_helper = 'ajax_freezedb(%s, "%s", %%s)' # NOTE: must insert permanent_flavors list as string here refresh_call = refresh_helper % (str(permanent_flavors), keyword_final) table_spec = { 'table_id': 'frozenarchivetable', 'sort_order': '[[5,1],[3,1],[2,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': 'Frozen Archives' }) output_objects.append({ 'object_type': 'text', 'text': '''Frozen archives are write-once collections of files used e.g. in relation to conference paper submissions. Please note that local policies may prevent users from deleting frozen archives without explicit acceptance from the management. ''' }) output_objects.append({ 'object_type': 'sectionheader', 'text': 'Existing frozen archives' }) # Helper form for removes form_method = 'post' csrf_limit = get_csrf_limit(configuration) target_op = 'deletefreeze' csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) helper = html_post_helper( 'delfreeze', '%s.py' % target_op, { 'freeze_id': '__DYNAMIC__', 'flavor': '__DYNAMIC__', 'target': TARGET_ARCHIVE, csrf_field: csrf_token }) output_objects.append({'object_type': 'html_form', 'text': helper}) output_objects.append({ 'object_type': 'table_pager', 'entry_name': 'frozen archives', 'default_entries': default_pager_entries }) frozenarchives, pending_updates = [], False if operation in list_operations: logger.info("list frozen archives with caching %s" % caching) # NOTE: we do NOT enforce creator match here as edituser can't update # without breaking any published archives (list_status, ret) = list_frozen_archives(configuration, client_id, strict_owner=False, caching=caching) if not list_status: logger.error("%s: failed for '%s': %s" % (op_name, client_id, ret)) output_objects.append({'object_type': 'error_text', 'text': ret}) return (output_objects, returnvalues.SYSTEM_ERROR) # NOTE: use simple pending check if caching to avoid lock during update if caching: pending_updates = pending_archives_update(configuration, client_id) else: pending_updates = False if pending_updates: logger.debug("found pending cache updates: %s" % pending_updates) else: logger.debug("no pending cache updates") logger.debug("%s %s: building list of archives" % (op_name, operation)) for freeze_id in ret: # TODO: add file count to meta and switch here # (load_status, freeze_dict) = get_frozen_meta(client_id, freeze_id, # configuration) (load_status, freeze_dict) = get_frozen_archive(client_id, freeze_id, configuration, checksum_list=[], caching=caching) if not load_status: logger.error("%s: load failed for '%s': %s" % (op_name, freeze_id, freeze_dict)) output_objects.append({ 'object_type': 'error_text', 'text': 'Could not read details for "%s"' % freeze_id }) return (output_objects, returnvalues.SYSTEM_ERROR) freeze_item = build_freezeitem_object(configuration, freeze_dict, summary=True) freeze_id = freeze_item['id'] flavor = freeze_item.get('flavor', 'freeze') # Users may view all their archives freeze_item['viewfreezelink'] = { 'object_type': 'link', 'destination': "showfreeze.py?freeze_id=%s;flavor=%s" % (freeze_id, flavor), 'class': 'infolink iconspace', 'title': 'View frozen archive %s' % freeze_id, 'text': '' } # Users may edit pending archives if freeze_item['state'] != keyword_final: freeze_item['editfreezelink'] = { 'object_type': 'link', 'destination': "adminfreeze.py?freeze_id=%s" % freeze_id, 'class': 'adminlink iconspace', 'title': 'Edit archive %s' % freeze_id, 'text': '' } # Users may delete pending or non permanent archives. # Freeze admins may delete all their own archives. if freeze_item['state'] != keyword_final or \ flavor not in configuration.site_permanent_freeze or \ client_id in configuration.site_freeze_admins: freeze_item['delfreezelink'] = { 'object_type': 'link', 'destination': "javascript: confirmDialog(%s, '%s', %s, %s);" % ('delfreeze', 'Really remove %s?' % freeze_id, 'undefined', "{freeze_id: '%s', flavor: '%s'}" % (freeze_id, flavor)), 'class': 'removelink iconspace', 'title': 'Remove %s' % freeze_id, 'text': '' } frozenarchives.append(freeze_item) logger.debug("%s %s: inserting list of %d archives" % (op_name, operation, len(frozenarchives))) output_objects.append({ 'object_type': 'frozenarchives', 'frozenarchives': frozenarchives, 'pending_updates': pending_updates }) if operation in show_operations: output_objects.append({ 'object_type': 'sectionheader', 'text': 'Additional Frozen Archives' }) output_objects.append({ 'object_type': 'text', 'text': """ You can create frozen snapshots/archives of particular subsets of your data in order to make sure a verbatim copy is preserved. The freeze archive method includes support for persistent publishing, so that you can e.g. reference your data in publications. Backup archives can be used as a basic backup mechanism, so that you can manually recover from any erroneous file removals.""" }) output_objects.append({ 'object_type': 'html_form', 'text': """<p> Choose one of the archive methods below to make a manual archive: </p> <p>""" }) output_objects.append({ 'object_type': 'link', 'destination': 'adminfreeze.py?flavor=freeze', 'class': 'addlink iconspace', 'title': 'Make a new freeze archive of e.g. ' 'research data to be published', 'text': 'Create a new freeze archive' }) output_objects.append({'object_type': 'html_form', 'text': '</p><p>'}) output_objects.append({ 'object_type': 'link', 'destination': 'adminfreeze.py?flavor=backup', 'class': 'addlink iconspace', 'title': 'Make a new backup archive of %s data' % configuration.short_title, 'text': 'Create a new backup archive' }) output_objects.append({ 'object_type': 'html_form', 'text': "<br/><br/></p>" }) if configuration.site_enable_duplicati: output_objects.append({ 'object_type': 'text', 'text': ''' Alternatively you can use Duplicati for traditional incremental backup/restore with optional encryption of all your backup contents.''' }) output_objects.append({ 'object_type': 'html_form', 'text': """ <p>For further details please refer to the """ }) output_objects.append({ 'object_type': 'link', 'destination': 'setup.py?topic=duplicati', 'class': '', 'title': 'Open Duplicati settings', 'text': 'Duplicati Settings' }) output_objects.append({ 'object_type': 'html_form', 'text': """ and the %s documentation.<br/><br/></p>""" % configuration.short_title }) if configuration.site_enable_seafile: output_objects.append({ 'object_type': 'text', 'text': ''' We recommend our Seafile sync solution for any small or medium sized data sets, for which you want automatic file versioning and easy roll-back support.''' }) output_objects.append({ 'object_type': 'html_form', 'text': """ <p>For further details please refer to the """ }) output_objects.append({ 'object_type': 'link', 'destination': 'setup.py?topic=seafile', 'class': '', 'title': 'Open Seafile settings', 'text': 'Seafile Settings' }) output_objects.append({ 'object_type': 'html_form', 'text': """ and the %s documentation.</p>""" % configuration.short_title }) logger.info("%s %s end for %s" % (op_name, operation, client_id)) 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) defaults = signature()[1] title_entry = find_entry(output_objects, 'title') label = "%s" % configuration.site_vgrid_label title_entry['text'] = "Add %s Resource" % label output_objects.append({ 'object_type': 'header', 'text': 'Add %s Resource(s)' % label }) status = returnvalues.OK (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() res_id_list = accepted['unique_resource_name'] request_name = unhexlify(accepted['request_name'][-1]) rank_list = accepted['rank'] + ['' for _ in res_id_list] 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) user_map = get_full_user_map(configuration) user_dict = user_map.get(client_id, None) # Optional site-wide limitation of manage vgrid permission if not user_dict or \ not vgrid_manage_allowed(configuration, user_dict): logger.warning("user %s is not allowed to manage vgrids!" % client_id) output_objects.append({ 'object_type': 'error_text', 'text': 'Only privileged users can manage %ss' % label }) return (output_objects, returnvalues.CLIENT_ERROR) # make sure vgrid settings allow this owner to edit resources (allow_status, allow_msg) = allow_resources_adm(configuration, vgrid_name, client_id) if not allow_status: output_objects.append({'object_type': 'error_text', 'text': allow_msg}) return (output_objects, returnvalues.CLIENT_ERROR) res_id_added = [] for (res_id, rank_str) in zip(res_id_list, rank_list): unique_resource_name = res_id.lower().strip() try: rank = int(rank_str) except ValueError: rank = None # 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, unique_resource_name, 'resource', configuration) if not ret_val: output_objects.append({'object_type': 'error_text', 'text': msg}) status = returnvalues.CLIENT_ERROR continue elif msg: # In case of warnings, msg is non-empty while ret_val remains True output_objects.append({'object_type': 'warning', 'text': msg}) # don't add if already in vgrid or parent vgrid unless rank is given if rank is None and vgrid_is_resource(vgrid_name, unique_resource_name, configuration): output_objects.append({ 'object_type': 'error_text', 'text': '%s is already a resource in the %s' % (unique_resource_name, label) }) status = returnvalues.CLIENT_ERROR continue # don't add if already in subvgrid (list_status, subvgrids) = vgrid_list_subvgrids(vgrid_name, configuration) if not list_status: output_objects.append({ 'object_type': 'error_text', 'text': 'Error getting list of sub%ss: %s' % (label, subvgrids) }) status = returnvalues.SYSTEM_ERROR continue skip_entity = False for subvgrid in subvgrids: if vgrid_is_resource(subvgrid, unique_resource_name, configuration, recursive=False): output_objects.append({ 'object_type': 'error_text', 'text': '''%(res_name)s is already in a sub-%(vgrid_label)s (%(subvgrid)s). Please remove the resource from the sub-%(vgrid_label)s and try again''' % { 'res_name': unique_resource_name, 'subvgrid': subvgrid, 'vgrid_label': label } }) status = returnvalues.CLIENT_ERROR skip_entity = True break if skip_entity: continue # Check if only rank change was requested and apply if so if rank is not None: (add_status, add_msg) = vgrid_add_resources(configuration, vgrid_name, [unique_resource_name], rank=rank) if not add_status: output_objects.append({ 'object_type': 'error_text', 'text': add_msg }) status = returnvalues.SYSTEM_ERROR else: output_objects.append({ 'object_type': 'text', 'text': 'changed %s to resource %d' % (res_id, rank) }) # No further action after rank change as everything else exists continue # Getting here means res_id is neither resource of any parent or # sub-vgrids. # Please note that base_dir must end in slash to avoid access to other # vgrid dirs when own name is a prefix of another name base_dir = os.path.abspath(configuration.vgrid_home + os.sep + vgrid_name) + os.sep resources_file = base_dir + 'resources' # Add to list and pickle (add_status, add_msg) = vgrid_add_resources(configuration, vgrid_name, [unique_resource_name]) if not add_status: output_objects.append({ 'object_type': 'error_text', 'text': '%s' % add_msg }) status = returnvalues.SYSTEM_ERROR continue res_id_added.append(unique_resource_name) if request_name: request_dir = os.path.join(configuration.vgrid_home, vgrid_name) if not delete_access_request(configuration, request_dir, request_name): logger.error("failed to delete res request for %s in %s" % (vgrid_name, request_name)) output_objects.append({ 'object_type': 'error_text', 'text': 'Failed to remove saved request for %s in %s!' % (vgrid_name, request_name) }) if res_id_added: output_objects.append({ 'object_type': 'html_form', 'text': 'New resource(s)<br />%s<br />successfully added to %s %s!' '' % ('<br />'.join(res_id_added), vgrid_name, label) }) res_id_fields = '' for res_id in res_id_added: res_id_fields += """ <input type=hidden name=unique_resource_name value='%s' />""" % res_id form_method = 'post' csrf_limit = get_csrf_limit(configuration) fill_helpers = { 'vgrid_name': vgrid_name, 'unique_resource_name': unique_resource_name, 'protocol': any_protocol, 'short_title': configuration.short_title, 'vgrid_label': label, 'res_id_fields': res_id_fields, 'form_method': form_method, 'csrf_field': csrf_field, 'csrf_limit': csrf_limit } 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': """ <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='vgridaccept' /> <input type=hidden name=vgrid_name value='%(vgrid_name)s' /> %(res_id_fields)s <input type=hidden name=protocol value='%(protocol)s' /> <table> <tr> <td class='title'>Custom message to resource owners</td> </tr><tr> <td><textarea name=request_text cols=72 rows=10> We have granted your %(unique_resource_name)s resource access to our %(vgrid_name)s %(vgrid_label)s. You can assign it to accept jobs from the %(vgrid_name)s %(vgrid_label)s from your Resources page on %(short_title)s. Regards, the %(vgrid_name)s %(vgrid_label)s owners </textarea></td> </tr> <tr> <td><input type='submit' value='Inform owners' /></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 html_tmpl(configuration, client_id, title_entry, csrf_map={}, chroot=''): """HTML page base: some upload and menu entries depend on configuration""" active_menu = extract_menu(configuration, title_entry) user_settings = title_entry.get('user_settings', {}) legacy_ui = legacy_user_interface(configuration, user_settings) fill_helpers = {'short_title': configuration.short_title} html = ''' <!-- CONTENT --> <div class="container"> <div id="app-nav-container" class="row"> <h1>Welcome to %(short_title)s!</h1> <div class="home-page__header col-12"> <p class="sub-title">Tools from %(short_title)s helps you with storage, sharing and archiving of data. %(short_title)s delivers centralised storage space for personal and shared files.</p> </div> <div id="tips-container" class="col-12 invert-theme"> <div id="tips-content" class="tips-placeholder"> <!-- NOTE: filled by AJAX .. init for space --> </div> </div> ''' % fill_helpers html += render_apps(configuration, title_entry, active_menu) html += ''' <div class="col-lg-12 vertical-spacer"></div> </div> </div> ''' # Dynamic app selection form_method = 'post' csrf_limit = get_csrf_limit(configuration) target_op = 'settingsaction' csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) settings_specs = get_keywords_dict() current_settings_dict = load_settings(client_id, configuration) if not current_settings_dict: # no current settings found current_settings_dict = {} fill_helpers['form_prefix'] = ''' <form class="save_settings save_apps" action="settingsaction.py" method="%s"> <input type="hidden" name="%s" value="%s"> ''' % (form_method, csrf_field, csrf_token) fill_helpers['form_suffix'] = ''' %s <input type="submit" value="Save"> </form> ''' % save_settings_html(configuration) apps_field = 'SITE_USER_MENU' # NOTE: build list of all default and user selectable apps in that order app_list = [app_id for app_id in configuration.site_default_menu] app_list += [ app_id for app_id in configuration.site_user_menu if not app_id in app_list ] mandatory_apps = [] for app_name in configuration.site_default_menu: mandatory_apps.append(menu_items[app_name]['title']) fill_helpers['mandatory_apps'] = ', '.join(mandatory_apps) html += ''' <!-- APP POP-UP --> <div id="add-app__window" class="app-container hidden"> <span class="add-app__close far fa-times-circle" onclick="closeAddApp()"></span> <div class="container"> <div class="row"> <div class="app-page__header col-12"> <h1>Your apps & app-setup</h1> <p class="sub-title-white">Here you can select which apps you want to use in your %(short_title)s system. Only %(mandatory_apps)s are mandatory.</p> </div> <div class="app-page__header col-12"> <h2>Select your apps</h2> %(form_prefix)s ''' % fill_helpers # The backend form requires all user settings even if we only want to edit # the user apps subset here - just fill as hidden values. # TODO: consider a save version with only relevant values. for (key, val) in current_settings_dict.items(): if key == apps_field: continue spec = settings_specs[key] if spec['Type'] == 'multiplestrings': html += ''' <textarea class="hidden" name="%s">%s</textarea> ''' % (key, '\n'.join(val)) elif spec['Type'] == 'string': html += ''' <input type="hidden" name="%s" value="%s"> ''' % (key, val) elif spec['Type'] == 'boolean': html += ''' <input type="hidden" name="%s" value="%s"> ''' % (key, val) html += ''' <div class="app-grid row"> ''' for app_id in app_list: app = menu_items[app_id] app_name = app['title'] if not legacy_ui and app.get('legacy_only', False): continue if app_id == 'vgrids': app_name = '%ss' % configuration.site_vgrid_label mandatory = (app_id in configuration.site_default_menu) app_btn_classes = 'app__btn col-12 ' if mandatory: app_btn_classes += "mandatory" else: app_btn_classes += "optional" app_icon_classes = app['class'] check_field = '' if not mandatory: checked, checkmark = "", "checkmark" if app_id in current_settings_dict.get(apps_field, []): checked = "checked='checked'" checkmark = "checkmark" check_field = ''' <input type="checkbox" name="SITE_USER_MENU" %s value="%s"> <span class="%s"></span> ''' % (checked, app_id, checkmark) html += ''' <div class="col-lg-2"> <div class="%s"> <label class="app-content"> %s <div class="background-app"> <span class="%s"></span><h3>%s</h3> </div> </label> </div> </div> ''' % (app_btn_classes, check_field, app_icon_classes, app_name) html += ''' </div> <br /> %(form_suffix)s </div> </div> </div> <div class="col-lg-12 vertical-spacer"></div> </div> ''' % fill_helpers return html
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': 'Reject Resource Request' }) (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].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) 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 reject requests!' % unique_resource_name }) return (output_objects, returnvalues.CLIENT_ERROR) # Please note that base_dir must end in slash to avoid access to other # resource dirs when own name is a prefix of another user name base_dir = \ os.path.abspath(os.path.join(configuration.resource_home, unique_resource_name)) + os.sep # IMPORTANT: path must be expanded to abs for proper chrooting abs_path = os.path.abspath(os.path.join(base_dir, request_name)) if not valid_user_path( configuration, abs_path, base_dir, allow_equal=False): logger.warning('%s tried to access restricted path %s ! (%s)' % \ (client_id, abs_path, request_name)) output_objects.append({ 'object_type': 'error_text', 'text': '''Illegal request name "%s": you can only reject requests to your own resources.''' % request_name }) return (output_objects, returnvalues.CLIENT_ERROR) if request_name: request_dir = os.path.join(configuration.resource_home, unique_resource_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" % \ (unique_resource_name, request_name)) output_objects.append({ 'object_type': 'error_text', 'text': 'Failed to remove saved resource request for %s in %s!'\ % (unique_resource_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 }) form_method = 'post' csrf_limit = get_csrf_limit(configuration) fill_helpers = { 'protocol': any_protocol, 'unique_resource_name': unique_resource_name, '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='resourcereject' /> <input type=hidden name=unique_resource_name value='%(target)s' /> <input type=hidden name=cert_id 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 resource. Regards, the %(target)s resource 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': 'resadmin.py?unique_resource_name=%s' % unique_resource_name, 'text': 'Back to administration for %s' % unique_resource_name }) return (output_objects, returnvalues.OK)
def display_resource(client_id, resourcename, raw_conf, resource_config, owners, re_list, configuration, fill_helpers): """Format and print the information and actions for a given resource. """ exe_units = [] store_units = [] frontend = None hosturl = None html = '' row_name = ('even', 'odd') if resource_config: if 'EXECONFIG' in resource_config: for exe in resource_config['EXECONFIG']: exe_units.append(exe['name']) if 'STORECONFIG' in resource_config: for store in resource_config['STORECONFIG']: store_units.append(store['name']) if 'FRONTENDNODE' in resource_config: frontend = resource_config['FRONTENDNODE'] if 'HOSTURL' in resource_config: hosturl = resource_config['HOSTURL'] # Try to split resourcename first to support resources where name # doesn't match hosturl. sep = '.' index = resourcename.rfind(sep) if index: hosturl = resourcename[:index] identifier = resourcename[index + 1:] elif hosturl: identifier = resourcename.replace(hosturl + sep, '') else: configuration.logger.warning( 'failed to find host identifier from unique resource name!') (hosturl, identifier) = (None, 0) form_method = fill_helpers['form_method'] csrf_limit = fill_helpers['csrf_limit'] fill_helpers.update({ 'res_id': resourcename, 'hosturl': hosturl, 'identifier': identifier }) html += '''<a id="%(res_id)s"></a> <h1>%(res_id)s</h1> <h3>Configuration</h3> Use the <a class="editlink iconspace" href="resedit.py?hosturl=%(hosturl)s;hostidentifier=%(identifier)s"> editing interface </a> or make any changes manually in the text box below.<br /> <a class="infolink iconspace" href="docs.py?show=Resource"> Resource configuration docs </a> ''' target_op = 'updateresconfig' 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}) html += ''' <form method="%(form_method)s" action="%(target_op)s.py"> <input type="hidden" name="%(csrf_field)s" value="%(csrf_token)s" /> <table class=resources> <tr> <td class=centertext> <textarea class="fillwidth padspace" rows="25" name="resconfig">''' % \ fill_helpers for line in raw_conf: html += '%s\n' % line.strip() html += \ '''</textarea> <br /> <input type="hidden" name="unique_resource_name" value="%(res_id)s" /> <input type="submit" value="Save" /> ---------- <input type="reset" value="Forget changes" /> ''' html += ''' </td></tr> </table> </form> <p> <h3>Control Resource Units</h3> <table class=resources> <tr class=title><td colspan="5">Front End</td></tr> ''' if not frontend: html += '<tr><td colspan=5>Not specified</td></tr>\n' else: html += '<tr><td>%s</td>' % frontend for action in ['restart', 'status', 'stop', 'clean']: if action == 'restart': action_str = '(Re)Start' else: action_str = action.capitalize() target_op = "%sfe" % action csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) fill_helpers.update({ 'action_str': action_str, 'target_op': target_op, 'csrf_token': csrf_token }) html += '''<td> <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="unique_resource_name" value="%(res_id)s" /> <input type="submit" value="%(action_str)s" /> </form> </td> ''' % fill_helpers html += '</tr>' html += '<tr class=title><td colspan=5>Execution Units</td></tr>\n' if not exe_units: html += '<tr><td colspan=5>None specified</td></tr>\n' else: html += '<tr><td>ALL UNITS</td>' for action in ['restart', 'status', 'stop', 'clean']: if action == 'restart': action_str = '(Re)Start' else: action_str = action.capitalize() target_op = "%sexe" % action csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) fill_helpers.update({ 'action_str': action_str, 'target_op': target_op, 'csrf_token': csrf_token }) html += '''<td> <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="unique_resource_name" value="%(res_id)s" /> <input type="hidden" name="all" value="true" /> <input type="hidden" name="parallel" value="true" /> <input type="submit" value="%(action_str)s" /> </form> </td> ''' % fill_helpers html += '</tr>' row_number = 1 for unit in exe_units: row_class = row_name[row_number % 2] html += '<tr class=%s><td>%s</td>' % (row_class, unit) for action in ['restart', 'status', 'stop', 'clean']: if action == 'restart': action_str = '(Re)Start' else: action_str = action.capitalize() target_op = "%sexe" % action csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) fill_helpers.update({ 'unit': unit, 'action_str': action_str, 'target_op': target_op, 'csrf_token': csrf_token }) html += '''<td> <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="unique_resource_name" value="%(res_id)s" /> <input type="hidden" name="exe_name" value="%(unit)s" /> <input type="submit" value="%(action_str)s" /> </form> </td> ''' % fill_helpers html += '</tr>' row_number += 1 html += '<tr class=title><td colspan=5>Storage Units</td></tr>\n' if not store_units: html += '<tr><td colspan=5>None specified</td></tr>\n' else: html += '<tr><td>ALL UNITS</td>' for action in ['restart', 'status', 'stop', 'clean']: if action == 'restart': action_str = '(Re)Start' else: action_str = action.capitalize() target_op = "%sstore" % action csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) fill_helpers.update({ 'action_str': action_str, 'target_op': target_op, 'csrf_token': csrf_token }) html += '''<td> <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="unique_resource_name" value="%(res_id)s" /> <input type="hidden" name="all" value="true" /> <input type="hidden" name="parallel" value="true" /> <input type="submit" value="%(action_str)s" /> </form> </td> ''' % fill_helpers html += '</tr>' row_number = 1 for unit in store_units: row_class = row_name[row_number % 2] html += '<tr class=%s><td>%s</td>' % (row_class, unit) for action in ['restart', 'status', 'stop', 'clean']: if action == 'restart': action_str = '(Re)Start' else: action_str = action.capitalize() target_op = "%sstore" % action csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) fill_helpers.update({ 'unit': unit, 'action_str': action_str, 'target_op': target_op, 'csrf_token': csrf_token }) html += '''<td> <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="unique_resource_name" value="%(res_id)s" /> <input type="hidden" name="store_name" value="%(unit)s" /> <input type="submit" value="%(action_str)s" /> </form> </td> ''' % fill_helpers html += '</tr>' row_number += 1 html += '</table><p>' html += '<h3>Owners</h3>' html += \ ''' Current owners of %(res_id)s.<br /> <table class=resources> ''' target_op = "rmresowner" 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}) for owner_id in owners: fill_helpers['cert_id'] = owner_id html += '''<tr><td> <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="unique_resource_name" value="%(res_id)s" /> <input type="hidden" name="cert_id" value="%(cert_id)s" /> <input type="hidden" name="output_format" value="html" /> <input type="submit" value="Remove" /> </form> </td> ''' % fill_helpers anon_id = anon_user_id(owner_id) html += '<td>%s</td></tr>' % \ html_link({ 'destination': 'viewuser.py?cert_id=%s' % anon_id, 'class': 'userlink iconspace', 'title': 'View user details for %s' % owner_id, 'text': owner_id}) html += '</table>' openid_add = "" if configuration.user_openid_providers: openid_add = "either the OpenID alias or " target_op = "addresowner" csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) fill_helpers.update({ 'openid_add': openid_add, 'target_op': target_op, 'csrf_token': csrf_token }) html += ''' <table class=resources> <tr><td> <form method="%(form_method)s" action="%(target_op)s.py"> <input type="hidden" name="%(csrf_field)s" value="%(csrf_token)s" /> <fieldset> <legend>Add resource owner(s)</legend> Note: owners are specified with %(openid_add)s the Distinguished Name (DN) of the user. If in doubt, just let the user request access and accept it with the <span class="addlink"></span>-icon in the Pending Requests table. <br /> <input type="hidden" name="unique_resource_name" value="%(res_id)s" /> <input type="hidden" name="output_format" value="html" /> <div id="dynownerspares"> <!-- placeholder for dynamic add owner fields --> </div> <input type="submit" value=" Add " /> </fieldset> </form> </td></tr> </table> <br /> ''' % fill_helpers # create html to request vgrid resource access html += '<h3>%(vgrid_label)s access</h3>' 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}) html += ''' <table class=resources> <tr><td> <form method="%(form_method)s" action="%(target_op)s.py"> <input type="hidden" name="%(csrf_field)s" value="%(csrf_token)s" /> <fieldset> <legend>Request resource access to additional %(vgrid_label)ss</legend> <input type="hidden" name="unique_resource_name" value="%(res_id)s" /> <input type="hidden" name="request_type" value="vgridresource" /> <select name="vgrid_name">''' % fill_helpers # list all vgrids without access use_cache = True allowed_vgrids = res_vgrid_access(configuration, resourcename, caching=use_cache) vgrid_list = get_vgrid_map_vgrids(configuration, caching=use_cache) for vgrid_name in vgrid_list: if not vgrid_name in allowed_vgrids: html += '<option value=%s>%s' % (vgrid_name, vgrid_name) html += """</select>""" html += ''' Message to owners: <input type="text" name="request_text" size=50 value="" /> <input type="submit" value="send" /> </fieldset> </form> </td></tr></table><br /> ''' # create html to select and execute a runtime environment testprocedure html += '<h3>Runtime environments</h3>' target_op = "testresupport" 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}) html += ''' <table class=resources> <tr><td> <form method="%(form_method)s" action="%(target_op)s.py"> <input type="hidden" name="%(csrf_field)s" value="%(csrf_token)s" /> <fieldset> <legend>Verify that resource supports the selected runtime environment </legend> <input type="hidden" name="unique_resource_name" value="%(res_id)s" /> <select name="re_name">''' % fill_helpers # list runtime environments that have a testprocedure for env in re_list: (re_dict, re_msg) = get_re_dict(env, configuration) if re_dict: if 'TESTPROCEDURE' in re_dict: if re_dict['TESTPROCEDURE'] != []: html += '<option value=%s>%s' % (env, env) html += """</select>""" html += '<input type="submit" value="verify" /></fieldset>' html += ''' </form> </td></tr></table><br/> ''' # create html to select and call script to display testprocedure history target_op = "showresupport" 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}) verify_history = ''' Show testprocedure history for the selected runtime environment and the resource with its current configuration. <table class=resources> <tr><td> <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="unique_resource_name" value="%(res_id)s" /> <select name="re_name">''' % fill_helpers # list runtime environments that have a testprocedure for env in re_list: (re_dict, re_msg) = get_re_dict(env, configuration) if re_dict: if 'TESTPROCEDURE' in re_dict: if re_dict['TESTPROCEDURE'] != []: verify_history += '<option value=%s>%s' % (env, env) verify_history += """</select>""" verify_history += '<input type="submit" value="Show" />' verify_history += ''' </form> </td></tr></table><br /> ''' # TODO: reimplement showresupporthistory in new style and re-enable here #html += verify_history return html % fill_helpers
# NOTE: force update user_map before calling sendrequestaction! # create_user does NOT necessarily update it due to caching time. refresh_user_map(configuration) # Needed for CSRF check in safe_handler form_method = 'post' csrf_limit = get_csrf_limit(configuration) target_op = 'sendrequestaction' os.environ.update({ 'SCRIPT_URL': '%s.py' % target_op, 'REQUEST_METHOD': form_method }) for user_dict in new_users: fill_user(user_dict) client_id = user_dict['distinguished_name'] csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) for name in vgrids: request = { 'vgrid_name': [name], 'request_type': ['vgridmember'], 'request_text': ['automatic request from importusers script'], csrf_field: [csrf_token] } (output_objs, status) = main(client_id, request) if status == returnvalues.OK: print('Request for %s membership in %s sent to owners' % (client_id, name)) else: print('Request for %s membership in %s with %s failed:' % (client_id, name, request)) output_format = 'text'
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) 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) hosturl = accepted['hosturl'][-1] hostidentifier = accepted['hostidentifier'][-1] resource_id = '%s.%s' % (hosturl, hostidentifier) extra_selects = 3 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) # Find allowed VGrids and Runtimeenvironments and add them to # configuration object for automated choice handling allowed_vgrids = [''] + res_vgrid_access(configuration, resource_id) allowed_vgrids.sort() configuration.vgrids = allowed_vgrids (re_status, allowed_run_envs) = list_runtime_environments(configuration) allowed_run_envs.sort() area_cols = 80 area_rows = 5 status = returnvalues.OK logger.info('Starting Resource edit GUI.') title_entry = find_entry(output_objects, 'title') title_entry['text'] = 'Resource Editor' output_objects.append({'object_type': 'header', 'text': 'Resource Editor'}) output_objects.append({ 'object_type': 'sectionheader', 'text': '%s Resource Editor' % configuration.short_title }) output_objects.append({ 'object_type': 'text', 'text': ''' Please fill in or edit the fields below to fit your %s resource reservation. Most fields will work with their default values. So if you are still in doubt after reading the help description, you can likely just leave the field alone.''' % configuration.short_title }) if hosturl and hostidentifier: conf = init_conf(configuration, hosturl, hostidentifier) if not conf: status = returnvalues.CLIENT_ERROR output_objects.append({ 'object_type': 'error_text', 'text': '''No such resource! (%s.%s)''' % (hosturl, hostidentifier) }) return (output_objects, status) else: conf = empty_resource_config(configuration) res_fields = resconfkeywords.get_resource_specs(configuration) exe_fields = resconfkeywords.get_exenode_specs(configuration) store_fields = resconfkeywords.get_storenode_specs(configuration) 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 = 'reseditaction' 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' /> """ % fill_helpers }) # Resource overall fields output_objects.append({ 'object_type': 'sectionheader', 'text': "Main Resource Settings" }) output_objects.append({ 'object_type': 'text', 'text': """This section configures general options for the resource.""" }) (title, field) = ('Host FQDN', 'HOSTURL') if hosturl: try: hostip = conf.get('HOSTIP', socket.gethostbyname(hosturl)) except: hostip = '<unknown>' output_objects.append({ 'object_type': 'html_form', 'text': """<br /> <b>%s:</b> <a class='infolink iconspace' href='resedithelp.py#res-%s'>help</a><br /> <input type='hidden' name='%s' value='%s' /> <input type='hidden' name='hostip' value='%s' /> %s <br /> <br />""" % (title, field, field, conf[field], hostip, conf[field]) }) else: output_objects.append({ 'object_type': 'html_form', 'text': """<br /> <b>%s:</b> <a class='infolink iconspace' href='resedithelp.py#res-%s'>help</a><br /> <input class='fillwidth padspace' type='text' name='%s' size='%d' value='%s' required pattern='[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+' title='Fully qualified domain name or Internet IP address of the resource' /> <br /> <br />""" % (title, field, field, field_size(conf[field]), conf[field]) }) (title, field) = ('Host identifier', 'HOSTIDENTIFIER') if hostidentifier: output_objects.append({ 'object_type': 'html_form', 'text': """<br /> <b>%s:</b> <a class='infolink iconspace' href='resedithelp.py#res-%s'>help</a><br /> <input type='hidden' name='%s' value='%s' /> %s <br /> <br />""" % (title, field, field, conf[field], conf[field]) }) (field, title) = 'frontendhome', 'Frontend Home Path' output_objects.append({ 'object_type': 'html_form', 'text': """<br /> <b>%s:</b> <a class='infolink iconspace' href='resedithelp.py#%s'>help</a><br /> <input class='fillwidth padspace' type='text' name='%s' size='%d' value='%s' required pattern='[^ ]+' title='Absolute path to user home on the resource' /> <br /> <br />""" % (title, field, field, field_size(conf[field]), conf[field]) }) for (field, spec) in res_fields: title = spec['Title'] field_type = spec['Type'] if spec['Required']: required_str = 'required' else: required_str = '' if 'invisible' == spec['Editor']: continue elif 'input' == spec['Editor']: if spec['Type'] == 'int': input_str = """ <input class='fillwidth padspace' type='number' name='%s' size='%d' value='%s' min=0 pattern='[0-9]+' %s /> """ % (field, field_size(conf[field]), conf[field], required_str) else: input_str = """ <input class='fillwidth padspace' type='text' name='%s' size='%d' value='%s' %s /> """ % (field, field_size(conf[field]), conf[field], required_str) output_objects.append({ 'object_type': 'html_form', 'text': """<br /> <b>%s:</b> <a class='infolink iconspace' href='resedithelp.py#res-%s'>help</a> <br /> %s<br /> <br />""" % (title, field, input_str) }) elif 'select' == spec['Editor']: choices = available_choices(configuration, client_id, resource_id, field, spec) res_value = conf[field] value_select = '' if field_type.startswith('multiple'): select_count = len(res_value) + extra_selects else: select_count = 1 res_value = [res_value] for i in range(select_count): value_select += "<select name='%s'>\n" % field for name in choices: selected = '' if i < len(res_value) and res_value[i] == name: selected = 'selected' display = "%s" % name if display == '': display = ' ' value_select += """<option %s value='%s'>%s</option>\n""" \ % (selected, name, display) value_select += """</select><br />\n""" output_objects.append({ 'object_type': 'html_form', 'text': """<br /> <b>%s:</b> <a class='infolink iconspace' href='resedithelp.py#res-%s'>help</a><br /> %s <br />""" % (title, field, value_select) }) # Not all resource fields here map directly to keywords/specs input field (title, field) = ('Runtime Environments', 'RUNTIMEENVIRONMENT') re_list = conf[field] show = re_list + [('', []) for i in range(extra_selects)] re_select = "<input type='hidden' name='runtime_env_fields' value='%s'/>\n" \ % len(show) i = 0 for active in show: re_select += "<select name='runtimeenvironment%d'>\n" % i for name in allowed_run_envs + ['']: selected = '' if active[0] == name: selected = 'selected' display = "%s" % name if display == '': display = ' ' re_select += """<option %s value='%s'>%s</option>\n""" % \ (selected, name, display) re_select += """</select><br />\n""" values = '\n'.join(['%s=%s' % pair for pair in active[1]]) re_select += "<textarea class='fillwidth padspace' cols='%d' rows='%d' name='re_values%d'>%s</textarea><br />\n" % \ (area_cols, area_rows, i, values) i += 1 output_objects.append({ 'object_type': 'html_form', 'text': """<br /> <b>%s:</b> <a class='infolink iconspace' href='resedithelp.py#res-%s'>help</a><br /> Please enter any required environment variable settings on the form NAME=VALUE in the box below each selected runtimeenvironment.<br /> %s <br />""" % (title, field, re_select) }) # Execution node fields output_objects.append({ 'object_type': 'sectionheader', 'text': "Execution nodes" }) output_objects.append({ 'object_type': 'text', 'text': """This section configures execution nodes on the resource.""" }) (field, title) = 'executionnodes', 'Execution Node(s)' output_objects.append({ 'object_type': 'html_form', 'text': """<br /> <b>%s:</b> <a class='infolink iconspace' href='resedithelp.py#exe-%s'>help</a><br /> <input class='fillwidth padspace' type='text' name='exe-%s' size='%d' value='%s' /> <br /> <br />""" % (title, field, field, field_size( conf['all_exes'][field]), conf['all_exes'][field]) }) (field, title) = 'executionhome', 'Execution Home Path' output_objects.append({ 'object_type': 'html_form', 'text': """<br /> <b>%s:</b> <a class='infolink iconspace' href='resedithelp.py#exe-%s'>help</a><br /> <input class='fillwidth padspace' type='text' name='exe-%s' size='%d' value='%s' /> <br /> <br />""" % (title, field, field, field_size( conf['all_exes'][field]), conf['all_exes'][field]) }) for (field, spec) in exe_fields: title = spec['Title'] field_type = spec['Type'] # We don't really have a good map of required fields here so disable required_str = '' if 'invisible' == spec['Editor']: continue elif 'input' == spec['Editor']: if spec['Type'] == 'int': input_str = """ <input class='fillwidth padspace' type='number' name='exe-%s' size='%d' value='%s' min=0 pattern='[0-9]+' %s /> """ % (field, field_size(conf['all_exes'][field]), conf['all_exes'][field], required_str) else: input_str = """ <input class='fillwidth padspace' type='text' name='exe-%s' size='%d' value='%s' %s /> """ % (field, field_size(conf['all_exes'][field]), conf['all_exes'][field], required_str) output_objects.append({ 'object_type': 'html_form', 'text': """<br /> <b>%s:</b> <a class='infolink iconspace' href='resedithelp.py#exe-%s'>help</a> <br /> %s <br /> <br />""" % (title, field, input_str) }) elif 'select' == spec['Editor']: choices = available_choices(configuration, client_id, resource_id, field, spec) exe_value = conf['all_exes'][field] value_select = '' if field_type.startswith('multiple'): select_count = len(exe_value) + extra_selects else: select_count = 1 exe_value = [exe_value] for i in range(select_count): value_select += "<select name='exe-%s'>\n" % field for name in choices: selected = '' if i < len(exe_value) and exe_value[i] == name: selected = 'selected' display = "%s" % name if display == '': display = ' ' value_select += """<option %s value='%s'>%s</option>\n""" \ % (selected, name, display) value_select += """</select><br />\n""" output_objects.append({ 'object_type': 'html_form', 'text': """<br /> <b>%s:</b> <a class='infolink iconspace' href='resedithelp.py#exe-%s'>help</a><br /> %s <br />""" % (title, field, value_select) }) # Storage node fields output_objects.append({ 'object_type': 'sectionheader', 'text': "Storage nodes" }) output_objects.append({ 'object_type': 'text', 'text': """This section configures storage nodes on the resource.""" }) (field, title) = 'storagenodes', 'Storage Node(s)' output_objects.append({ 'object_type': 'html_form', 'text': """<br /> <b>%s:</b> <a class='infolink iconspace' href='resedithelp.py#store-%s'>help</a><br /> <input class='fillwidth padspace' type='text' name='store-%s' size='%d' value='%s' /> <br /> <br />""" % (title, field, field, field_size( conf['all_stores'][field]), conf['all_stores'][field]) }) (field, title) = 'storagehome', 'Storage Home Path' output_objects.append({ 'object_type': 'html_form', 'text': """<br /> <b>%s:</b> <a class='infolink iconspace' href='resedithelp.py#store-%s'>help</a><br /> <input class='fillwidth padspace' type='text' name='store-%s' size='%d' value='%s' /> <br /> <br />""" % (title, field, field, field_size( conf['all_stores'][field]), conf['all_stores'][field]) }) for (field, spec) in store_fields: title = spec['Title'] field_type = spec['Type'] # We don't really have a good map of required fields here so disable required_str = '' if 'invisible' == spec['Editor']: continue elif 'input' == spec['Editor']: if spec['Type'] == 'int': input_str = """ <input class='fillwidth padspace' type='number' name='store-%s' size='%d' value='%s' min=0 pattern='[0-9]+' %s /> """ % (field, field_size(conf['all_stores'][field]), conf['all_stores'][field], required_str) else: input_str = """ <input class='fillwidth padspace' type='text' name='store-%s' size='%d' value='%s' %s /> """ % (field, field_size(conf['all_stores'][field]), conf['all_stores'][field], required_str) output_objects.append({ 'object_type': 'html_form', 'text': """<br /> <b>%s:</b> <a class='infolink iconspace' href='resedithelp.py#store-%s'>help</a> <br /> %s <br /> <br />""" % (title, field, input_str) }) elif 'select' == spec['Editor']: choices = available_choices(configuration, client_id, resource_id, field, spec) store_value = conf['all_stores'][field] value_select = '' if field_type.startswith('multiple'): select_count = len(store_value) + extra_selects else: select_count = 1 store_value = [store_value] for i in range(select_count): value_select += "<select name='store-%s'>\n" % field for name in choices: selected = '' if i < len(store_value) and store_value[i] == name: selected = 'selected' display = "%s" % name if display == '': display = ' ' value_select += """<option %s value='%s'>%s</option>\n""" \ % (selected, name, display) value_select += """</select><br />\n""" output_objects.append({ 'object_type': 'html_form', 'text': """<br /> <b>%s:</b> <a class='infolink iconspace' href='resedithelp.py#store-%s'>help</a><br /> %s <br />""" % (title, field, value_select) }) output_objects.append({ 'object_type': 'html_form', 'text': """ <input type='submit' value='Save' /> </form> """ }) 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, op_header=False) defaults = signature()[1] title_entry = find_entry(output_objects, 'title') label = "%s" % configuration.site_vgrid_label title_entry['text'] = '%s send request' % configuration.short_title output_objects.append({'object_type': 'header', 'text': 'Send request'}) (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': 'warning', 'text': '''Remember that sending a membership or ownership request generates a message to the owners of the target. All requests are logged together with the ID of the submitter. Spamming and other abuse will not be tolerated!''' }) output_objects.append({ 'object_type': 'sectionheader', 'text': 'Request %s membership/ownership' % label }) form_method = 'post' csrf_limit = get_csrf_limit(configuration) fill_helpers = { 'vgrid_label': label, 'form_method': form_method, 'csrf_field': csrf_field, 'csrf_limit': csrf_limit } 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': """ <form method='%(form_method)s' action='%(target_op)s.py'> <input type='hidden' name='%(csrf_field)s' value='%(csrf_token)s' /> <table align='center'> <tr><td>Request type</td><td><select name=request_type> <option value=vgridmember>%(vgrid_label)s membership</option> <option value=vgridowner>%(vgrid_label)s ownership</option> </select></td></tr> <tr><td> %(vgrid_label)s name </td><td><input name=vgrid_name /> </td></tr> <tr> <td>Reason (text to owners)</td><td><input name=request_text size=40 /></td> </tr> <tr><td><input type='submit' value='Submit' /></td><td></td></tr></table> </form>""" % fill_helpers }) output_objects.append({ 'object_type': 'sectionheader', 'text': 'Request resource ownership' }) 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' /> <table align='center'> <tr><td>Request type</td><td><select name=request_type> <option value=resourceowner>Resource ownership</option> </select></td></tr> <tr><td> Resource ID </td><td><input name=unique_resource_name /> </td></tr> <tr> <td>Reason (text to owners)</td><td><input name=request_text size=40 /></td> </tr> <tr><td><input type='submit' value='Submit' /></td><td></td></tr></table> </form>""" % fill_helpers }) output_objects.append({ 'object_type': 'sectionheader', 'text': 'Send message' }) protocol_options = '' for proto in [any_protocol] + configuration.notify_protocols: protocol_options += '<option value=%s>%s</option>\n' % (proto, proto) fill_helpers['protocol_options'] = protocol_options 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' /> <table align='center'> <tr><td>Request type</td><td><select name=request_type> <option value=plain>Plain message</option> </select></td></tr> <tr><td> User ID </td><td><input name=cert_id size=50 /> </td></tr> <tr><td>Protocol</td><td><select name=protocol> %(protocol_options)s </select></td></tr> <tr> <td>Message</td> <td><textarea name=request_text cols=72 rows=10 /></textarea></td> </tr> <tr><td><input type='submit' value='Send' /></td><td></td></tr></table> </form>""" % fill_helpers }) return (output_objects, returnvalues.OK)