def recv_notification(configuration, path): """Read notification event from file""" logger = configuration.logger # logger.debug("read_notification: %s" % file) status = True new_notification = unpickle(path, logger) if not new_notification: logger.error("Failed to unpickle: %s" % path) return False user_id = new_notification.get('user_id', '') # logger.debug("Received user_id: '%s'" % user_id) if not user_id: status = False logger.error("Missing user_id in notification: %s" % path) else: client_id = expand_openid_alias(user_id, configuration) # logger.debug("resolved client_id: '%s'" % client_id) if not client_id or not extract_field(client_id, 'email'): status = False logger.error("Failed to resolve client_id from user_id: '%s'" % user_id) if status: category = new_notification.get('category', []) # logger.debug("Received category: %s" % category) if not isinstance(category, list): status = False logger.error("Received category: %s must be a list" % category) if status: logger.info("Received event: %s, from: '%s'" % (category, client_id)) new_timestamp = new_notification.get('timestamp') message = new_notification.get('message', '') # logger.debug("Received message: %s" % message) client_dict = received_notifications.get(client_id, {}) if not client_dict: received_notifications[client_id] = client_dict files_list = client_dict.get('files', []) if not files_list: client_dict['files'] = files_list if path in files_list: logger.warning("Skipping previously received notification: '%s'" % path) else: files_list.append(path) client_dict['timestamp'] = min( client_dict.get('timestamp', sys.maxsize), new_timestamp) messages_dict = client_dict.get('messages', {}) if not messages_dict: client_dict['messages'] = messages_dict header = " ".join(category) if not header: header = '* UNKNOWN *' body_dict = messages_dict.get(header, {}) if not body_dict: messages_dict[header] = body_dict message_count = body_dict.get(message, 0) body_dict[message] = message_count + 1 return status
def send_notifications(configuration): """Generate message and send notification to users""" logger = configuration.logger # logger.debug("send_notifications") result = [] for (client_id, client_dict) in received_notifications.iteritems(): timestamp = client_dict.get('timestamp', 0) timestr = ( datetime.fromtimestamp(timestamp)).strftime('%d/%m/%Y %H:%M:%S') client_name = extract_field(client_id, 'full_name') client_email = extract_field(client_id, 'email') recipient = "%s <%s>" % (client_name, client_email) total_events = 0 notify_message = "" messages_dict = client_dict.get('messages', {}) for (header, value) in messages_dict.iteritems(): if notify_message: notify_message += "\n\n" notify_message += "= %s =\n" % header for (message, events) in value.iteritems(): notify_message += "#%s : %s\n" % (events, message) total_events += events subject = "System notification: %s new events" % total_events notify_message = "Found %s new events since: %s\n\n" \ % (total_events, timestr) \ + notify_message status = send_email(recipient, subject, notify_message, logger, configuration) if status: logger.info("Send email with %s events to: %s" % (total_events, recipient)) result.append(client_id) else: logger.error("Failed to send email to: '%s', '%s'" % (recipient, client_id)) return result
def get_twofactor_secrets(configuration, client_id): """Load twofactor base32 key and OTP uri for QR code. Generates secret for user if not already done. Actual twofactor login requirement is not enabled here, however. """ _logger = configuration.logger if pyotp is None: raise Exception("The pyotp module is missing and required for 2FA") if configuration.site_enable_gdp: client_id = get_base_client_id(configuration, client_id, expand_oid_alias=False) # NOTE: 2FA secret key is a standalone file in user settings dir # Try to load existing and generate new one if not there. # We need the base32-encoded form as returned here. b32_key = load_twofactor_key(client_id, configuration) if not b32_key: b32_key = reset_twofactor_key(client_id, configuration) totp = get_totp(client_id, b32_key, configuration) # URI-format for otp auth is # otpauth://<otptype>/(<issuer>:)<accountnospaces>? # secret=<secret>(&issuer=<issuer>)(&image=<imageuri>) # which we pull out of pyotp directly. # IMPORTANT: we use the QRious JS library to keep rendering local. if configuration.user_openid_alias: username = extract_field(client_id, configuration.user_openid_alias) else: username = client_id otp_uri = totp.provisioning_uri(username, issuer_name=configuration.short_title) # Some auth apps like FreeOTP support addition of logo with &image=PNG_URL # Testing is easy with https://freeotp.github.io/qrcode.html if configuration.site_logo_left.endswith('.png'): logo_url = configuration.site_logo_left # NOTE: image URL must a full URL and logo_url is abs or anchored path if not logo_url.startswith('http'): # Remove any leading slashes which would break join logo_url = os.path.join(configuration.migserver_https_sid_url, logo_url.lstrip('/')) # Clear 'safe' argument to also encode slashes in url otp_uri += '&image=%s' % urllib.quote(logo_url, '') # IMPORTANT: pyotp unicode breaks wsgi when inserted - force utf8! otp_uri = force_utf8(otp_uri) return (b32_key, totp.interval, otp_uri)
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': '%s send request' % configuration.short_title }) (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) target_id = client_id vgrid_name = accepted['vgrid_name'][-1].strip() visible_user_names = accepted['cert_id'] visible_res_names = accepted['unique_resource_name'] request_type = accepted['request_type'][-1].strip().lower() request_text = accepted['request_text'][-1].strip() protocols = [proto.strip() for proto in accepted['protocol']] use_any = False if any_protocol in protocols: use_any = True protocols = configuration.notify_protocols protocols = [proto.lower() for proto in protocols] 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) valid_request_types = [ 'resourceowner', 'resourceaccept', 'resourcereject', 'vgridowner', 'vgridmember', 'vgridresource', 'vgridaccept', 'vgridreject', 'plain' ] if not request_type in valid_request_types: output_objects.append({ 'object_type': 'error_text', 'text': '%s is not a valid request_type (valid types: %s)!' % (request_type.lower(), valid_request_types) }) return (output_objects, returnvalues.CLIENT_ERROR) if not protocols: output_objects.append({ 'object_type': 'error_text', 'text': 'No protocol specified!' }) return (output_objects, returnvalues.CLIENT_ERROR) user_map = get_user_map(configuration) reply_to = user_map[client_id][USERID] # Try to point replies to client_id email client_email = extract_field(reply_to, 'email') if request_type == "plain": if not visible_user_names: output_objects.append({ 'object_type': 'error_text', 'text': 'No user ID specified!' }) return (output_objects, returnvalues.CLIENT_ERROR) user_id = visible_user_names[-1].strip() anon_map = anon_to_real_user_map(configuration) if user_id in anon_map: user_id = anon_map[user_id] if user_id not in user_map: output_objects.append({ 'object_type': 'error_text', 'text': 'No such user: %s' % user_id }) return (output_objects, returnvalues.CLIENT_ERROR) target_name = user_id user_dict = user_map[user_id] vgrid_access = user_vgrid_access(configuration, client_id) 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 any_vgrid in vgrids_allow_im: im_vgrids = vgrid_access else: im_vgrids = set(vgrids_allow_im).intersection(vgrid_access) if use_any: # Do not try disabled protocols if ANY was requested if not email_vgrids: protocols = [ proto for proto in protocols if proto not in email_keyword_list ] if not im_vgrids: protocols = [ proto for proto in protocols if proto in email_keyword_list ] if not email_vgrids and [ proto for proto in protocols if proto in email_keyword_list ]: output_objects.append({ 'object_type': 'error_text', 'text': 'You are not allowed to send emails to %s!' % user_id }) return (output_objects, returnvalues.CLIENT_ERROR) if not im_vgrids and [ proto for proto in protocols if proto not in email_keyword_list ]: output_objects.append({ 'object_type': 'error_text', 'text': 'You are not allowed to send instant messages to %s!' % user_id }) return (output_objects, returnvalues.CLIENT_ERROR) for proto in protocols: if not user_dict[CONF].get(proto.upper(), False): if use_any: # Remove missing protocols if ANY protocol was requested protocols = [i for i in protocols if i != proto] else: output_objects.append({ 'object_type': 'error_text', 'text': 'User %s does not accept %s messages!' % (user_id, proto) }) return (output_objects, returnvalues.CLIENT_ERROR) if not protocols: output_objects.append({ 'object_type': 'error_text', 'text': 'User %s does not accept requested protocol(s) messages!' % user_id }) return (output_objects, returnvalues.CLIENT_ERROR) target_list = [user_id] elif request_type in ["vgridaccept", "vgridreject"]: # Always allow accept messages but only between owners/members if not visible_user_names and not visible_res_names: output_objects.append({ 'object_type': 'error_text', 'text': 'No user or resource ID specified!' }) return (output_objects, returnvalues.CLIENT_ERROR) if not vgrid_name: output_objects.append({ 'object_type': 'error_text', 'text': 'No vgrid_name specified!' }) return (output_objects, returnvalues.CLIENT_ERROR) if vgrid_name.upper() == default_vgrid.upper(): output_objects.append({ 'object_type': 'error_text', 'text': 'No requests for %s are allowed!' % default_vgrid }) return (output_objects, returnvalues.CLIENT_ERROR) if not vgrid_is_owner(vgrid_name, client_id, configuration): output_objects.append({ 'object_type': 'error_text', 'text': 'You are not an owner of %s or a parent %s!' % (vgrid_name, label) }) return (output_objects, returnvalues.CLIENT_ERROR) # NOTE: we support exactly one vgrid but multiple users/resources here if visible_user_names: logger.info("setting user recipients: %s" % visible_user_names) target_list = [user_id.strip() for user_id in visible_user_names] elif visible_res_names: # vgrid resource accept - lookup and notify resource owners logger.info("setting res owner recipients: %s" % visible_res_names) target_list = [] for unique_resource_name in visible_res_names: logger.info("loading res owners for %s" % unique_resource_name) (load_status, res_owners) = resource_owners(configuration, unique_resource_name) if not load_status: output_objects.append({ 'object_type': 'error_text', 'text': 'Could not lookup owners of %s!' % unique_resource_name }) continue logger.info("adding res owners to recipients: %s" % res_owners) target_list += [user_id for user_id in res_owners] target_id = '%s %s owners' % (vgrid_name, label) target_name = vgrid_name elif request_type in ["resourceaccept", "resourcereject"]: # Always allow accept messages between actual resource owners if not visible_user_names: output_objects.append({ 'object_type': 'error_text', 'text': 'No user ID specified!' }) return (output_objects, returnvalues.CLIENT_ERROR) if not visible_res_names: output_objects.append({ 'object_type': 'error_text', 'text': 'No resource ID specified!' }) return (output_objects, returnvalues.CLIENT_ERROR) # NOTE: we support exactly one resource but multiple users here unique_resource_name = visible_res_names[-1].strip() target_name = unique_resource_name res_map = get_resource_map(configuration) if unique_resource_name not in res_map: output_objects.append({ 'object_type': 'error_text', 'text': 'No such resource: %s' % unique_resource_name }) return (output_objects, returnvalues.CLIENT_ERROR) owners_list = res_map[unique_resource_name][OWNERS] if not client_id in owners_list: output_objects.append({ 'object_type': 'error_text', 'text': 'You are not an owner of %s!' % unique_resource_name }) output_objects.append({ 'object_type': 'error_text', 'text': 'Invalid resource %s message!' % request_type }) return (output_objects, returnvalues.CLIENT_ERROR) target_id = '%s resource owners' % unique_resource_name target_name = unique_resource_name target_list = [user_id.strip() for user_id in visible_user_names] elif request_type == "resourceowner": if not visible_res_names: output_objects.append({ 'object_type': 'error_text', 'text': 'No resource ID specified!' }) return (output_objects, returnvalues.CLIENT_ERROR) # NOTE: we support exactly one resource but multiple users here unique_resource_name = visible_res_names[-1].strip() anon_map = anon_to_real_res_map(configuration.resource_home) if unique_resource_name in anon_map: unique_resource_name = anon_map[unique_resource_name] target_name = unique_resource_name res_map = get_resource_map(configuration) if unique_resource_name not in res_map: output_objects.append({ 'object_type': 'error_text', 'text': 'No such resource: %s' % unique_resource_name }) return (output_objects, returnvalues.CLIENT_ERROR) target_list = res_map[unique_resource_name][OWNERS] if client_id in target_list: output_objects.append({ 'object_type': 'error_text', 'text': 'You are already an owner of %s!' % unique_resource_name }) return (output_objects, returnvalues.CLIENT_ERROR) request_dir = os.path.join(configuration.resource_home, unique_resource_name) access_request = { 'request_type': request_type, 'entity': client_id, 'target': unique_resource_name, 'request_text': request_text } if not save_access_request(configuration, request_dir, access_request): output_objects.append({ 'object_type': 'error_text', 'text': 'Could not save request - owners may still manually add you' }) return (output_objects, returnvalues.SYSTEM_ERROR) elif request_type in ["vgridmember", "vgridowner", "vgridresource"]: if not vgrid_name: output_objects.append({ 'object_type': 'error_text', 'text': 'No vgrid_name specified!' }) return (output_objects, returnvalues.CLIENT_ERROR) # default vgrid is read-only if vgrid_name.upper() == default_vgrid.upper(): output_objects.append({ 'object_type': 'error_text', 'text': 'No requests for %s are not allowed!' % default_vgrid }) return (output_objects, returnvalues.CLIENT_ERROR) # stop owner or member request if already an owner # and prevent repeated resource access requests if request_type == 'vgridresource': # NOTE: we support exactly one resource here unique_resource_name = visible_res_names[-1].strip() target_id = entity = unique_resource_name if vgrid_is_resource(vgrid_name, unique_resource_name, configuration): output_objects.append({ 'object_type': 'error_text', 'text': 'You already have access to %s or a parent %s.' % (vgrid_name, label) }) return (output_objects, returnvalues.CLIENT_ERROR) else: target_id = entity = client_id if vgrid_is_owner(vgrid_name, client_id, configuration): output_objects.append({ 'object_type': 'error_text', 'text': 'You are already an owner of %s or a parent %s!' % (vgrid_name, label) }) return (output_objects, returnvalues.CLIENT_ERROR) # only ownership requests are allowed for existing members if request_type == 'vgridmember': if vgrid_is_member(vgrid_name, client_id, configuration): output_objects.append({ 'object_type': 'error_text', 'text': 'You are already a member of %s or a parent %s.' % (vgrid_name, label) }) return (output_objects, returnvalues.CLIENT_ERROR) # Find all VGrid owners configured to receive notifications target_name = vgrid_name (settings_status, settings_dict) = vgrid_settings(vgrid_name, configuration, recursive=True, as_dict=True) if not settings_status: settings_dict = {} request_recipients = settings_dict.get('request_recipients', default_vgrid_settings_limit) # We load and use direct owners first if any - otherwise inherited owners_list = [] for inherited in (False, True): (owners_status, owners_list) = vgrid_owners(vgrid_name, configuration, recursive=inherited) if not owners_status: output_objects.append({ 'object_type': 'error_text', 'text': 'Failed to lookup owners for %s %s - sure it exists?' % (vgrid_name, label) }) return (output_objects, returnvalues.CLIENT_ERROR) elif owners_list: break if not owners_list: output_objects.append({ 'object_type': 'error_text', 'text': 'Failed to lookup owners for %s %s - sure it exists?' % (vgrid_name, label) }) return (output_objects, returnvalues.CLIENT_ERROR) # Now we have direct or inherited owners to notify target_list = owners_list[:request_recipients] request_dir = os.path.join(configuration.vgrid_home, vgrid_name) access_request = { 'request_type': request_type, 'entity': entity, 'target': vgrid_name, 'request_text': request_text } if not save_access_request(configuration, request_dir, access_request): output_objects.append({ 'object_type': 'error_text', 'text': 'Could not save request - owners may still manually add you' }) return (output_objects, returnvalues.SYSTEM_ERROR) else: output_objects.append({ 'object_type': 'error_text', 'text': 'Invalid request type: %s' % request_type }) return (output_objects, returnvalues.CLIENT_ERROR) # Now send request to all targets in turn # TODO: inform requestor if no owners have mail/IM set in their settings logger.debug("sending notification to recipients: %s" % target_list) for target in target_list: if not target: logger.warning("skipping empty notify target: %s" % target_list) continue # USER_CERT entry is destination notify = [] for proto in protocols: notify.append('%s: SETTINGS' % proto) job_dict = { 'NOTIFY': notify, 'JOB_ID': 'NOJOBID', 'USER_CERT': target, 'EMAIL_SENDER': client_email } notifier = notify_user_thread( job_dict, [target_id, target_name, request_type, request_text, reply_to], 'SENDREQUEST', logger, '', configuration, ) # Try finishing delivery but do not block forever on one message notifier.join(30) output_objects.append({ 'object_type': 'text', 'text': 'Sent %s message to %d people' % (request_type, len(target_list)) }) im_notify_protocols = [ i for i in configuration.notify_protocols if i != 'email' ] if im_notify_protocols: enabled_notify = 'IM / email' else: enabled_notify = 'email' output_objects.append({ 'object_type': 'text', 'text': """Please make sure you have %s notifications configured on your Setings page if you expect a reply to this message""" % enabled_notify }) 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) defaults = signature()[1] client_dir = client_id_dir(client_id) logger.debug('in peersaction: %s' % user_arguments_dict) (validate_status, accepted) = validate_input(user_arguments_dict, defaults, output_objects, allow_rejects=False) if not validate_status: return (accepted, returnvalues.CLIENT_ERROR) if not safe_handler(configuration, 'post', op_name, client_id, get_csrf_limit(configuration), accepted): output_objects.append({ 'object_type': 'error_text', 'text': '''Only accepting CSRF-filtered POST requests to prevent unintended updates''' }) return (output_objects, returnvalues.CLIENT_ERROR) title_entry = find_entry(output_objects, 'title') title_entry['text'] = 'Save Peers' output_objects.append({'object_type': 'header', 'text': 'Save Peers'}) admin_email = configuration.admin_email smtp_server = configuration.smtp_server user_pending = os.path.abspath(configuration.user_pending) user_map = get_full_user_map(configuration) user_dict = user_map.get(client_id, None) # Optional site-wide limitation of peers permission if not user_dict or \ not peers_permit_allowed(configuration, user_dict): logger.warning("user %s is not allowed to permit peers!" % client_id) output_objects.append({ 'object_type': 'error_text', 'text': 'Only privileged users can permit external peers!' }) return (output_objects, returnvalues.CLIENT_ERROR) action = accepted['action'][-1].strip() label = accepted['peers_label'][-1].strip() kind = accepted['peers_kind'][-1].strip() raw_expire = accepted['peers_expire'][-1].strip() peers_content = accepted['peers_content'] peers_format = accepted['peers_format'][-1].strip() peers_invite = accepted['peers_invite'][-1].strip() do_invite = (peers_invite.lower() in ['on', 'true', 'yes', '1']) try: expire = datetime.datetime.strptime(raw_expire, '%Y-%m-%d') if datetime.datetime.now() > expire: raise ValueError("specified expire value is in the past!") except Exception as exc: logger.error("expire %r could not be parsed into a (future) date" % raw_expire) output_objects.append({ 'object_type': 'text', 'text': 'No valid expire provided - using default: %d days' % default_expire_days }) expire = datetime.datetime.now() expire += datetime.timedelta(days=default_expire_days) expire = expire.date().isoformat() if not action in peer_actions: output_objects.append({ 'object_type': 'error_text', 'text': 'Unsupported peer action %r - only %s are allowed' % (action, ', '.join(peer_actions)) }) return (output_objects, returnvalues.CLIENT_ERROR) if not kind in peer_kinds: output_objects.append({ 'object_type': 'error_text', 'text': 'Unsupported peer kind %r - only %s are allowed' % (kind, ', '.join(peer_kinds)) }) return (output_objects, returnvalues.CLIENT_ERROR) # TODO: implement and enable more formats? if peers_format not in ("csvform", 'userid'): output_objects.append({ 'object_type': 'error_text', 'text': 'Only Import Peers is implemented so far!' }) return (output_objects, returnvalues.CLIENT_ERROR) peers_path = os.path.join(configuration.user_settings, client_dir, peers_filename) try: all_peers = load(peers_path) except Exception as exc: logger.warning("could not load peers from: %s" % exc) all_peers = {} # Extract peer(s) from request (peers, err) = parse_peers(configuration, peers_content, peers_format) if not err and not peers: err = ["No valid peers provided"] if err: output_objects.append({ 'object_type': 'error_text', 'text': 'Parsing failed: %s' % '.\n '.join(err) }) output_objects.append({ 'object_type': 'link', 'destination': 'peers.py', 'text': 'Back to peers' }) return (output_objects, returnvalues.CLIENT_ERROR) # NOTE: general cases of operation here: # * import multiple peers in one go (add new, update existing) # * add one or more new peers # * update one or more existing peers # * remove one or more existing peers # * accept one or more pending requests # * reject one or more pending requests # The kind and expire values are generally applied for all included peers. # NOTE: we check all peers before any action for user in peers: fill_distinguished_name(user) peer_id = user['distinguished_name'] cur_peer = all_peers.get(peer_id, {}) if 'add' == action and cur_peer: output_objects.append({ 'object_type': 'error_text', 'text': 'Peer %r already exists!' % peer_id }) return (output_objects, returnvalues.CLIENT_ERROR) elif 'update' == action and not cur_peer: output_objects.append({ 'object_type': 'error_text', 'text': 'Peer %r does not exists!' % peer_id }) return (output_objects, returnvalues.CLIENT_ERROR) elif 'remove' == action and not cur_peer: output_objects.append({ 'object_type': 'error_text', 'text': 'Peer %r does not exists!' % peer_id }) return (output_objects, returnvalues.CLIENT_ERROR) elif 'accept' == action and cur_peer: output_objects.append({ 'object_type': 'error_text', 'text': 'Peer %r already accepted!' % peer_id }) return (output_objects, returnvalues.CLIENT_ERROR) elif 'reject' == action and cur_peer: output_objects.append({ 'object_type': 'error_text', 'text': 'Peer %r already accepted!' % peer_id }) return (output_objects, returnvalues.CLIENT_ERROR) elif 'import' == action and cur_peer: # Only warn on import with existing match output_objects.append({ 'object_type': 'text', 'text': 'Updating existing peer %r' % peer_id }) # Now apply changes for user in peers: peer_id = user['distinguished_name'] cur_peer = all_peers.get(peer_id, {}) user.update({'label': label, 'kind': kind, 'expire': expire}) if 'add' == action: all_peers[peer_id] = user elif 'update' == action: all_peers[peer_id] = user elif 'remove' == action: del all_peers[peer_id] elif 'accept' == action: all_peers[peer_id] = user elif 'reject' == action: pass elif 'import' == action: all_peers[peer_id] = user logger.info("%s peer %s" % (action, peer_id)) try: dump(all_peers, peers_path) logger.debug('%s %s peers %s in %s' % (client_id, action, all_peers, peers_path)) output_objects.append({ 'object_type': 'text', 'text': "Completed %s peers" % action }) for user in peers: output_objects.append({ 'object_type': 'text', 'text': "%(distinguished_name)s" % user }) if action in ['import', 'add', 'update']: client_name = extract_field(client_id, 'full_name') client_email = extract_field(client_id, 'email') if do_invite: succeeded, failed = [], [] email_header = '%s Invitation' % configuration.short_title email_msg_template = """Hi %%s, This is an automatic email sent on behalf of %s who vouched for you to get a user account on %s. You can accept the invitation by going to %%s entering a password of your choice and submitting the form. If you do not want a user account you can safely ignore this email. We would be grateful if you report any abuse of the invitation system to the site administrators (%s). """ % (client_name, configuration.short_title, admin_email) for peer_user in peers: peer_name = peer_user['full_name'] peer_email = peer_user['email'] peer_url = os.path.join( configuration.migserver_https_sid_url, 'cgi-sid', 'reqoid.py') peer_req = {} for field in peers_fields: peer_req[field] = peer_user.get(field, '') peer_req['comment'] = 'Invited by %s (%s) for %s purposes' \ % (client_name, client_email, kind) # Mark ID fields as readonly in the form to limit errors peer_req['ro_fields'] = keyword_auto peer_url += '?%s' % urllib.urlencode(peer_req) email_msg = email_msg_template % (peer_name, peer_url) logger.info( 'Sending invitation: to: %s, header: %s, msg: %s, smtp_server: %s' % (peer_email, email_header, email_msg, smtp_server)) if send_email(peer_email, email_header, email_msg, logger, configuration): succeeded.append(peer_email) else: failed.append(peer_email) if failed: output_objects.append({ 'object_type': 'error_text', 'text': """An error occured trying to email the peer invitation to %s . Please inform the site admins (%s) if the problem persists. """ % (', '.join(failed), admin_email) }) if succeeded: output_objects.append({ 'object_type': 'text', 'text': """Sent invitation to %s with a link to a mostly pre-filled %s account request form with the exact ID fields you provided here.""" % (', '.join(succeeded), configuration.short_title) }) else: output_objects.append({ 'object_type': 'text', 'text': """Please tell your peers to request an account at %s with the exact ID fields you provided here and importantly mentioning the purpose and your email (%s) in the sign up Comment field. Alternatively you can use the invite button to send out an email with a link to a mostly prefilled request form.""" % (configuration.short_title, client_email) }) except Exception as exc: logger.error('Failed to save %s peers to %s: %s' % (client_id, peers_path, exc)) output_objects.append({ 'object_type': 'error_text', 'text': ''' Could not %s peers %r. Please contact the site admins on %s if this error persists. ''' % (action, label, admin_email) }) return (output_objects, returnvalues.SYSTEM_ERROR) if action in ["accept", "reject"]: changed = [(i['distinguished_name'], i) for i in peers] if not manage_pending_peers(configuration, client_id, "remove", changed): logger.warning('could not update pending peers for %s after %s' % (client_id, action)) logger.info('%s completed for %s peers for %s in %s' % (action, label, client_id, peers_path)) user_lines = [] pretty_peers = {'label': label, 'kind': kind, 'expire': expire} for user in peers: user_lines.append(user['distinguished_name']) pretty_peers['user_lines'] = '\n'.join(user_lines) email_header = '%s Peers %s' % (configuration.short_title, action) email_msg = """Received %s peers from %s """ % (action, client_id) email_msg += """ Kind: %(kind)s , Expire: %(expire)s, Label: %(label)s , Peers: %(user_lines)s """ % pretty_peers logger.info('Sending email: to: %s, header: %s, msg: %s, smtp_server: %s' % (admin_email, email_header, email_msg, smtp_server)) if not send_email(admin_email, email_header, email_msg, logger, configuration): output_objects.append({ 'object_type': 'error_text', 'text': ''' An error occured trying to send the email about your %s peers to the site administrators. Please manually inform them (%s) if the problem persists. ''' % (action, admin_email) }) return (output_objects, returnvalues.SYSTEM_ERROR) output_objects.append({ 'object_type': 'text', 'text': ''' Informed the site admins about your %s peers action to let them accept peer account requests you already validated.''' % action }) output_objects.append({ 'object_type': 'link', 'destination': 'peers.py', 'text': 'Back to peers' }) return (output_objects, returnvalues.OK)
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) output_objects.append({ 'object_type': 'header', 'text': 'Cloud Instance Management' }) user_map = get_full_user_map(configuration) user_dict = user_map.get(client_id, None) # Optional limitation of cload access vgrid 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) return_status = returnvalues.OK action = accepted['action'][-1] # NOTE: instance_X may be empty list - fall back to empty string instance_id = ([''] + accepted['instance_id'])[-1] instance_label = ([''] + accepted['instance_label'])[-1] instance_image = ([''] + accepted['instance_image'])[-1] accept_terms = (([''] + accepted['accept_terms'])[-1] in ('yes', 'on')) cloud_id = accepted['service'][-1] service = { k: v for options in configuration.cloud_services for k, v in options.items() if options['service_name'] == cloud_id } if not service: valid_services = [ options['service_name'] for options in configuration.cloud_services ] output_objects.append({ 'object_type': 'error_text', 'text': '%s is not among the valid cloud services: %s' % (cloud_id, ', '.join(valid_services)) }) return (output_objects, returnvalues.CLIENT_ERROR) valid_service = valid_cloud_service(configuration, service) if not valid_service: output_objects.append({ 'object_type': 'error_text', 'text': 'The service %s appears to be misconfigured, ' 'please contact a system administrator about this issue' % cloud_id }) return (output_objects, returnvalues.SYSTEM_ERROR) service_title = service['service_title'] if not action in valid_actions: output_objects.append({ 'object_type': 'error_text', 'text': '%s is not a valid action ' 'allowed actions include %s' % (action, ', '.join(valid_actions)) }) return (output_objects, returnvalues.CLIENT_ERROR) elif action in cloud_edit_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) cloud_flavor = service.get("service_flavor", "openstack") user_home_dir = os.path.join(configuration.user_home, client_dir) client_email = extract_field(client_id, 'email') if not client_email: logger.error("could not extract client email for %s!" % client_id) output_objects.append({ 'object_type': 'error_text', 'text': "No client ID found - can't continue" }) return (output_objects, returnvalues.SYSTEM_ERROR) ssh_auth_msg = "Login requires your private key for your public key:" instance_missing_msg = "Found no '%s' instance at %s. Please contact a " \ + "site administrator if it should be there." _label = instance_label if instance_id and not _label: _, _label, _ = cloud_split_instance_id(configuration, client_id, instance_id) if "create" == action: if not accept_terms: logger.error("refusing create without accepting terms for %s!" % client_id) output_objects.append({ 'object_type': 'error_text', 'text': "You MUST accept the cloud user terms to create instances" }) return (output_objects, returnvalues.CLIENT_ERROR) # Load all instances and make sure none contains label in ID saved_instances = cloud_load_instance(configuration, client_id, cloud_id, keyword_all) for (saved_id, instance) in saved_instances.items(): if instance_label == instance.get('INSTANCE_LABEL', saved_id): logger.error("Refused %s re-create %s cloud instance %s!" % (client_id, cloud_id, instance_label)) output_objects.append({ 'object_type': 'error_text', 'text': "You already have an instance with the label '%s'!" % instance_label }) return (output_objects, returnvalues.CLIENT_ERROR) max_instances = lookup_user_service_value( configuration, client_id, service, 'service_max_user_instances') max_user_instances = int(max_instances) # NOTE: a negative max value means unlimited but 0 or more is enforced if max_user_instances >= 0 and \ len(saved_instances) >= max_user_instances: logger.error("Refused %s create additional %s cloud instances!" % (client_id, cloud_id)) output_objects.append({ 'object_type': 'error_text', 'text': "You already have the maximum allowed %s instances (%d)!" % (service_title, max_user_instances) }) return (output_objects, returnvalues.CLIENT_ERROR) if not instance_label: logger.error("Refused %s create unlabelled %s cloud instance!" % (client_id, cloud_id)) output_objects.append({ 'object_type': 'error_text', 'text': "No instance label provided!" }) return (output_objects, returnvalues.CLIENT_ERROR) # 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 / allowed cloud images found!" }) return (output_objects, returnvalues.CLIENT_ERROR) if not instance_image: instance_image = allowed_images[0] logger.info("No image specified - using first for %s in %s: %s" % (client_id, cloud_id, instance_image)) image_id = None for (img_name, img_id, img_alias) in allowed_images: if instance_image == img_name: image_id = img_id break if not image_id: logger.error("No matching image ID found for %s in %s: %s" % (client_id, cloud_id, instance_image)) output_objects.append({ 'object_type': 'error_text', 'text': "No such image found: %s" % instance_image }) return (output_objects, returnvalues.CLIENT_ERROR) # TODO: remove this direct key injection if we can delay it cloud_settings = load_cloud(client_id, configuration) raw_keys = cloud_settings.get('authkeys', '').split('\n') auth_keys = [i.split('#', 1)[0].strip() for i in raw_keys] auth_keys = [i for i in auth_keys if i] if not auth_keys: logger.error("No cloud pub keys setup for %s - refuse create" % client_id) output_objects.append({ 'object_type': 'error_text', 'text': """ You haven't provided any valid ssh pub key(s) for cloud instance login, which is stricly required for all use. Please do so before you try again. """ }) output_objects.append({ 'object_type': 'link', 'destination': 'setup.py?topic=cloud', 'text': 'Open cloud setup', 'class': 'cloudsetuplink iconspace', 'title': 'open cloud setup', 'target': '_blank' }) return (output_objects, returnvalues.CLIENT_ERROR) logger.debug("Continue create for %s with auth_keys: %s" % (client_id, auth_keys)) # Create a new internal keyset and session id (priv_key, pub_key) = generate_ssh_rsa_key_pair(encode_utf8=True) session_id = generate_random_ascii(session_id_bytes, charset='0123456789abcdef') # We make sure to create instance with a globally unique ID on the # cloud while only showing the requested instance_label to the user. instance_id = cloud_build_instance_id(configuration, client_email, instance_label, session_id) # TODO: make more fields flexible/conf cloud_dict = { 'INSTANCE_ID': instance_id, 'INSTANCE_LABEL': instance_label, 'INSTANCE_IMAGE': instance_image, 'IMAGE_ID': image_id, 'AUTH_KEYS': auth_keys, 'USER_CERT': client_id, 'INSTANCE_PRIVATE_KEY': priv_key, 'INSTANCE_PUBLIC_KEY': pub_key, # don't need fraction precision, also not all systems provide fraction # precision. 'CREATED_TIMESTAMP': int(time.time()), # Init unset ssh address and leave for floating IP assigment below 'INSTANCE_SSH_IP': '', 'INSTANCE_SSH_PORT': 22, } (action_status, action_msg) = create_cloud_instance(configuration, client_id, cloud_id, cloud_flavor, instance_id, image_id, auth_keys) if not action_status: logger.error( "%s %s cloud instance %s for %s failed: %s" % (action, cloud_id, instance_id, client_id, action_msg)) output_objects.append({ 'object_type': 'error_text', 'text': 'Your %s instance %s at %s did not succeed: %s' % (action, instance_label, service_title, action_msg) }) return (output_objects, returnvalues.SYSTEM_ERROR) # On success the action_msg contains the assigned floating IP address instance_ssh_fqdn = action_msg cloud_dict['INSTANCE_SSH_IP'] = instance_ssh_fqdn if not cloud_save_instance(configuration, client_id, cloud_id, instance_id, cloud_dict): logger.error("save new %s cloud instance %s for %s failed" % (cloud_id, instance_id, client_id)) output_objects.append({ 'object_type': 'error_text', 'text': 'Error saving your %s cloud instance setup' % service_title }) return (output_objects, returnvalues.SYSTEM_ERROR) output_objects.append({ 'object_type': 'text', 'text': "%s instance %s at %s: %s" % (action, instance_label, service_title, "success") }) output_objects.append({ 'object_type': 'html_form', 'text': _ssh_help(configuration, client_id, cloud_id, cloud_dict, instance_id) }) elif "delete" == action: saved_instance = cloud_load_instance(configuration, client_id, cloud_id, instance_id) if not saved_instance: logger.error("no saved %s cloud instance %s for %s to delete" % (cloud_id, instance_id, client_id)) output_objects.append({ 'object_type': 'error_text', 'text': instance_missing_msg % (_label, service_title) }) return (output_objects, returnvalues.CLIENT_ERROR) (action_status, action_msg) = delete_cloud_instance(configuration, client_id, cloud_id, cloud_flavor, instance_id) if not action_status: logger.error( "%s %s cloud instance %s for %s failed: %s" % (action, cloud_id, instance_id, client_id, action_msg)) output_objects.append({ 'object_type': 'error_text', 'text': 'Your %s instance %s at %s did not succeed: %s' % (action, _label, service_title, action_msg) }) return (output_objects, returnvalues.SYSTEM_ERROR) if not cloud_purge_instance(configuration, client_id, cloud_id, instance_id): logger.error("purge %s cloud instance %s for %s failed" % (cloud_id, instance_id, client_id)) output_objects.append({ 'object_type': 'error_text', 'text': 'Error deleting your %s cloud instance setup' % service_title }) return (output_objects, returnvalues.SYSTEM_ERROR) output_objects.append({ 'object_type': 'text', 'text': "%s instance %s at %s: %s" % (action, _label, service_title, "success") }) elif "status" == action: saved_instance = cloud_load_instance(configuration, client_id, cloud_id, instance_id) if not saved_instance: logger.error("no saved %s cloud instance %s for %s to query" % (cloud_id, instance_id, client_id)) output_objects.append({ 'object_type': 'error_text', 'text': instance_missing_msg % (_label, service_title) }) return (output_objects, returnvalues.CLIENT_ERROR) (action_status, action_msg) = status_of_cloud_instance(configuration, client_id, cloud_id, cloud_flavor, instance_id) if not action_status: logger.error( "%s %s cloud instance %s for %s failed: %s" % (action, cloud_id, instance_id, client_id, action_msg)) output_objects.append({ 'object_type': 'error_text', 'text': 'Your %s instance %s at %s did not succeed: %s' % (action, _label, service_title, action_msg) }) return (output_objects, returnvalues.SYSTEM_ERROR) output_objects.append({ 'object_type': 'text', 'text': "%s instance %s at %s: %s" % (action, _label, service_title, action_msg) }) # Show instance access details if running if action_msg in ('ACTIVE', 'RUNNING'): # Only include web console if explicitly configured if configuration.user_cloud_console_access: (console_status, console_msg) = web_access_cloud_instance( configuration, client_id, cloud_id, cloud_flavor, instance_id) if not console_status: logger.error( "%s cloud instance %s console for %s failed: %s" % \ (cloud_id, instance_id, client_id, console_msg)) output_objects.append({ 'object_type': 'error_text', 'text': 'Failed to get instance %s at %s console: %s' % (_label, service_title, console_msg) }) return (output_objects, returnvalues.SYSTEM_ERROR) logger.info("%s cloud instance %s console for %s: %s" % (cloud_id, instance_id, client_id, console_msg)) output_objects.append({ 'object_type': 'link', 'destination': console_msg, 'text': 'Open web console', 'class': 'consolelink iconspace', 'title': 'open web console', 'target': '_blank' }) output_objects.append({'object_type': 'text', 'text': ''}) output_objects.append({ 'object_type': 'html_form', 'text': _ssh_help(configuration, client_id, cloud_id, saved_instance, instance_id) }) output_objects.append({'object_type': 'text', 'text': ''}) elif "start" == action: saved_instance = cloud_load_instance(configuration, client_id, cloud_id, instance_id) if not saved_instance: logger.error("no saved %s cloud instance %s for %s to start" % (cloud_id, instance_id, client_id)) output_objects.append({ 'object_type': 'error_text', 'text': instance_missing_msg % (_label, service_title) }) return (output_objects, returnvalues.CLIENT_ERROR) (action_status, action_msg) = start_cloud_instance(configuration, client_id, cloud_id, cloud_flavor, instance_id) if not action_status: logger.error( "%s %s cloud instance %s for %s failed: %s" % (action, cloud_id, instance_id, client_id, action_msg)) output_objects.append({ 'object_type': 'error_text', 'text': 'Your %s instance %s at %s did not succeed: %s' % (action, _label, service_title, action_msg) }) return (output_objects, returnvalues.SYSTEM_ERROR) output_objects.append({ 'object_type': 'text', 'text': "%s instance %s at %s: %s" % (action, _label, service_title, "success") }) output_objects.append({ 'object_type': 'html_form', 'text': _ssh_help(configuration, client_id, cloud_id, saved_instance, instance_id) }) elif action in ("softrestart", "hardrestart"): boot_strength = action.replace("restart", "").upper() saved_instance = cloud_load_instance(configuration, client_id, cloud_id, instance_id) if not saved_instance: logger.error("no saved %s cloud instance %s for %s to restart" % (cloud_id, instance_id, client_id)) output_objects.append({ 'object_type': 'error_text', 'text': instance_missing_msg % (_label, service_title) }) return (output_objects, returnvalues.CLIENT_ERROR) (action_status, action_msg) = restart_cloud_instance(configuration, client_id, cloud_id, cloud_flavor, instance_id, boot_strength) if not action_status: logger.error( "%s %s cloud instance %s for %s failed: %s" % (action, cloud_id, instance_id, client_id, action_msg)) output_objects.append({ 'object_type': 'error_text', 'text': 'Your %s instance %s at %s did not succeed: %s' % (action, _label, service_title, action_msg) }) return (output_objects, returnvalues.SYSTEM_ERROR) output_objects.append({ 'object_type': 'text', 'text': "%s instance %s at %s: %s" % (action, _label, service_title, "success") }) output_objects.append({ 'object_type': 'html_form', 'text': _ssh_help(configuration, client_id, cloud_id, saved_instance, instance_id) }) elif "stop" == action: saved_instance = cloud_load_instance(configuration, client_id, cloud_id, instance_id) if not saved_instance: logger.error("no saved %s cloud instance %s for %s to %s" % (cloud_id, instance_id, client_id, action)) output_objects.append({ 'object_type': 'error_text', 'text': instance_missing_msg % (_label, service_title) }) return (output_objects, returnvalues.CLIENT_ERROR) (action_status, action_msg) = stop_cloud_instance(configuration, client_id, cloud_id, cloud_flavor, instance_id) if not action_status: logger.error( "%s %s cloud instance %s for %s failed: %s" % (action, cloud_id, instance_id, client_id, action_msg)) output_objects.append({ 'object_type': 'error_text', 'text': 'Your %s instance %s at %s did not succeed: %s' % (action, _label, service_title, action_msg) }) return (output_objects, returnvalues.SYSTEM_ERROR) output_objects.append({ 'object_type': 'text', 'text': "%s instance %s at %s: %s" % (action, _label, service_title, "success") }) elif "webaccess" == action: saved_instance = cloud_load_instance(configuration, client_id, cloud_id, instance_id) if not saved_instance: logger.error("no saved %s cloud instance %s for %s to query" % (cloud_id, instance_id, client_id)) output_objects.append({ 'object_type': 'error_text', 'text': instance_missing_msg % (_label, service_title) }) return (output_objects, returnvalues.CLIENT_ERROR) if not configuration.user_cloud_console_access: logger.error("web console not enabled in conf!") output_objects.append({ 'object_type': 'error_text', 'text': 'Site does not expose cloud web console!' }) return (output_objects, returnvalues.CLIENT_ERROR) (action_status, action_msg) = web_access_cloud_instance(configuration, client_id, cloud_id, cloud_flavor, instance_id) if not action_status: logger.error( "%s %s cloud instance %s for %s failed: %s" % (action, service_title, instance_id, client_id, action_msg)) output_objects.append({ 'object_type': 'error_text', 'text': 'Your %s instance %s at %s did not succeed: %s' % (action, _label, service_title, action_msg) }) return (output_objects, returnvalues.SYSTEM_ERROR) output_objects.append({ 'object_type': 'text', 'text': "%s instance %s at %s" % (action, _label, service_title) }) output_objects.append({ 'object_type': 'link', 'destination': action_msg, 'text': 'Open web console', 'class': 'consolelink iconspace', 'title': 'open web console', 'target': '_blank' }) output_objects.append({'object_type': 'text', 'text': ''}) output_objects.append({ 'object_type': 'html_form', 'text': _ssh_help(configuration, client_id, cloud_id, saved_instance, instance_id) }) elif "updatekeys" == action: saved_instance = cloud_load_instance(configuration, client_id, cloud_id, instance_id) if not saved_instance: logger.error("no saved %s cloud instance %s for %s to update" % (cloud_id, instance_id, client_id)) output_objects.append({ 'object_type': 'error_text', 'text': instance_missing_msg % (_label, service_title) }) return (output_objects, returnvalues.CLIENT_ERROR) cloud_settings = load_cloud(client_id, configuration) auth_keys = cloud_settings.get('authkeys', '').split('\n') (action_status, action_msg) = update_cloud_instance_keys(configuration, client_id, cloud_id, cloud_flavor, instance_id, auth_keys) if not action_status: logger.error( "%s %s cloud instance %s for %s failed: %s" % (action, cloud_id, instance_id, client_id, action_msg)) output_objects.append({ 'object_type': 'error_text', 'text': 'Your %s instance %s at %s did not succeed: %s' % (action, _label, service_title, action_msg) }) return (output_objects, returnvalues.SYSTEM_ERROR) output_objects.append({ 'object_type': 'text', 'text': "%s instance %s at %s: %s" % (action, _label, service_title, "success") }) output_objects.append({ 'object_type': 'html_form', 'text': _ssh_help(configuration, client_id, cloud_id, saved_instance, instance_id) }) output_objects.append({'object_type': 'text', 'text': ssh_auth_msg}) for pub_key in auth_keys: output_objects.append({'object_type': 'text', 'text': pub_key}) else: output_objects.append({ 'object_type': 'error_text', 'text': 'Unknown action: %s' % action }) return_status = returnvalues.CLIENT_ERROR output_objects.append({ 'object_type': 'link', 'destination': 'cloud.py', 'class': 'backlink iconspace', 'title': 'Go back to cloud management', 'text': 'Back to cloud management' }) return (output_objects, return_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) title_entry = find_entry(output_objects, 'title') title_entry['text'] = 'Dashboard' # jquery support for tablesorter and confirmation on "leave": add_import, add_init, add_ready = '', '', '' add_init += ''' function roundNumber(num, dec) { var result = Math.round(num*Math.pow(10,dec))/Math.pow(10,dec); return result; } ''' add_ready += ''' $("#jobs_stats").addClass("spinner iconleftpad"); $("#jobs_stats").html("Loading job stats..."); $("#res_stats").addClass("spinner iconleftpad"); $("#res_stats").html("Loading resource stats..."); $("#disk_stats").addClass("spinner iconleftpad"); $("#disk_stats").html("Loading disk stats..."); $("#cert_stats").addClass("spinner iconleftpad"); $("#cert_stats").html("Loading certificate information..."); /* Run certificate request in the background and handle as soon as results come in */ $.ajax({ url: "userstats.py?output_format=json;stats=certificate", type: "GET", dataType: "json", cache: false, success: function(jsonRes, textStatus) { var i = 0; var certificate = null; var renew_days = 30; var day_msecs = 24*60*60*1000; // Grab results from json response and place them in resource status. for(i=0; i<jsonRes.length; i++) { if (jsonRes[i].object_type == "user_stats") { certificate = jsonRes[i].certificate; $("#cert_stats").removeClass("spinner iconleftpad"); $("#cert_stats").empty(); if (certificate.expire == -1) { break; } var expire_date = new Date(certificate.expire); $("#cert_stats").append("Your user certificate expires on " + expire_date + "."); // Use date from time diff in ms to avoid calendar mangling var show_renew = new Date(expire_date.getTime() - renew_days*day_msecs); if(new Date().getTime() > show_renew.getTime()) { $("#cert_stats").addClass("warningtext"); $("#cert_stats").append(" <a class=\'certrenewlink iconspace\' href=\'reqcert.py\'>Renew certificate</a>."); } break; } } } }); /* Run jobs request in the background and handle as soon as results come in */ $.ajax({ url: "userstats.py?output_format=json;stats=jobs", type: "GET", dataType: "json", cache: false, success: function(jsonRes, textStatus) { var i = 0; var jobs = null; // Grab results from json response and place them in job status. for(i=0; i<jsonRes.length; i++) { if (jsonRes[i].object_type == "user_stats") { jobs = jsonRes[i].jobs; //alert("inspect stats result: " + jobs); $("#jobs_stats").removeClass("spinner iconleftpad"); $("#jobs_stats").empty(); $("#jobs_stats").append("You have submitted a total of " + jobs.total + " jobs: " + jobs.parse + " parse, " + jobs.queued + " queued, " + jobs.frozen + " frozen, " + jobs.executing + " executing, " + jobs.finished + " finished, " + jobs.retry + " retry, " + jobs.canceled + " canceled, " + jobs.expired + " expired and " + jobs.failed + " failed."); break; } } } }); /* Run resources request in the background and handle as soon as results come in */ $.ajax({ url: "userstats.py?output_format=json;stats=resources", type: "GET", dataType: "json", cache: false, success: function(jsonRes, textStatus) { var i = 0; var resources = null; // Grab results from json response and place them in resource status. for(i=0; i<jsonRes.length; i++) { if (jsonRes[i].object_type == "user_stats") { resources = jsonRes[i].resources; //alert("inspect resources stats result: " + resources); $("#res_stats").removeClass("spinner iconleftpad"); $("#res_stats").empty(); $("#res_stats").append(resources.resources + " resources providing " + resources.exes + " execution units in total allow execution of your jobs."); break; } } } }); /* Run disk request in the background and handle as soon as results come in */ $.ajax({ url:"userstats.py?output_format=json;stats=disk", type: "GET", dataType: "json", cache: false, success: function(jsonRes, textStatus) { var i = 0; var disk = null; // Grab results from json response and place them in resource status. for(i=0; i<jsonRes.length; i++) { if (jsonRes[i].object_type == "user_stats") { disk = jsonRes[i].disk; //alert("inspect disk stats result: " + disk); $("#disk_stats").removeClass("spinner iconleftpad"); $("#disk_stats").empty(); $("#disk_stats").append("Your own " + disk.own_files +" files and " + disk.own_directories + " directories take up " + roundNumber(disk.own_megabytes, 2) + " MB in total and you additionally share " + disk.vgrid_files + " files and " + disk.vgrid_directories + " directories of " + roundNumber(disk.vgrid_megabytes, 2) + " MB in total."); break; } } } }); ''' 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': 'Dashboard'}) output_objects.append({ 'object_type': 'sectionheader', 'text': "Welcome to the %s" % configuration.site_title }) welcome_line = "Hi %s" % extract_field(client_id, "full_name") output_objects.append({'object_type': 'text', 'text': welcome_line}) dashboard_info = """ This is your private entry page or your dashboard where you can get a quick status overview and find pointers to help and documentation. When you are logged in with your user credentials/certificate, as you are now, you can navigate your pages using the menu on the left. """ % os.environ output_objects.append({'object_type': 'text', 'text': dashboard_info}) output_objects.append({ 'object_type': 'sectionheader', 'text': "Your Status" }) output_objects.append({ 'object_type': 'html_form', 'text': ''' <p> This is a general status overview for your Grid activities. Please note that some of the numbers are cached for a while to keep server load down. </p> <div id="jobs_stats"><!-- for jquery --></div><br /> <div id="res_stats"><!-- for jquery --></div><br /> <div id="disk_stats"><!-- for jquery --></div><br /> <div id="cert_stats"><!-- for jquery --></div><br /> <div id="cert_renew" class="hidden"><a href="reqcert.py">renew certificate</a> </div> ''' }) output_objects.append({ 'object_type': 'sectionheader', 'text': 'Documentation and Help' }) online_help = """ %s includes some built-in documentation like the """ % configuration.site_title output_objects.append({'object_type': 'text', 'text': online_help}) output_objects.append({ 'object_type': 'link', 'destination': 'docs.py', 'class': 'infolink iconspace', 'title': 'built-in documentation', 'text': 'Docs page' }) project_info = """ but additional help, background information and tutorials are available in the """ output_objects.append({'object_type': 'text', 'text': project_info}) output_objects.append({ 'object_type': 'link', 'destination': configuration.site_external_doc, 'class': 'urllink iconspace', 'title': 'external documentation', 'text': 'external %s documentation' % configuration.site_title }) output_objects.append({ 'object_type': 'sectionheader', 'text': "Personal Settings" }) settings_info = """ You can customize your personal pages by opening the Settings page from the navigation menu and entering personal preferences. In that way you can completely redecorate your interface and configure things like notification, profile visibility and remote file access. """ output_objects.append({'object_type': 'text', 'text': settings_info}) #env_info = """Env %s""" % os.environ #output_objects.append({'object_type': 'text', 'text': env_info}) 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'] = '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)