def format_type_error( keyword, msg, keyword_dict, keyword_data, ): """Format type check errors for safe(!) and pretty printing. It is essential that raw user input is never printed unescaped as it could lead to security hazards. """ out = '''<table border=1> <tr><td>Keyword</td><td>%(keyword)s</td></tr> <tr><td>Error</td><td>%(msg)s</td></tr> <tr><td>You supplied</td><td>%(safe_data)s</td></tr> <tr><td>Keyword example</td><td>%(example)s</td></tr> <tr><td>Keyword description</td><td>%(description)s</td></tr> </table><br /> <!-- ****** INFORMATION ABOUT THE TYPE ERROR IN TEXT FORMAT ****** Keyword: %(keyword)s Error: %(msg)s You supplied: %(safe_data)s Keyword example: %(example)s Keyword description: %(description)s ******************************************************************* --> ''' % { 'keyword': html_escape(keyword), 'msg': html_escape(msg), 'safe_data': ' '.join([html_escape(i) for i in keyword_data]), 'example': keyword_dict['Example'].replace('\n', '<br/>'), 'description': keyword_dict['Description'] } return out
def operation_value_checker(operation_value): """ Validate that the provided workflow operation is allowed. A ValueError Exception will be raised if operation_value is invalid. :param operation_value: The operation to be checked. Valid operations are: 'create', 'read', 'update' and 'delete'. :return: No return. """ if operation_value not in VALID_OPERATIONS: raise ValueError("Workflow operation '%s' is not valid" % html_escape(operation_value))
def type_value_checker(type_value): """ Validate that the provided job type is allowed. A ValueError Exception will be raised if type_value is invalid. :param type_value: The type to be checked. Valid types are 'job', and 'queue' :return: No return """ valid_types = JOB_TYPES if type_value not in valid_types: raise ValueError("Workflow type '%s' is not valid. " % html_escape(type_value))
def type_value_checker(type_value): """ Validate that the provided workflow type is allowed. A ValueError Exception will be raised if type_value is invalid. :param type_value: The type to be checked. Valid types are 'workflowpattern', 'workflowrecipe', 'any', and 'manual_trigger'. :return: No return """ valid_types = WORKFLOW_TYPES + WORKFLOW_ACTION_TYPES +\ WORKFLOW_SEARCH_TYPES if type_value not in valid_types: raise ValueError("Workflow type '%s' is not valid" % html_escape(valid_types))
def stub(configuration, client_id, import_path, backend, user_arguments_dict, environ): """Run backend on behalf of client_id with supplied user_arguments_dict. I.e. import main from import_path and execute it with supplied arguments. """ _logger = configuration.logger _addr = environ.get('REMOTE_ADDR', 'UNKNOWN') before_time = time.time() output_objects = [] main = dummy_main # IMPORTANT: we cannot trust potentially user-provided backend value. # NEVER print/output it verbatim before it is validated below. try: valid_backend_name(backend) # Import main from backend module exec 'from %s import main' % import_path except Exception, err: _logger.error("%s could not import %s: %s" % (_addr, import_path, err)) bailout_helper(configuration, backend, output_objects) output_objects.extend([{ 'object_type': 'error_text', 'text': 'Could not load backend: %s' % html_escape(backend) }, { 'object_type': 'link', 'text': 'Go to default interface', 'destination': configuration.site_landing_page }]) return (output_objects, returnvalues.SYSTEM_ERROR)
def parse( localfile_spaces, job_id, client_id, forceddestination, outfile='AUTOMATIC', ): """Parse job description and optionally write results to parsed mRSL file. If outfile is non-empty it is used as destination file, and the keyword AUTOMATIC is replaced by the default mrsl dir destination. """ configuration = get_configuration_object() logger = configuration.logger client_dir = client_id_dir(client_id) # return a tuple (bool status, str msg). This is done because cgi-scripts # are not allowed to print anything before 'the first two special lines' # are printed result = parser.parse(localfile_spaces) external_dict = mrslkeywords.get_keywords_dict(configuration) # The mRSL has the right structure check if the types are correct too # and inline update the default external_dict entries with the ones # from the actual job specification (status, msg) = parser.check_types(result, external_dict, configuration) if not status: return (False, 'Parse failed (typecheck) %s' % msg) logger.debug('check_types updated job dict to: %s' % external_dict) global_dict = {} # Insert the parts from mrslkeywords we need in the rest of the MiG system for (key, value_dict) in external_dict.iteritems(): global_dict[key] = value_dict['Value'] # We do not expand any job variables yet in order to allow any future # resubmits to properly expand job ID. vgrid_list = global_dict['VGRID'] vgrid_access = user_vgrid_access(configuration, client_id) # Replace any_vgrid keyword with all allowed vgrids (on time of submit!) try: any_pos = vgrid_list.index(any_vgrid) vgrid_list[any_pos:any_pos] = vgrid_access # Remove any additional any_vgrid keywords while any_vgrid in vgrid_list: vgrid_list.remove(any_vgrid) except ValueError: # No any_vgrid keywords in list - move along pass # Now validate supplied vgrids for vgrid_name in vgrid_list: if not vgrid_name in vgrid_access: return (False, """Failure: You must be an owner or member of the '%s' vgrid to submit a job to it!""" % vgrid_name) # Fall back to default vgrid if no vgrid was supplied if not vgrid_list: # Please note that vgrid_list is a ref to global_dict list # so we must modify and not replace with a new list! vgrid_list.append(default_vgrid) # convert specified runtime environments to upper-case and verify they # actually exist # do not check runtime envs if the job is for ARC (submission will # fail later) if global_dict.get('JOBTYPE', 'unset') != 'arc' \ and global_dict.has_key('RUNTIMEENVIRONMENT'): re_entries_uppercase = [] for specified_re in global_dict['RUNTIMEENVIRONMENT']: specified_re = specified_re.upper() re_entries_uppercase.append(specified_re) if not is_runtime_environment(specified_re, configuration): return (False, """You have specified a non-nexisting runtime environment '%s', therefore the job can not be run on any resources.""" % \ specified_re) if global_dict.get('MOUNT', []) != []: if configuration.res_default_mount_re.upper()\ not in re_entries_uppercase: re_entries_uppercase.append( configuration.res_default_mount_re.upper()) global_dict['RUNTIMEENVIRONMENT'] = re_entries_uppercase if global_dict.get('JOBTYPE', 'unset').lower() == 'interactive': # if jobtype is interactive append command to create the notification # file .interactivejobfinished that breaks the infinite loop waiting # for the interactive job to finish and send output files to the MiG # server global_dict['EXECUTE'].append('touch .interactivejobfinished') # put job id and name of user in the dictionary global_dict['JOB_ID'] = job_id global_dict['USER_CERT'] = client_id # mark job as received global_dict['RECEIVED_TIMESTAMP'] = time.gmtime() global_dict['STATUS'] = 'PARSE' if forceddestination: global_dict['FORCEDDESTINATION'] = forceddestination if forceddestination.has_key('UNIQUE_RESOURCE_NAME'): global_dict["RESOURCE"] = "%(UNIQUE_RESOURCE_NAME)s_*" % \ forceddestination if forceddestination.has_key('RE_NAME'): re_name = forceddestination['RE_NAME'] # verify the verifyfiles entries are not modified (otherwise RE creator # can specify multiple ::VERIFYFILES:: keywords and give the entries # other names (perhaps overwriting files in the home directories of # resource owners executing the testprocedure) for verifyfile in global_dict['VERIFYFILES']: verifytypes = ['.status', '.stderr', '.stdout'] found = False for verifytype in verifytypes: if verifyfile == 'verify_runtime_env_%s%s' % (re_name, verifytype): found = True if not found: return (False, '''You are not allowed to specify the ::VERIFY:: keyword in a testprocedure, it is done automatically''') # normalize any path fields to be taken relative to home for field in ('INPUTFILES', 'OUTPUTFILES', 'EXECUTABLES', 'VERIFYFILES'): if not global_dict.has_key(field): continue normalized_field = [] for line in global_dict[field]: normalized_parts = [] line_parts = line.split(src_dst_sep) if len(line_parts) < 1 or len(line_parts) > 2: return (False, '%s entries must contain 1 or 2 space-separated items'\ % field) for part in line_parts: # deny leading slashes i.e. force absolute to relative paths part = part.lstrip('/') if part.find('://') != -1: # keep external targets as is - normpath breaks '://' normalized_parts.append(part) check_path = part.split('/')[-1] else: # normalize path to avoid e.g. './' which breaks dir # handling on resource check_path = os.path.normpath(part) normalized_parts.append(check_path) try: valid_path(check_path) except Exception, exc: return (False, 'Invalid %s part in %s: %s' % \ (field, html_escape(part), exc)) normalized_field.append(' '.join(normalized_parts)) global_dict[field] = normalized_field
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': 'Welcome to the %s OpenID account request page' % 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': '' } 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)) # 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}) fill_helpers.update(user_fields) html = """ <p class="sub-title">Please enter your information in at least the <span>mandatory</span> fields below and press the Send button to submit the OpenID account request to the %(site)s administrators.</p> %(site_signup_hint)s <p class='criticaltext highlight_message'> IMPORTANT: Please help us verify your identity by providing Organization and Email data that we can easily validate! </p> <hr /> """ user_country = user_fields.get('country', '') html += account_request_template(configuration, default_country=user_country) # 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><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, 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) title_entry = find_entry(output_objects, 'title') title_entry['text'] = '%s certificate request' % configuration.short_title title_entry['skipmenu'] = True form_fields = ['full_name', 'organization', 'email', 'country', 'state', 'password', 'verifypassword', 'comment'] title_entry['style'] = themed_styles(configuration) title_entry['javascript'] = cert_js_helpers(form_fields) 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' : 'Welcome to the %s certificate request page' % \ configuration.short_title} output_objects.append(header_entry) # 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': ''} 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 certificate you can still request one below:' % \ configuration.short_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 a valid %s certificate, but if it is about to expire you can renew it by posting the form below. Renewal with changed fields is <span class=mandatory>not</span> supported, so all fields including your original password must remain unchanged for renew to work. Otherwise it results in a request for a new account and certificate without access to your old files, jobs and privileges.</p>''' % \ configuration.short_title}) user_fields.update(distinguished_name_to_user(client_id)) user_fields.update({ 'valid_name_chars': html_escape(valid_name_chars), 'valid_password_chars': html_escape(valid_password_chars), 'password_min_len': password_min_len, 'password_max_len': password_max_len, 'site': configuration.short_title }) output_objects.append({'object_type': 'html_form', 'text' : """ Please enter your information in at least the <span class=mandatory>mandatory</span> fields below and press the Send button to submit the certificate request to the %(site)s administrators. <p class='criticaltext highlight_message'> IMPORTANT: Please help us verify your identity by providing Organization and Email data that we can easily validate!<br /> That is, if You're a student/employee at KU, please enter institute acronym (NBI, DIKU, etc.) in the Organization field and use your corresponding [email protected] or USER@*.ku.dk address in the Email field. </p> <hr /> <div class=form_container> <!-- use post here to avoid field contents in URL --> <form method=post action=reqcertaction.py onSubmit='return validate_form();'> <table> <tr><td class='mandatory label'>Full name</td><td><input id='full_name_field' type=text name=cert_name value='%(full_name)s' /></td><td class=fill_space><br /></td></tr> <tr><td class='mandatory label'>Email address</td><td><input id='email_field' type=text name=email value='%(email)s' /> </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' /></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 maxlength=2 value='%(country)s' /></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' /> </td><td class=fill_space><br /></td></tr> <tr><td class='mandatory label'>Password</td><td><input id='password_field' type=password name=password maxlength=%(password_max_len)s value='%(password)s' /> </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 maxlength=%(password_max_len)s value='%(verifypassword)s' /></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 certificate:</td><td><textarea id='comment_field' rows=4 name=comment></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> </div> <hr /> <br /> <div class='warn_message'>Please note that passwords may be accessible to the %(site)s administrators!</div> <br /> <!-- Hidden help text --> <div id='help_text'> <div id='full_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='http://www.iso.org/iso/country_codes/iso_3166_code_lists/country_names_and_code_elements.html'>help</a></div> <div id='state_help'>Optional state of your organization, please just leave empty unless it is in the US or similar</div> <div id='password_help'>Password is restricted to the characters in '%(valid_password_chars)s and must be %(password_min_len)s to %(password_max_len)s characters long'</div> <div id='verifypassword_help'>Please repeat password</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> """ % user_fields}) return (output_objects, returnvalues.OK)
- 1)] elif 'list' == kind: raw = user_arguments_dict[name] else: error = 'unknown kind %s for %s in parse_argument!' % (kind, name) return ('', '', error) try: check(raw) except ValueError, verr: error = 'invalid contents of %s in parse_argument! (%s)'\ % (name, verr) if 'list' == kind: safe = [html_escape(item) for item in raw] else: safe = html_escape(raw) return (raw, safe, error) def fieldstorage_to_dict(fieldstorage, fields=[]): """Get a plain dictionary, rather than the '.value' system used by the cgi module. Please note that all values are on list form even if only a single value is provided. If the fields list is provided, only the provided fields are read. This may be necessary in PUT requests where fieldstorage.keys() is not supported. """ params = {} if not fields:
key_fh.close() except: public_key_file_content = None # Avoid line breaks in displayed key if public_key_file_content: public_key_info = ''' The public key you must add:<br /> ***BEGIN KEY***<br /> %s<br /> ***END KEY***<br /> <br />''' % public_key_file_content.replace(' ', ' ') else: public_key_info = '''<br /> Please request an SSH public key from the %s administrator(s) (%s)<br /> <br />''' % (configuration.short_title, html_escape(configuration.admin_email)) output += """ Please make sure the %s server can SSH to your resource without a passphrase. The %s server's public key should be in ~/.ssh/authorized_keys for the MiG user on the resource frontend. %s <br /> Also, please note that %s resources require the curl command line tool from <a href='http://www.curl.haxx.se'>cURL</a>. <br /> <a href='resadmin.py'>View existing resources</a> where your new resource will also eventually show up. """ % (configuration.short_title, configuration.short_title, public_key_info, configuration.short_title) output_objects.append({'object_type': 'html_form', 'text': output})
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) # Please note that base_dir must end in slash to avoid access to other # user dirs when own name is a prefix of another user name base_dir = os.path.abspath(os.path.join(configuration.user_home, client_dir)) + os.sep title_entry = find_entry(output_objects, 'title') title_entry['text'] = 'Settings' # prepare support for toggling the views (by css/jquery) title_entry['style'] = themed_styles(configuration) title_entry['style']['skin'] += ''' %s ''' % cm_css title_entry['javascript'] = ''' <script type="text/javascript" src="/images/js/jquery.js"></script> <script type="text/javascript" src="/images/js/jquery-ui.js"></script> %s <script type="text/javascript" > var toggleHidden = function(classname) { // classname supposed to have a leading dot $(classname).toggleClass("hidden"); } $(document).ready(function() { } ); </script> ''' % cm_javascript valid_topics = ['general', 'style'] active_menu = extract_menu(configuration, title_entry) if 'submitjob' in active_menu: valid_topics.append('job') if 'people' in active_menu: valid_topics.append('profile') if configuration.site_script_deps: valid_topics.append('widgets') if configuration.arc_clusters: valid_topics.append('arc') if configuration.site_enable_sftp: valid_topics.append('sftp') if configuration.site_enable_davs: valid_topics.append('webdavs') if configuration.site_enable_ftps: valid_topics.append('ftps') topics = accepted['topic'] # Backwards compatibility if topics and topics[0] == 'ssh': topics[0] = 'sftp' topics = [i for i in topics if i in valid_topics] # Default to general if no valid topics given if not topics: topics.append(valid_topics[0]) topic_titles = dict([(i, i.title()) for i in valid_topics]) for (key, val) in [('sftp', 'SFTP'), ('webdavs', 'WebDAVS'), ('ftps', 'FTPS')]: if key in valid_topics: topic_titles[key] = val output_objects.append({'object_type': 'header', 'text' : 'Settings'}) links = [] for name in valid_topics: active_menu = '' if topics[0] == name: active_menu = 'activebutton' links.append({'object_type': 'link', 'destination': "settings.py?topic=%s" % name, 'class': '%ssettingslink settingsbutton %s' % \ (name, active_menu), 'title': 'Switch to %s settings' % topic_titles[name], 'text' : '%s' % topic_titles[name], }) output_objects.append({'object_type': 'multilinkline', 'links': links, 'sep': ' '}) output_objects.append({'object_type': 'text', 'text': ''}) # load current settings current_settings_dict = load_settings(client_id, configuration) if not current_settings_dict: # no current settings found current_settings_dict = {} if not topics: output_objects.append({'object_type': 'error_text', 'text': 'No valid topics!'}) return (output_objects, returnvalues.CLIENT_ERROR) if 'general' in topics: html = \ ''' <div id="settings"> <form method="post" action="settingsaction.py"> <table class="settings fixedlayout"> <tr class="title"><td class="centertext"> Select your %s settings </td></tr> <tr><td> </td></tr> <tr><td> <input type="hidden" name="topic" value="general" /> Please note that if you want to set multiple values (e.g. addresses) in the same field, you must write each value on a separate line but without blank lines. </td></tr> <tr><td> </td></tr> ''' % configuration.short_title settings_entries = get_settings_specs() for (keyword, val) in settings_entries: if 'SUBMITUI' == keyword and \ 'job' not in valid_topics: continue if 'notify' == val['Context'] and \ keyword.lower() not in configuration.notify_protocols: continue entry = \ """ <tr class='title'><td> %s </td></tr> <tr><td> %s </td></tr> <tr><td> """\ % (keyword.replace('_', ' ').title(), val['Description']) if val['Type'] == 'multiplestrings': try: # get valid choices from conf. multiple selections valid_choices = eval('configuration.%s' % keyword.lower()) current_choice = [] if current_settings_dict.has_key(keyword): current_choice = current_settings_dict[keyword] if len(valid_choices) > 0: entry += '<div class="scrollselect">' for choice in valid_choices: selected = '' if choice in current_choice: selected = 'checked' entry += ''' <input type="checkbox" name="%s" %s value="%s">%s<br />''' % \ (keyword, selected, choice, choice) entry += '</div>' else: entry = '' except: # failed on evaluating configuration.%s area = ''' <textarea id="%s" cols=40 rows=1 name="%s">''' % \ (keyword, keyword) if current_settings_dict.has_key(keyword): area += '\n'.join(current_settings_dict[keyword]) area += '</textarea>' entry += wrap_edit_area(keyword, area, general_edit, 'BASIC') elif val['Type'] == 'string': # get valid choices from conf valid_choices = eval('configuration.%s' % keyword.lower()) current_choice = '' if current_settings_dict.has_key(keyword): current_choice = current_settings_dict[keyword] if len(valid_choices) > 0: entry += '<select name="%s">' % keyword for choice in valid_choices: selected = '' if choice == current_choice: selected = 'selected' entry += '<option %s value="%s">%s</option>'\ % (selected, choice, choice) entry += '</select><br />' else: entry = '' elif val['Type'] == 'boolean': current_choice = '' if current_settings_dict.has_key(keyword): current_choice = current_settings_dict[keyword] entry += '<select name="%s">' % keyword for choice in (True, False): selected = '' if choice == current_choice: selected = 'selected' entry += '<option %s value="%s">%s</option>'\ % (selected, choice, choice) entry += '</select><br />' html += """%s </td></tr> """ % entry html += \ """ <tr><td> <input type="submit" value="Save General Settings" /> </td></tr> </table> </form> </div> """ output_objects.append({'object_type': 'html_form', 'text': html}) if 'job' in topics: mrsl_path = os.path.join(base_dir, default_mrsl_filename) default_mrsl = get_default_mrsl(mrsl_path) html = \ ''' <div id="defaultmrsl"> <form method="post" action="editfile.py"> <table class="defaultjob fixedlayout"> <tr class="title"><td class="centertext"> Default job on submit page </td></tr> <tr><td> </td></tr> <tr><td> If you use the same fields and values in many of your jobs, you can save your preferred job description here to always start out with that description on your submit job page. </td></tr> <tr><td> </td></tr> <tr><td> <input type="hidden" name="path" value="%(mrsl_template)s" /> <input type="hidden" name="newline" value="unix" /> ''' keyword = "defaultjob" area = ''' <textarea id="%(keyword)s" cols=82 rows=25 name="editarea"> %(default_mrsl)s </textarea> ''' html += wrap_edit_area(keyword, area, cm_options, 'BASIC') html += ''' </td></tr> <tr><td> <input type="submit" value="Save Job Template" /> </td></tr> </table> </form> </div> ''' html = html % { 'default_mrsl': default_mrsl, 'mrsl_template': default_mrsl_filename, 'site': configuration.short_title, 'keyword': keyword } output_objects.append({'object_type': 'html_form', 'text': html}) if 'style' in topics: css_path = os.path.join(base_dir, default_css_filename) default_css = get_default_css(css_path) html = \ ''' <div id="defaultcss"> <form method="post" action="editfile.py"> <table class="defaultstyle fixedlayout"> <tr class="title"><td class="centertext"> Default CSS (style) for all pages </td></tr> <tr><td> </td></tr> <tr><td> If you want to customize the look and feel of the %(site)s web interfaces you can override default values here. If you leave the style file blank you will just use the default style.<br /> You can copy paste from the available style file links below if you want to override specific parts.<br /> <div class="warningtext">Please note that you can not save an empty style file, but must at least leave a blank line to use defaults. Additionally some errors in your style code may potentially cause severe corruption in your page layouts, so it may be a good idea to keep another browser tab/window ready to (re)move your .default.css file to restore the defaults while experimenting here. </div> </td></tr> <tr><td> <a class="urllink" href="/images/default.css">default</a> , <a class="urllink" href="/images/bluesky.css">bluesky</a> </td></tr> <tr><td> </td></tr> <tr><td> <input type="hidden" name="path" value="%(css_template)s" /> <input type="hidden" name="newline" value="unix" /> ''' keyword = "defaultstyle" area = ''' <textarea id="%(keyword)s" cols=82 rows=25 min_len=1 name="editarea"> %(default_css)s </textarea> ''' html += wrap_edit_area(keyword, area, style_edit) html += ''' </td></tr> <tr><td> <input type="submit" value="Save Style Settings" /> </td></tr> </table> </form> </div> ''' html = html % { 'default_css': default_css, 'css_template': default_css_filename, 'site': configuration.short_title, 'keyword': keyword } output_objects.append({'object_type': 'html_form', 'text': html}) if 'widgets' in topics: # load current widgets current_widgets_dict = load_widgets(client_id, configuration) if not current_widgets_dict: # no current widgets found current_widgets_dict = {} show_widgets = current_settings_dict.get('ENABLE_WIDGETS', True) if show_widgets: edit_widgets = '''You can simply copy/paste from the available widget file links below if you want to reuse existing widgets.<br /> </td></tr> <tr><td> <a class="urllink" href="/images/widgets/hello-grid.app">hello grid</a>, <a class="urllink" href="/images/widgets/simple-calendar.app">simple calendar</a>, <a class="urllink" href="/images/widgets/calendar.app">calendar</a>, <a class="urllink" href="/images/widgets/gcal.app">google calendar</a>, <a class="urllink" href="/images/widgets/calculator.app">calculator</a>, <a class="urllink" href="/images/widgets/localrss.app">local rss reader</a>, <a class="urllink" href="/images/widgets/rss.app">rss reader</a>, <a class="urllink" href="/images/widgets/clock.app">clock</a>, <a class="urllink" href="/images/widgets/weather.app">weather</a>, <a class="urllink" href="/images/widgets/progressbar.app">progress bar</a>, <a class="urllink" href="/images/widgets/simple-move.app">simple-move</a>, <a class="urllink" href="/images/widgets/portlets.app">portlets</a>, <a class="urllink" href="/images/widgets/countdown.app">countdown</a>, <a class="urllink" href="/images/widgets/sparkline.app">mini chart</a>, <a class="urllink" href="/images/widgets/piechart.app">pie chart</a>, <a class="urllink" href="/images/widgets/simple-jobmon.app">simple-jobmon</a>, <a class="urllink" href="/images/widgets/cert-countdown.app">certificate countdown</a>, <a class="urllink" href="/images/widgets/disk-use.app">disk use progress bar</a>, <a class="urllink" href="/images/widgets/jobs-stats.app">jobs stats table</a>, <a class="urllink" href="/images/widgets/jobs-stats-chart.app">jobs stats chart</a>, <a class="urllink" href="/images/widgets/daily-wm-comic.app">Daily WulffMorgenthaler comic</a>, <a class="urllink" href="/images/widgets/kunet-login.app">KUnet login</a> <a class="urllink" href="/images/widgets/tdchotspot-login.app">TDC Hotspot login</a> </td></tr> <tr><td> <div class="warningtext">Please note that the widgets parser is rather grumpy so you may have to avoid blank lines in your widget code below. Additionally any errors in your widgets code may cause severe corruption in your pages, so it may be a good idea to keep another browser tab/window ready for emergency disabling of widgets while experimenting here.</div> </td></tr> <tr><td> <input type="hidden" name="topic" value="widgets" /> </td></tr> <tr><td> ''' html = \ '''<div id="widgets"> <form method="post" action="settingsaction.py"> <table class="widgets fixedlayout"> <tr class="title"><td class="centertext"> Default user defined widgets for all pages </td></tr> <tr><td> </td></tr> <tr><td> If you want to customize the look and feel of the %s web interfaces you can add your own widgets here. If you leave the widgets blank you will just get the default empty widget spaces.<br /> ''' % configuration.short_title widgets_entries = get_widgets_specs() widgets_html = '' for (keyword, val) in widgets_entries: widgets_html += \ """ <tr class=title><td> %s </td></tr> <tr><td> %s </td></tr> <tr><td> """\ % (keyword.replace('_', ' ').title(), val['Description']) if val['Type'] == 'multiplestrings': try: # get valid choices from conf. multiple selections valid_choices = eval('configuration.%s' % keyword.lower()) current_choice = [] if current_widgets_dict.has_key(keyword): current_choice = current_widgets_dict[keyword] if len(valid_choices) > 0: widgets_html += '<div class="scrollselect">' for choice in valid_choices: selected = '' if choice in current_choice: selected = 'checked' widgets_html += ''' <input type="checkbox" name="%s" %s value="%s">%s<br />'''\ % (keyword, selected, choice, choice) widgets_html += '</div>' except: area = \ """<textarea id='%s' cols=78 rows=10 name='%s'>""" % \ (keyword, keyword) if current_widgets_dict.has_key(keyword): area += '\n'.join(current_widgets_dict[keyword]) area += '</textarea>' widgets_html += wrap_edit_area(keyword, area, widgets_edit) if show_widgets: edit_widgets += ''' %s <tr><td> <input type="submit" value="Save Widgets Settings" /> </td></tr> ''' % widgets_html else: edit_widgets = ''' <br/> <div class="warningtext"> Widgets are disabled on your <em>General</em> settings page. Please enable them there first if you want to customize your grid pages. </div> ''' html += \ ''' %s </table> </form> </div> ''' % edit_widgets output_objects.append({'object_type': 'html_form', 'text': html}) if 'profile' in topics: # load current profile current_profile_dict = load_profile(client_id, configuration) if not current_profile_dict: # no current profile found current_profile_dict = {} (got_list, all_vgrids) = vgrid_list_vgrids(configuration) if not got_list: all_vgrids = [] all_vgrids.append(any_vgrid) all_vgrids.sort() configuration.vgrids_allow_email = all_vgrids configuration.vgrids_allow_im = all_vgrids images = [] for path in os.listdir(base_dir): real_path = os.path.join(base_dir, path) if os.path.splitext(path)[1].strip('.') in profile_img_extensions \ and os.path.getsize(real_path) < profile_img_max_kb*1024: images.append(path) configuration.public_image = images html = \ ''' <div id="profile"> <form method="post" action="settingsaction.py"> <table class="profile fixedlayout"> <tr class="title"><td class="centertext"> Public profile information visible to other users. </td></tr> <tr><td> </td></tr> <tr><td> If you want to let other users know more about you can add your own text here. If you leave the text area blank you will just get the default empty profile information.<br /> </td></tr> <tr><td> <div class="warningtext">Please note that the profile parser is rather grumpy so you may have to avoid blank lines in your text below. </div> </td></tr> <tr><td> <input type="hidden" name="topic" value="profile" /> </td></tr> <tr><td> ''' profile_entries = get_profile_specs() for (keyword, val) in profile_entries: # Mask VGrid name if configured mask_title = keyword.replace( 'VGRID', configuration.site_vgrid_label.upper()) mask_desc = val['Description'].replace( 'VGrid', configuration.site_vgrid_label) html += \ """ <tr class=title><td> %s </td></tr> <tr><td> %s </td></tr> <tr><td> """ % (mask_title.replace('_', ' ').title(), html_escape(mask_desc)) if val['Type'] == 'multiplestrings': try: # get valid choices from conf. multiple selections valid_choices = eval('configuration.%s' % keyword.lower()) current_choice = [] if current_profile_dict.has_key(keyword): current_choice = current_profile_dict[keyword] if len(valid_choices) > 0: html += '<div class="scrollselect">' for choice in valid_choices: selected = '' if choice in current_choice: selected = 'checked' html += ''' <input type="checkbox" name="%s" %s value="%s">%s<br />''' % \ (keyword, selected, choice, choice) html += '</div>' except: area = \ """<textarea id='%s' cols=78 rows=10 name='%s'>""" % \ (keyword, keyword) if current_profile_dict.has_key(keyword): area += '\n'.join(current_profile_dict[keyword]) area += '</textarea>' html += wrap_edit_area(keyword, area, profile_edit) elif val['Type'] == 'boolean': valid_choices = [True, False] current_choice = '' if current_profile_dict.has_key(keyword): current_choice = current_profile_dict[keyword] if len(valid_choices) > 0: html += '<select name="%s">' % keyword for choice in valid_choices: selected = '' if choice == current_choice: selected = 'selected' html += '<option %s value="%s">%s</option>'\ % (selected, choice, choice) html += '</select><br />' html += ''' <tr><td> <input type="submit" value="Save Profile Settings" /> </td></tr> </table> </form> </div> ''' output_objects.append({'object_type': 'html_form', 'text': html}) if 'sftp' in topics: # load current ssh/sftp current_ssh_dict = load_ssh(client_id, configuration) if not current_ssh_dict: # no current ssh found current_ssh_dict = {} default_authkeys = current_ssh_dict.get('authkeys', '') default_authpassword = current_ssh_dict.get('authpassword', '') username = client_alias(client_id) if configuration.user_sftp_alias: username = extract_field(client_id, configuration.user_sftp_alias) create_alias_link(username, client_id, configuration.user_home) sftp_server = configuration.user_sftp_show_address sftp_port = configuration.user_sftp_show_port html = \ ''' <div id="sshaccess"> <form method="post" action="settingsaction.py"> <table class="sshsettings fixedlayout"> <tr class="title"><td class="centertext"> SFTP access to your %(site)s account </td></tr> <tr><td> </td></tr> <tr><td> You can configure SFTP login to your %(site)s account for efficient file access. On Linux/UN*X it also allows transparent access through SSHFS. <br/> <h3>Login Details</h3> <ul> <li>Host <em>%(sftp_server)s</em></li> <li>Port <em>%(sftp_port)s</em></li> <li>Username <em>%(username)s</em></li> <li>%(auth_methods)s <em>as you choose below</em></li> </ul> </td></tr> <tr><td> <input type="hidden" name="topic" value="sftp" /> <div class="div-sftp-client-notes hidden"> <a href="javascript:toggleHidden('.div-sftp-client-notes');" class="removeitemlink" title="Toggle view"> Show less SFTP client details...</a> <h3>Graphical SFTP access</h3> The FireFTP plugin for Firefox is known to generally work for graphical access to your %(site)s home over SFTP. Enter the following values in the FireFTP Account Manager: <pre> Host %(sftp_server)s Login %(username)s Password YOUR_PASSWORD_HERE (passphrase if you configured public key access) Security SFTP Port %(sftp_port)s Private Key ~/.mig/key.pem (if you configured public key access) </pre> other graphical clients may work as well. <h3>Command line SFTP/SSHFS access on Linux/UN*X</h3> Save something like the following lines in your local ~/.ssh/config to avoid typing the full login details every time:<br /> <pre> Host %(sftp_server)s Hostname %(sftp_server)s User %(username)s Port %(sftp_port)s IdentityFile ~/.mig/key.pem </pre> From then on you can use sftp and sshfs to access your %(site)s home: <pre> sftp %(sftp_server)s </pre> <pre> sshfs %(sftp_server)s: mig-home -o uid=$(id -u) -o gid=$(id -g) </pre> You can also integrate with ordinary mounts by adding a line like: <pre> sshfs#%(username)s@%(sftp_server)s: /home/USER/mig-home fuse noauto,user,port=%(sftp_port)d 0 0 </pre> to your /etc/fstab . </div> <div class="div-sftp-client-notes"> <a href="javascript:toggleHidden('.div-sftp-client-notes');" class="additemlink" title="Toggle view">Show more SFTP client details... </a> </div> ''' keyword_keys = "authkeys" if 'publickey' in configuration.user_sftp_auth: html += ''' </td></tr> <tr><td> <h3>Authorized Public Keys</h3> You can use any existing RSA key, or create a new one. If you signed up with a x509 user certificate, you should also have received such a key.pem along with your user certificate. In any case you need to save the contents of the corresponding public key (X.pub) in the text area below, to be able to connect with username and key as described in the Login Details. <br/> ''' area = ''' <textarea id="%(keyword_keys)s" cols=82 rows=5 name="publickeys"> %(default_authkeys)s </textarea> ''' html += wrap_edit_area(keyword_keys, area, ssh_edit, 'BASIC') html += ''' (leave empty to disable sftp access with public keys) </td></tr> ''' keyword_password = "******" if 'password' in configuration.user_sftp_auth: # We only want a single password and a masked input field html += ''' <tr><td> <h3>Authorized Password</h3> Please enter and save your desired password in the text field below, to be able to connect with username and password as described in the Login Details. <br/> <input type=password id="%(keyword_password)s" size=40 name="password" value="%(default_authpassword)s" /> (leave empty to disable sftp access with password) </td></tr> ''' html += ''' <tr><td> <input type="submit" value="Save SFTP Settings" /> </td></tr> ''' html += ''' </table> </form> </div> ''' html = html % { 'default_authkeys': default_authkeys, 'default_authpassword': default_authpassword, 'site': configuration.short_title, 'keyword_keys': keyword_keys, 'keyword_password': keyword_password, 'username': username, 'sftp_server': sftp_server, 'sftp_port': sftp_port, 'auth_methods': ' / '.join(configuration.user_sftp_auth).title(), } output_objects.append({'object_type': 'html_form', 'text': html}) if 'webdavs' in topics: # load current davs current_davs_dict = load_davs(client_id, configuration) if not current_davs_dict: # no current davs found current_davs_dict = {} default_authkeys = current_davs_dict.get('authkeys', '') default_authpassword = current_davs_dict.get('authpassword', '') username = client_alias(client_id) if configuration.user_davs_alias: username = extract_field(client_id, configuration.user_davs_alias) create_alias_link(username, client_id, configuration.user_home) davs_server = configuration.user_davs_show_address davs_port = configuration.user_davs_show_port html = \ ''' <div id="davsaccess"> <form method="post" action="settingsaction.py"> <table class="davssettings fixedlayout"> <tr class="title"><td class="centertext"> WebDAVS access to your %(site)s account </td></tr> <tr><td> </td></tr> <tr><td> You can configure WebDAVS login to your %(site)s account for transparent file access from your PC or workstation.<br/> <h3>Login Details</h3> <ul> <li>Host <em>%(davs_server)s</em></li> <li>Port <em>%(davs_port)s</em></li> <li>Username <em>%(username)s</em></li> <li>%(auth_methods)s <em>as you choose below</em></li> </ul> </td></tr> <tr><td> <input type="hidden" name="topic" value="webdavs" /> <div class="div-webdavs-client-notes hidden"> <a href="javascript:toggleHidden('.div-webdavs-client-notes');" class="removeitemlink" title="Toggle view"> Show less WebDAVS client details...</a> <h3>Graphical WebDAVS access</h3> Several native file browsers and web browsers are known to generally work for graphical access to your %(site)s home over WebDAVS. <br /> Enter the address https://%(davs_server)s:%(davs_port)s and when fill in the login details: <pre> Username %(username)s Password YOUR_PASSWORD_HERE </pre> other graphical clients should work as well. <h3>Command line WebDAVS access on Linux/UN*X</h3> Save something like the following lines in your local ~/.netrc to avoid typing the full login details every time:<br /> <pre> machine %(davs_server)s login %(username)s password YOUR_PASSWORD_HERE </pre> From then on you can use e.g. cadaver or fusedav to access your %(site)s home: <pre> cadaver https://%(davs_server)s:%(davs_port)s </pre> <pre> fusedav https://%(davs_server)s:%(davs_port)s mig-home -o uid=$(id -u) -o gid=$(id -g) </pre> </div> <div class="div-webdavs-client-notes"> <a href="javascript:toggleHidden('.div-webdavs-client-notes');" class="additemlink" title="Toggle view"> Show more WebDAVS client details...</a> </div> ''' keyword_keys = "authkeys" if 'publickey' in configuration.user_davs_auth: html += ''' </td></tr> <tr><td> <h3>Authorized Public Keys</h3> You can use any existing RSA key, including the key.pem you received along with your user certificate, or create a new one. In any case you need to save the contents of the corresponding public key (X.pub) in the text area below, to be able to connect with username and key as described in the Login Details. <br/>''' area = ''' <textarea id="%(keyword_keys)s" cols=82 rows=5 name="publickeys"> %(default_authkeys)s </textarea> ''' html += wrap_edit_area(keyword_keys, area, davs_edit, 'BASIC') html += ''' (leave empty to disable davs access with public keys) </td></tr> ''' keyword_password = "******" if 'password' in configuration.user_davs_auth: # We only want a single password and a masked input field html += ''' <tr><td> <h3>Authorized Password</h3> Please enter and save your desired password in the text field below, to be able to connect with username and password as described in the Login Details. <br/> <input type=password id="%(keyword_password)s" size=40 name="password" value="%(default_authpassword)s" /> (leave empty to disable davs access with password) </td></tr> ''' html += ''' <tr><td> <input type="submit" value="Save WebDAVS Settings" /> </td></tr> ''' html += ''' </table> </form> </div> ''' html = html % { 'default_authkeys': default_authkeys, 'default_authpassword': default_authpassword, 'site': configuration.short_title, 'keyword_keys': keyword_keys, 'keyword_password': keyword_password, 'username': username, 'davs_server': davs_server, 'davs_port': davs_port, 'auth_methods': ' / '.join(configuration.user_davs_auth).title(), } output_objects.append({'object_type': 'html_form', 'text': html}) if 'ftps' in topics: # load current ftps current_ftps_dict = load_ftps(client_id, configuration) if not current_ftps_dict: # no current ftps found current_ftps_dict = {} default_authkeys = current_ftps_dict.get('authkeys', '') default_authpassword = current_ftps_dict.get('authpassword', '') username = client_alias(client_id) if configuration.user_ftps_alias: username = extract_field(client_id, configuration.user_ftps_alias) create_alias_link(username, client_id, configuration.user_home) ftps_server = configuration.user_ftps_show_address ftps_ctrl_port = configuration.user_ftps_show_ctrl_port html = \ ''' <div id="ftpsaccess"> <form method="post" action="settingsaction.py"> <table class="ftpssettings fixedlayout"> <tr class="title"><td class="centertext"> FTPS access to your %(site)s account </td></tr> <tr><td> </td></tr> <tr><td> You can configure FTPS login to your %(site)s account for efficient file access.<br/> <h3>Login Details</h3> <ul> <li>Host <em>%(ftps_server)s</em></li> <li>Port <em>%(ftps_ctrl_port)s</em></li> <li>Username <em>%(username)s</em></li> <li>%(auth_methods)s <em>as you choose below</em></li> </ul> </td></tr> <tr><td> <input type="hidden" name="topic" value="ftps" /> <div class="div-ftps-client-notes hidden"> <a href="javascript:toggleHidden('.div-ftps-client-notes');" class="removeitemlink" title="Toggle view"> Show less FTPS client details...</a> <h3>Graphical FTPS access</h3> The FireFTP plugin for Firefox is known to generally work for graphical access to your %(site)s home over FTPS. Enter the following values in the FireFTP Account Manager: <pre> Host %(ftps_server)s Login %(username)s Password YOUR_PASSWORD_HERE Security FTPS Port %(ftps_ctrl_port)s </pre> Other FTP clients and web browsers may work as well if you enter the address ftps://%(ftps_server)s:%(ftps_ctrl_port)s and fill in the login details when prompted: <pre> Username %(username)s Password YOUR_PASSWORD_HERE </pre> <h3>Command line FTPS access on Linux/UN*X</h3> Save something like the following lines in your local ~/.netrc to avoid typing the full login details every time:<br /> <pre> machine %(ftps_server)s login %(username)s password YOUR_PASSWORD_HERE </pre> From then on you can use e.g. lftp or CurlFtpFS to access your %(site)s home: <!-- TODO: we need to provide the intermediate cert for server cert check like this set ssl:ca-file sub.class1.server.ca.pem --> <pre> lftp -e "set ssl:verify-certificate no; set ftp:ssl-protect-data on" \\ -p %(ftps_ctrl_port)s %(ftps_server)s </pre> <pre> curlftpfs -o ssl %(ftps_server)s:%(ftps_ctrl_port)s mig-home \\ -o user=%(username)s -ouid=$(id -u) -o gid=$(id -g) -o no_verify_peer </pre> </div> <div class="div-ftps-client-notes"> <a href="javascript:toggleHidden('.div-ftps-client-notes');" class="additemlink" title="Toggle view">Show more FTPS client details... </a> </div> ''' keyword_keys = "authkeys" if 'publickey' in configuration.user_ftps_auth: html += ''' </td></tr> <tr><td> <h3>Authorized Public Keys</h3> You can use any existing RSA key, including the key.pem you received along with your user certificate, or create a new one. In any case you need to save the contents of the corresponding public key (X.pub) in the text area below, to be able to connect with username and key as described in the Login Details. <br/> ''' area = ''' <textarea id="%(keyword_keys)s" cols=82 rows=5 name="publickeys"> %(default_authkeys)s </textarea> ''' html += wrap_edit_area(keyword_keys, area, ftps_edit, 'BASIC') html += ''' (leave empty to disable ftps access with public keys) </td></tr> ''' keyword_password = "******" if 'password' in configuration.user_ftps_auth: # We only want a single password and a masked input field html += ''' <tr><td> <h3>Authorized Password</h3> Please enter and save your desired password in the text field below, to be able to connect with username and password as described in the Login Details. <br/> <input type=password id="%(keyword_password)s" size=40 name="password" value="%(default_authpassword)s" /> (leave empty to disable ftps access with password) </td></tr> ''' html += ''' <tr><td> <input type="submit" value="Save FTPS Settings" /> </td></tr> ''' html += ''' </table> </form> </div> ''' html = html % { 'default_authkeys': default_authkeys, 'default_authpassword': default_authpassword, 'site': configuration.short_title, 'keyword_keys': keyword_keys, 'keyword_password': keyword_password, 'username': username, 'ftps_server': ftps_server, 'ftps_ctrl_port': ftps_ctrl_port, 'auth_methods': ' / '.join(configuration.user_ftps_auth).title(), } output_objects.append({'object_type': 'html_form', 'text': html}) # if ARC-enabled server: if 'arc' in topics: # provide information about the available proxy, offer upload try: home_dir = os.path.normpath(base_dir) session_Ui = arc.Ui(home_dir, require_user_proxy=True) proxy = session_Ui.getProxy() if proxy.IsExpired(): # can rarely happen, constructor will throw exception output_objects.append({'object_type': 'text', 'text': 'Proxy certificate is expired.'}) else: output_objects.append({'object_type': 'text', 'text': 'Proxy for %s' \ % proxy.GetIdentitySN()}) output_objects.append( {'object_type': 'text', 'text': 'Proxy certificate will expire on %s (in %s sec.)' % (proxy.Expires(), proxy.getTimeleft()) }) except arc.NoProxyError, err: output_objects.append({'object_type':'warning', 'text': 'No proxy certificate to load: %s' \ % err.what()}) output_objects = output_objects + arc.askProxy()
def parse( localfile_spaces, job_id, client_id, forceddestination, outfile='AUTOMATIC', ): """Parse job description and optionally write results to parsed mRSL file. If outfile is non-empty it is used as destination file, and the keyword AUTOMATIC is replaced by the default mrsl dir destination. """ configuration = get_configuration_object() logger = configuration.logger client_dir = client_id_dir(client_id) # return a tuple (bool status, str msg). This is done because cgi-scripts # are not allowed to print anything before 'the first two special lines' # are printed result = parser.parse(localfile_spaces) external_dict = mrslkeywords.get_keywords_dict(configuration) # The mRSL has the right structure check if the types are correct too # and inline update the default external_dict entries with the ones # from the actual job specification (status, msg) = parser.check_types(result, external_dict, configuration) if not status: return (False, 'Parse failed (typecheck) %s' % msg) logger.debug('check_types updated job dict to: %s' % external_dict) global_dict = {} # Insert the parts from mrslkeywords we need in the rest of the MiG system for (key, value_dict) in external_dict.iteritems(): global_dict[key] = value_dict['Value'] # We do not expand any job variables yet in order to allow any future # resubmits to properly expand job ID. vgrid_list = global_dict['VGRID'] allowed_vgrids = user_allowed_vgrids(configuration, client_id) # Replace any_vgrid keyword with all allowed vgrids (on time of submit!) try: any_pos = vgrid_list.index(any_vgrid) vgrid_list[any_pos:any_pos] = allowed_vgrids # Remove any additional any_vgrid keywords while any_vgrid in vgrid_list: vgrid_list.remove(any_vgrid) except ValueError: # No any_vgrid keywords in list - move along pass # Now validate supplied vgrids for vgrid_name in vgrid_list: if not vgrid_name in allowed_vgrids: return (False, """Failure: You must be an owner or member of the '%s' vgrid to submit a job to it!""" % vgrid_name) # Fall back to default vgrid if no vgrid was supplied if not vgrid_list: # Please note that vgrid_list is a ref to global_dict list # so we must modify and not replace with a new list! vgrid_list.append(default_vgrid) # convert specified runtime environments to upper-case and verify they # actually exist # do not check runtime envs if the job is for ARC (submission will # fail later) if global_dict.get('JOBTYPE', 'unset') != 'arc' \ and global_dict.has_key('RUNTIMEENVIRONMENT'): re_entries_uppercase = [] for specified_re in global_dict['RUNTIMEENVIRONMENT']: specified_re = specified_re.upper() re_entries_uppercase.append(specified_re) if not is_runtime_environment(specified_re, configuration): return (False, """You have specified a non-nexisting runtime environment '%s', therefore the job can not be run on any resources.""" % \ specified_re) if global_dict.get('MOUNT', []) != []: re_entries_uppercase.append(configuration.res_default_mount_re.upper()) global_dict['RUNTIMEENVIRONMENT'] = re_entries_uppercase if global_dict.get('JOBTYPE', 'unset').lower() == 'interactive': # if jobtype is interactive append command to create the notification # file .interactivejobfinished that breaks the infinite loop waiting # for the interactive job to finish and send output files to the MiG # server global_dict['EXECUTE'].append('touch .interactivejobfinished') # put job id and name of user in the dictionary global_dict['JOB_ID'] = job_id global_dict['USER_CERT'] = client_id # mark job as received global_dict['RECEIVED_TIMESTAMP'] = time.gmtime() global_dict['STATUS'] = 'PARSE' if forceddestination: global_dict['FORCEDDESTINATION'] = forceddestination if forceddestination.has_key('UNIQUE_RESOURCE_NAME'): global_dict["RESOURCE"] = "%(UNIQUE_RESOURCE_NAME)s_*" % \ forceddestination if forceddestination.has_key('RE_NAME'): re_name = forceddestination['RE_NAME'] # verify the verifyfiles entries are not modified (otherwise RE creator # can specify multiple ::VERIFYFILES:: keywords and give the entries # other names (perhaps overwriting files in the home directories of # resource owners executing the testprocedure) for verifyfile in global_dict['VERIFYFILES']: verifytypes = ['.status', '.stderr', '.stdout'] found = False for verifytype in verifytypes: if verifyfile == 'verify_runtime_env_%s%s' % (re_name, verifytype): found = True if not found: return (False, '''You are not allowed to specify the ::VERIFY:: keyword in a testprocedure, it is done automatically''') # normalize any path fields to be taken relative to home for field in ('INPUTFILES', 'OUTPUTFILES', 'EXECUTABLES', 'VERIFYFILES'): if not global_dict.has_key(field): continue normalized_field = [] for line in global_dict[field]: normalized_parts = [] line_parts = line.split() if len(line_parts) < 1 or len(line_parts) > 2: return (False, '%s entries must contain 1 or 2 space-separated items'\ % field) for part in line_parts: # deny leading slashes i.e. force absolute to relative paths part = part.lstrip('/') if part.find('://') != -1: # keep external targets as is - normpath breaks '://' normalized_parts.append(part) check_path = part.split('/')[-1] else: # normalize path to avoid e.g. './' which breaks dir # handling on resource check_path = os.path.normpath(part) normalized_parts.append(check_path) try: valid_path(check_path) except Exception, exc: return (False, 'Invalid %s part in %s: %s' % \ (field, html_escape(part), exc)) normalized_field.append(' '.join(normalized_parts)) global_dict[field] = normalized_field
def check_types(parse_output, external_keyword_dict, configuration): """Help check input from job descriptions and resource configurations. IMPORTANT: we parse raw user input here so we can NOT trust any of it to be safe even for for printing. Always handle with utmost care and escape any user values if printing values in errors and the like. """ status = True msg = '' for keyword_block in parse_output: # remove the two colons before and after the keyword job_keyword = keyword_block[0].strip(':') keyword_data = keyword_block[1] if not external_keyword_dict.has_key(job_keyword): status = False # NOTE: we can't trust keyword to be safe for printing msg += 'unknown keyword: %s\n' % html_escape(job_keyword) else: # name of keyword ok, check if the type is correct keyword_dict = external_keyword_dict[job_keyword] keyword_type = keyword_dict['Type'] value = keyword_dict['Value'] # Required = keyword_dict["Required"] # IMPORTANT: all values must be strictly safeinput screened! # Some resource conf values are used in e.g. ssh commands from # subprocess calls. We do try to avoid full shell invocation # and thus variable interpretation but better safe than sorry. # First we validate all keywords and values to be safeinput sub_key = '' try: # Handle sublevels like execonfig and storeconfig explicitly if keyword_dict.get('Sublevel', False): sub_keywords = external_keyword_dict[job_keyword] required = sub_keywords.get('Sublevel_required', []) optional = sub_keywords.get('Sublevel_optional', []) (stat, sub_dict) = get_config_sub_level_dict( keyword_data, {}, required, optional) if not stat: raise Exception('Error in sub level checking: %s %s' % (job_keyword, sub_dict)) for (sub_key, sub_val) in sub_dict.items(): safe_checker = guess_type(sub_key) safe_checker(sub_val) else: safe_checker = guess_type(job_keyword) for data_val in keyword_data: safe_checker(data_val) except Exception, exc: # found invalid value configuration.logger.error("parser type check for %s: %s" % (sub_key, exc)) status = False key = job_keyword val = keyword_data if sub_key: key += ' -> %s' % sub_key val = [sub_val] msg += format_type_error(key, 'invalid data value: %s' % exc, keyword_dict, val) if keyword_type == 'int': if len(keyword_data) != 1: status = False msg += format_type_error(job_keyword, 'requires only a single integer', keyword_dict, keyword_data) else: try: # assign keyword_dict['Value'] = int(keyword_data[0]) except: # could not convert value to int status = False msg += format_type_error(job_keyword, 'requires an integer', keyword_dict, keyword_data) elif keyword_type == 'boolean': if len(keyword_data) > 1: status = False msg += format_type_error(job_keyword, 'requires only a single boolean', keyword_dict, keyword_data) elif len(keyword_data) < 1: # Unset checkbox results in empty data (html "feature") keyword_dict['Value'] = False else: if str(keyword_data[0]).lower() in ['true', '1', 'on']: keyword_dict['Value'] = True elif str(keyword_data[0]).lower() in ['false', '0', 'off']: keyword_dict['Value'] = False else: # could not convert value to boolean status = False msg += format_type_error(job_keyword, 'requires a boolean', keyword_dict, keyword_data) elif keyword_type == 'string': if not keyword_data: # use default value keyword_data.append(keyword_dict['Value']) if len(keyword_data) > 1: status = False msg += format_type_error(job_keyword, 'requires only a single string', keyword_dict, keyword_data) else: try: # assign keyword_dict['Value'] = str(keyword_data[0]) except: # could not convert value to string status = False msg += format_type_error(job_keyword, 'requires a string', keyword_dict, keyword_data) if job_keyword == 'RENAME': # assign in upper case keyword_dict['Value'] = str(keyword_data[0]).upper() if job_keyword == 'ARCHITECTURE': if not str(keyword_data[0])\ in configuration.architectures: status = False msg += format_type_error( job_keyword, 'specified architecture not valid, should be %s' % configuration.architectures, keyword_dict, keyword_data) if job_keyword == 'SCRIPTLANGUAGE': if not str(keyword_data[0])\ in configuration.scriptlanguages: status = False msg += format_type_error( job_keyword, 'specified scriptlanguage not valid, should be %s' % configuration.scriptlanguages, keyword_dict, keyword_data) if job_keyword == 'JOBTYPE': if not str(keyword_data[0])\ in configuration.jobtypes: status = False msg += format_type_error( job_keyword, 'specified jobtype not valid, should be %s' % configuration.jobtypes, keyword_dict, keyword_data) if job_keyword == 'JOBNAME': try: valid_job_name(str(keyword_data[0]), min_length=0) except Exception, err: status = False msg += format_type_error( job_keyword, 'specified jobname not valid: %s' % err, keyword_dict, keyword_data) elif keyword_type == 'multiplestrings': maxfill_values = [keyword_all] + maxfill_fields for single_line in keyword_data: try: value.append(str(single_line)) except: status = False msg += format_type_error( job_keyword, 'requires one or more strings', keyword_dict, keyword_data) if job_keyword == 'MAXFILL': if not single_line.strip() in maxfill_values: status = False msg += format_type_error( job_keyword, 'specified maxfill not valid, should be in %s' % maxfill_values, keyword_dict, keyword_data)
msg += format_type_error( job_keyword, 'Error in sub level parsing: %s' % env_vars, keyword_dict, keyword_data) except Exception, err: status = False msg += format_type_error( job_keyword, 'Error getting RE_environmentvariable value.', keyword_dict, keyword_data) else: status = False msg += \ 'Internal error: Keyword %s with unknown type %s was accepted!'\ % (html_escape(job_keyword), keyword_type) # print str(value) # Keyword was found. Change required to False meaning that the keyword is no longer required if keyword_data: keyword_dict['Required'] = False # check if required keywords are in the mRSL file # (all Required fields should be False since the Required field # of a keyword is changed to False when the parser finds it) for keyword_entry in external_keyword_dict.keys(): entry = external_keyword_dict[keyword_entry] if entry.has_key('Required'): if entry['Required']: