Example #1
0
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
Example #2
0
def main(client_id, user_arguments_dict, environ=None):
    """Main function used by front end"""

    if environ is None:
        environ = os.environ
    (configuration, logger, output_objects, op_name) = \
        initialize_main_variables(client_id, op_header=False,
                                  op_menu=False)
    logger = configuration.logger
    logger.info('%s: args: %s' % (op_name, user_arguments_dict))
    prefilter_map = {}

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

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

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

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

    # Extract raw values

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

        # We may receive multiple roles and associations

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

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

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

        # We may receive multiple roles and associations

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

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

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

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

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

    if auth_type in (AUTH_OPENID_V2, AUTH_OPENID_CONNECT):

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

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

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

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

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

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

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

    # we should have the proxy file read...

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

    # keep comment to a single line

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

    # single quotes break command line format - remove

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

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

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

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

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

        return (output_objects, returnvalues.CLIENT_ERROR)

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

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

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

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

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

    # Save auth access method

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

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

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

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

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

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

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

                # save the file, display expiration date

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

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

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

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

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

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

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

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

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

    else:
        logger.warning('autocreate disabled and refused for %s' % client_id)
        output_objects.append({
            'object_type': 'error_text', 'text': """Automatic user creation
disabled on this site. Please contact the site admins (%(admin_email)s) if you
think it should be enabled.
""" % fill_helper})
        return (output_objects, returnvalues.ERROR)
Example #3
0
def main(client_id, user_arguments_dict):
    """Main function used by front end"""

    (configuration, logger, output_objects, op_name) = \
        initialize_main_variables(client_id, op_header=False, op_menu=False)
    defaults = signature()[1]
    logger.debug('in extoidaction: %s' % user_arguments_dict)
    (validate_status, accepted) = validate_input(user_arguments_dict,
                                                 defaults,
                                                 output_objects,
                                                 allow_rejects=False)
    if not validate_status:
        return (accepted, returnvalues.CLIENT_ERROR)

    # Unfortunately OpenID does not use POST
    # if not safe_handler(configuration, 'post', op_name, client_id,
    #                    get_csrf_limit(configuration), accepted):
    #    output_objects.append(
    #        {'object_type': 'error_text', 'text': '''Only accepting
# CSRF-filtered POST requests to prevent unintended updates'''
#         })
#    return (output_objects, returnvalues.CLIENT_ERROR)

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

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

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

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

    # lower case email address

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

    # keep comment to a single line

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

    # single quotes break command line format - remove

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

    if not safe_handler(configuration, 'post', op_name, client_id,
                        get_csrf_limit(configuration), accepted):
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            '''Only accepting
CSRF-filtered POST requests to prevent unintended updates'''
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

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

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

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

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

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

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

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

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


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

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

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

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

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

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

---

""" % user_dict

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

    output_objects.append({
        'object_type':
        'text',
        'text':
        """Request sent to site administrators: Your user account will
be created as soon as possible, so please be patient. Once handled an email
will be sent to the account you have specified ('%s') with further information.
In case of inquiries about this request, please email the site administrators
(%s) and include the session ID: %s""" %
        (email, configuration.admin_email, tmp_id)
    })
    return (output_objects, returnvalues.OK)
Example #4
0
def main(client_id, user_arguments_dict):
    """Main function used by front end"""

    (configuration, logger, output_objects, op_name) = \
        initialize_main_variables(client_id, op_header=False, op_menu=False)
    defaults = signature()[1]
    (validate_status, accepted) = validate_input(user_arguments_dict,
                                                 defaults,
                                                 output_objects,
                                                 allow_rejects=False)
    if not validate_status:
        logger.warning('%s invalid input: %s' % (op_name, accepted))
        return (accepted, returnvalues.CLIENT_ERROR)

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

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

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

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

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

    # lower case email address

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

    # keep comment to a single line

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

    # single quotes break command line format - remove

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

    if not safe_handler(configuration, 'post', op_name, client_id,
                        get_csrf_limit(configuration), accepted):
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            '''Only accepting
CSRF-filtered POST requests to prevent unintended updates'''
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

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

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

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

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

    # TODO: move this check to conf?

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

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

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

    # For testing only

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

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

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

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

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

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

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

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


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

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

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

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

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

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

---

""" % user_dict

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

    output_objects.append({
        'object_type':
        'text',
        'text':
        """Request sent to site administrators:
Your OpenID account request will be verified and handled as soon as possible,
so please be patient.
Once handled an email will be sent to the account you have specified ('%s') with
further information. In case of inquiries about this request, please email
the site administrators (%s) and include the session ID: %s""" %
        (email, configuration.admin_email, tmp_id)
    })
    return (output_objects, returnvalues.OK)
Example #5
0
def main(client_id, user_arguments_dict):
    """Main function used by front end"""

    (configuration, logger, output_objects, op_name) = \
        initialize_main_variables(client_id, op_header=False, op_menu=False)
    defaults = signature()[1]
    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)
Example #6
0
def main(client_id, user_arguments_dict):
    """Main function used by front end"""

    (configuration, logger, output_objects, op_name) = \
        initialize_main_variables(client_id, op_header=False)
    output_objects.append({
        'object_type':
        'header',
        'text':
        '%s external certificate sign up' % configuration.short_title
    })

    defaults = signature()[1]
    (validate_status, accepted) = validate_input_and_cert(user_arguments_dict,
                                                          defaults,
                                                          output_objects,
                                                          client_id,
                                                          configuration,
                                                          allow_rejects=False,
                                                          require_user=False)
    if not validate_status:
        logger.warning('%s invalid input: %s' % (op_name, accepted))
        return (accepted, returnvalues.CLIENT_ERROR)

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

    cert_id = accepted['cert_id'][-1].strip()

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

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

    # lower case email address

    email = accepted['email'][-1].strip().lower()

    # keep comment to a single line

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

    # single quotes break command line format - remove

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

    if not safe_handler(configuration, 'post', op_name, client_id,
                        get_csrf_limit(configuration), accepted):
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            '''Only accepting
CSRF-filtered POST requests to prevent unintended updates'''
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

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

    is_diku_email = False
    is_diku_org = False
    if email.find('@diku.dk') != -1:
        is_diku_email = True
    if 'DIKU' == org.upper():

        # Consistent upper casing

        org = org.upper()
        is_diku_org = True

    if is_diku_org != is_diku_email:
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            '''Illegal email and organization combination:
Please read and follow the instructions in red on the request page!
If you are a DIKU student with only a @*.ku.dk address please just use KU as
organization.
As long as you state that you want the certificate for DIKU purposes in the
comment field, you will be given access to the necessary resources anyway.
'''
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    try:
        distinguished_name_to_user(cert_id)
    except:
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            '''Illegal Distinguished name:
Please note that the distinguished name must be a valid certificate DN with
multiple "key=val" fields separated by "/".
'''
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    user_dict = {
        'distinguished_name': cert_id,
        'full_name': cert_name,
        'organization': org,
        'state': state,
        'country': country,
        'email': email,
        'password': '',
        'comment': '%s: %s' % ('Existing certificate', comment),
        'expire': default_account_expire(configuration, 'cert'),
        'openid_names': [],
        'auth': ['extcert'],
    }
    fill_distinguished_name(user_dict)
    user_id = user_dict['distinguished_name']
    if configuration.user_openid_providers and configuration.user_openid_alias:
        user_dict['openid_names'] += \
            [user_dict[configuration.user_openid_alias]]
    logger.info('got extcert request: %s' % user_dict)

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

    if configuration.auto_add_cert_user:
        fill_user(user_dict)

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

        db_path = os.path.join(configuration.mig_server_home, user_db_filename)
        try:
            create_user(user_dict,
                        configuration.config_file,
                        db_path,
                        ask_renew=False)
        except Exception as err:
            logger.error('Failed to create user with existing cert %s: %s' %
                         (cert_id, err))
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                '''Could not create the user account for you:
Please report this problem to the site administrators (%s).''' % admin_email
            })
            return (output_objects, returnvalues.SYSTEM_ERROR)

        output_objects.append({
            'object_type':
            'text',
            'text':
            '''Created the user account for you:
Please use the navigation menu to the left to proceed using it.
'''
        })
        return (output_objects, returnvalues.OK)

    # Without auto add we end here and go through the mail-to-admins procedure
    req_path = None
    try:
        (os_fd, req_path) = tempfile.mkstemp(dir=user_pending)
        os.write(os_fd, dumps(user_dict))
        os.close(os_fd)
    except Exception as err:
        logger.error('Failed to write existing certificate request to %s: %s' %
                     (req_path, err))
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            """Request could not be sent to site administrators. Please
contact them manually on %s if this error persists.""" % admin_email
        })
        return (output_objects, returnvalues.SYSTEM_ERROR)

    logger.info('Wrote existing certificate sign up request to %s' % req_path)
    tmp_id = req_path.replace(user_pending, '')
    user_dict['tmp_id'] = tmp_id

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

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

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

---

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

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

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

""" % user_dict

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

    output_objects.append({
        'object_type':
        'text',
        'text':
        """Request sent to site administrators: Your request for a %s user
account with your existing certificate will be verified and handled as soon as
possible, so please be patient. In case of inquiries about this request,
please email the site administrators (%s) and include the session ID: %s""" %
        (configuration.short_title, admin_email, tmp_id)
    })
    return (output_objects, returnvalues.OK)