Exemple #1
0
 def symlink(self, target_path, path):
     """Handle operations of same name"""
     target_path = force_utf8(target_path)
     path = force_utf8(path)
     self.logger.debug('symlink %s %s' % (target_path, path))
     # Prevent users from creating symlinks for security reasons
     self.logger.error("symlink rejected on path %s :: %s" % (target_path,
                                                              path))
     return paramiko.SFTP_OP_UNSUPPORTED
Exemple #2
0
 def rename(self, oldpath, newpath):
     """Handle operations of same name"""
     oldpath = force_utf8(oldpath)
     newpath = force_utf8(newpath)
     self.logger.debug("rename %s %s" % (oldpath, newpath))
     try:
         real_oldpath = self._get_fs_path(oldpath)
     except ValueError, err:
         self.logger.warning('rename %s %s: %s' % (oldpath, newpath, err))
         return paramiko.SFTP_PERMISSION_DENIED
Exemple #3
0
def warn_on_rejects(rejects, output_objects):
    """Helper to fill in output_objects in case of rejects"""
    if rejects:
        for (key, err_list) in rejects.items():
            for err in err_list:
                output_objects.append({
                    'object_type':
                    'error_text',
                    'text':
                    'input parsing error: %s: %s: %s' %
                    (key, force_utf8(err[0]), force_utf8(err[1]))
                })
Exemple #4
0
def list_country_codes(configuration):
    """Get a sorted list of available countries and their 2-letter ISO3166
    country code for use in country selection during account sign up.
    """
    logger = configuration.logger
    if iso3166 is None:
        logger.info("iso3166 module not available - manual country code entry")
        return False
    country_list = []
    for entry in iso3166.countries:
        name, code = force_utf8(entry.name), force_utf8(entry.alpha2)
        logger.debug("found country %s for code %s" % (name, code))
        country_list.append((name, code))
    return country_list
Exemple #5
0
def send_email(
    recipients,
    subject,
    message,
    logger,
    configuration,
    ):
    """Send message to recipients by email:
    Force utf8 encoding to avoid accented characters appearing garbled
    """

    recipients_list = recipients.split(', ')

    try:
        mime_msg = MIMEText(force_utf8(message), "plain", "utf8")
        mime_msg['Subject'] = subject
        mime_msg['From'] = configuration.smtp_sender
        mime_msg['To'] = recipients
        server = smtplib.SMTP(configuration.smtp_server)
        server.set_debuglevel(0)
        errors = server.sendmail(configuration.smtp_sender,
                                 recipients_list, mime_msg.as_string())
        server.quit()
        if errors:
            logger.warning('Partial error(s) sending email: %s'
                            % errors)
            return False
        else:
            logger.debug('Email was sent to %s' % recipients)
            return True
    except Exception, err:
        logger.error('Sending email to %s through %s failed!: %s'
                      % (recipients, configuration.smtp_server,
                     str(err)))
        return False
Exemple #6
0
    def __str__(self):
        """Byte string formater - username is already forced to utf8 so other
        strings are converted here as well.
        """
        out = '''username: %s
home: %s''' % (self.username, self.home)
        if self.password:
            out += '''
password: %s''' % force_utf8(self.password)
        if self.digest:
            out += '''
digest: %s''' % force_utf8(self.digest)
        if self.public_key:
            out += '''
pubkey: %s''' % force_utf8(self.public_key.get_base64())
        out += '''
last_update: %s''' % self.last_update
        return out
Exemple #7
0
 def readlink(self, path):
     """Handle operations of same name"""
     path = force_utf8(path)
     self.logger.debug("readlink %s" % path)
     try:
         real_path = self._get_fs_path(path)
     except ValueError, err:
         self.logger.warning('readlink %s: %s' % (path, err))
         return paramiko.SFTP_PERMISSION_DENIED
Exemple #8
0
 def _chmod(self, path, mode, sftphandle=None):
     """Handle chmod for SimpleSftpServer and SFTPHandle"""
     file_obj = None
     path = force_utf8(path)
     self.logger.debug("_chmod %s" % path)
     try:
         real_path = self._get_fs_path(path)
     except ValueError, err:
         self.logger.warning('chmod %s: %s' % (path, err))
         return paramiko.SFTP_PERMISSION_DENIED
Exemple #9
0
    def __update_crontab_monitor(
        self,
        configuration,
        src_path,
        state,
    ):

        pid = multiprocessing.current_process().pid

        if state == 'created':

            # logger.debug('(%s) Updating crontab monitor for src_path: %s, event: %s'
            #              % (pid, src_path, state))

            print '(%s) Updating crontab monitor for src_path: %s, event: %s' \
                % (pid, src_path, state)

            if os.path.exists(src_path):

                # _crontab_monitor_lock.acquire()

                if not shared_state['crontab_inotify']._wd_for_path.has_key(src_path):

                    # logger.debug('(%s) Adding watch for: %s' % (pid,
                    #             src_path))

                    shared_state['crontab_inotify'].add_watch(
                        force_utf8(src_path))

                    # Fire 'modified' events for all dirs and files in subpath
                    # to ensure that all crontab files are loaded

                    for ent in scandir(src_path):
                        if ent.is_dir(follow_symlinks=True):

                            # logger.debug('(%s) Dispatch DirCreatedEvent for: %s'
                            #         % (pid, ent.path))

                            shared_state['crontab_handler'].dispatch(
                                DirCreatedEvent(ent.path))
                        elif ent.path.find(configuration.user_settings) \
                                > -1:

                            # logger.debug('(%s) Dispatch FileCreatedEvent for: %s'
                            #         % (pid, ent.path))

                            shared_state['crontab_handler'].dispatch(
                                FileCreatedEvent(ent.path))

                # else:
                #    logger.debug('(%s) crontab_monitor watch already exists for: %s'
                #                  % (pid, src_path))
        else:
            logger.debug('(%s) unhandled event: %s for: %s' % (pid,
                                                               state, src_path))
Exemple #10
0
def generate_ssh_rsa_key_pair(size=2048,
                              public_key_prefix='',
                              public_key_postfix='',
                              encode_utf8=False):
    """Generates ssh rsa key pair"""

    if paramiko is None:
        raise Exception("You need paramiko to provide the ssh/sftp service")
    rsa_key = paramiko.RSAKey.generate(size)

    string_io_obj = StringIO.StringIO()
    rsa_key.write_private_key(string_io_obj)

    private_key = string_io_obj.getvalue()
    public_key = (
        "%s ssh-rsa %s %s" %
        (public_key_prefix, rsa_key.get_base64(), public_key_postfix)).strip()
    if encode_utf8:
        private_key = force_utf8(private_key)
        public_key = force_utf8(public_key)

    return (private_key, public_key)
Exemple #11
0
def get_twofactor_token(configuration, client_id, b32_key):
    """Get current twofactor taken for base32 key"""
    _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)
    # IMPORTANT: pyotp unicode breaks when used in our strings - force utf8!
    totp = get_totp(client_id, b32_key, configuration)
    token = totp.now()
    token = force_utf8(token)
    return token
Exemple #12
0
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.
    # We could display with Google Charts helper like this example
    # https://www.google.com/chart?chs=200x200&chld=M|0&cht=qr&
    #       chl=otpauth://totp/Example:[email protected]?
    #       secret=JBSWY3DPEHPK3PXP&issuer=Example
    # but we prefer to use the QRious JS library to keep it 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)
    # IMPORTANT: pyotp unicode breaks wsgi when inserted - force utf8!
    otp_uri = force_utf8(otp_uri)

    # Google img examle
    # img_url = 'https://www.google.com/chart?'
    # img_url += urllib.urlencode([('cht', 'qr'), ('chld', 'M|0'),
    #                             ('chs', '200x200'), ('chl', otp_uri)])
    # otp_img = '<img src="%s" />' % img_url

    return (b32_key, totp.interval, otp_uri)
Exemple #13
0
def reset_twofactor_key(client_id, configuration, seed=None, interval=None):
    """Reset 2FA secret key and write to user settings file in scrambled form.
    Return the new secret key on unscrambled base32 form.
    """
    _logger = configuration.logger
    if configuration.site_enable_gdp:
        client_id = get_base_client_id(configuration,
                                       client_id,
                                       expand_oid_alias=False)
    client_dir = client_id_dir(client_id)
    key_path = os.path.join(configuration.user_settings, client_dir,
                            twofactor_key_name)
    try:
        if pyotp is None:
            raise Exception("The pyotp module is missing and required for 2FA")
        if not seed:
            b32_key = pyotp.random_base32(length=twofactor_key_bytes)
        else:
            b32_key = seed
        # NOTE: pyotp.random_base32 returns unicode
        #       which causes trouble with WSGI
        b32_key = force_utf8(b32_key)
        scrambled = scramble_password(configuration.site_password_salt,
                                      b32_key)
        key_fd = open(key_path, 'w')
        key_fd.write(scrambled)
        key_fd.close()

        # Reset interval

        interval_path = os.path.join(configuration.user_settings, client_dir,
                                     twofactor_interval_name)
        delete_file(interval_path, _logger, allow_missing=True)
        if interval:
            i_fd = open(interval_path, 'w')
            i_fd.write("%d" % interval)
            i_fd.close()
    except Exception, exc:
        _logger.error("failed in reset 2FA key: %s" % exc)
        return False
Exemple #14
0
def handle_package_upload(
    real_src,
    relative_src,
    client_id,
    configuration,
    submit_mrslfiles,
    dst,
    ):
    """A file package was uploaded (eg. .zip file). Extract the content and
    submit mrsl files if submit_mrsl_files is True.
    """
    logger = configuration.logger
    msg = ''
    status = True

    logger.info("handle_package_upload %s %s %s" % \
                (real_src, relative_src, dst))

    client_dir = client_id_dir(client_id)

    # Please note that base_dir must end in slash to avoid access to other
    # user dirs when own name is a prefix of another user name

    base_dir = os.path.abspath(os.path.join(configuration.user_home,
                               client_dir)) + os.sep

    # Unpack in same directory unless real_dst is given

    if not dst:
        real_dst = os.path.abspath(os.path.dirname(real_src))
    elif os.path.isabs(dst):
        real_dst = os.path.abspath(dst)
    else:
        real_dst = os.path.join(base_dir, dst)
    real_dst += os.sep
    mrslfiles_to_parse = []

    real_src_lower = real_src.lower()
    if real_src_lower.endswith('.zip'):

        # Handle .zip file

        msg += "Received '%s' for unpacking. " % relative_src
        try:
            zip_object = zipfile.ZipFile(real_src, 'r', allowZip64=True)
        except Exception, exc:
            logger.error("open zip failed: %s" % exc)
            msg += 'Could not open zipfile: %s! ' % exc
            return (False, msg)

        logger.info("unpack entries of %s to %s" % \
                                  (real_src, real_dst))
        for zip_entry in zip_object.infolist():
            entry_filename = force_utf8(zip_entry.filename)
            msg += 'Extracting: %s . ' % entry_filename

            # write zip_entry to disk

            # IMPORTANT: we must abs-expand for valid_user_path_name check
            #            otherwise it will incorrectly fail on e.g. abc/
            #            dir entry in archive
            local_zip_entry_name = os.path.join(real_dst, entry_filename)
            valid_status, valid_err = valid_user_path_name(
                entry_filename, os.path.abspath(local_zip_entry_name),
                base_dir)
            if not valid_status:
                status = False
                msg += "Filename validation error: %s! " % valid_err
                continue

            # create sub dir(s) if missing

            zip_entry_dir = os.path.dirname(local_zip_entry_name)

            if not os.path.isdir(zip_entry_dir):
                msg += 'Creating dir %s . ' % entry_filename
                try:
                    os.makedirs(zip_entry_dir, 0775)
                except Exception, exc:
                    logger.error("create directory failed: %s" % exc)
                    msg += 'Error creating directory: %s! ' % exc
                    status = False
                    continue

            if os.path.isdir(local_zip_entry_name):
                logger.debug("nothing more to do for dir entry: %s" % \
                            local_zip_entry_name)
                continue

            try:
                zip_data = zip_object.read(zip_entry.filename)
            except Exception, exc:
                logger.error("read data in %s failed: %s" % \
                             (zip_entry.filename, exc))
                msg += 'Error reading %s :: %s! ' % (zip_entry.filename, exc)
                status = False
                continue
Exemple #15
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 correct_handler('POST'):
        output_objects.append(
            {'object_type': 'error_text', 'text'
             : 'Only accepting POST requests to prevent unintended updates'})
        return (output_objects, returnvalues.CLIENT_ERROR)

    title_entry = find_entry(output_objects, 'title')
    title_entry['text'] = '%s certificate request' % configuration.short_title
    title_entry['skipmenu'] = True
    output_objects.append({'object_type': 'header', 'text'
                          : '%s certificate request' % \
                            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)
    # 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().title()
    org = accepted['org'][-1].strip()

    # lower case email address

    email = accepted['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("'", ' ')

    if password != verifypassword:
        output_objects.append({'object_type': 'error_text', 'text'
                              : 'Password and verify password are not identical!'
                              })
        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 certificate for course
purposes in the comment field, you will be given access to the necessary
resources anyway.
'''})
        return (output_objects, returnvalues.CLIENT_ERROR)

    user_dict = {
        'full_name': cert_name,
        'organization': org,
        'state': state,
        'country': country,
        'email': email,
        'comment': comment,
        'password': base64.b64encode(password),
        'expire': int(time.time() + cert_valid_days * 24 * 60 * 60),
        'openid_names': [],
        }
    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 reqcert request: %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, err:
        logger.error('Failed to write certificate request to %s: %s'
                      % (req_path, err))
        output_objects.append({'object_type': 'error_text', 'text'
                              : 'Request could not be sent to grid administrators. Please contact them manually on %s if this error persists.'
                               % admin_email})
        return (output_objects, returnvalues.SYSTEM_ERROR)
Exemple #16
0
                logger.error("open tar bz failed: %s" % exc)
                msg += 'Could not open .tar.bz2 file: %s! ' % exc
                return (False, msg)
        else:
            try:
                tar_object = tarfile.open(real_src, 'r')
                tar_file_content = tarfile.TarFile.open(real_src)
            except Exception, exc:
                logger.error("open tar failed: %s" % exc)
                msg += 'Could not open .tar file: %s! ' % exc
                return (False, msg)

        logger.info("unpack entries of %s to %s" % \
                                  (real_src, real_dst))
        for tar_entry in tar_object:
            entry_filename = force_utf8(tar_entry.name)
            msg += 'Extracting: %s . ' % entry_filename

            # write tar_entry to disk

            # IMPORTANT: we must abs-expand for valid_user_path_name check
            #            otherwise it will incorrectly fail on e.g. abc/
            #            dir entry in archive
            local_tar_entry_name = os.path.join(real_dst, entry_filename)
            valid_status, valid_err = valid_user_path_name(
                entry_filename, os.path.abspath(local_tar_entry_name),
                base_dir)
            if not valid_status:
                status = False
                msg += "Filename validation error: %s! " % valid_err
                continue
Exemple #17
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)

    if not correct_handler('POST'):
        output_objects.append(
            {'object_type': 'error_text', 'text'
             : 'Only accepting POST requests to prevent unintended updates'})
        return (output_objects, 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()

    # 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().title()
    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("'", ' ')

    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': int(time.time() + cert_valid_days * 24 * 60 * 60),
        'openid_names': [],
        }
    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, 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 grid 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)
Exemple #18
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()

    # 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().title()
    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("'", ' ')

    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)

    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': int(time.time() + cert_valid_days * 24 * 60 * 60),
        '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, 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 grid 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)
Exemple #19
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
    })
    (_, identity) = extract_client_openid(configuration,
                                          environ,
                                          lookup_dn=False)
    req_url = environ['SCRIPT_URI']
    if client_id and client_id == identity:
        login_type = 'cert'
        if req_url.startswith(configuration.migserver_https_mig_cert_url):
            base_url = configuration.migserver_https_mig_cert_url
        elif req_url.startswith(configuration.migserver_https_ext_cert_url):
            base_url = configuration.migserver_https_ext_cert_url
        else:
            logger.warning('no match for cert request URL: %s' % req_url)
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                'No matching request URL: %s' % req_url
            })
            return (output_objects, returnvalues.SYSTEM_ERROR)
    elif identity:
        login_type = 'oid'
        if req_url.startswith(configuration.migserver_https_mig_oid_url):
            base_url = configuration.migserver_https_mig_oid_url
        elif req_url.startswith(configuration.migserver_https_ext_oid_url):
            base_url = configuration.migserver_https_ext_oid_url
        else:
            logger.warning('no match for oid request URL: %s' % req_url)
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                'No matching request URL: %s' % req_url
            })
            return (output_objects, returnvalues.SYSTEM_ERROR)
        for name in ('openid.sreg.cn', 'openid.sreg.fullname',
                     'openid.sreg.full_name'):
            prefilter_map[name] = filter_commonname
    else:
        output_objects.append({
            'object_type': 'error_text',
            'text': 'Missing user credentials'
        })
        return (output_objects, returnvalues.CLIENT_ERROR)
    defaults = signature(login_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 invalid input: %s' % (op_name, accepted))
        return (accepted, returnvalues.CLIENT_ERROR)

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

    # Unfortunately OpenID redirect does not use POST

    if login_type != 'oid' and 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)

    admin_email = configuration.admin_email
    (openid_names, oid_extras) = ([], {})

    # Extract raw values

    if login_type == 'cert':
        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 = ''
        role = ','.join([i for i in accepted['role'] if i])
        association = ','.join([i for i in accepted['association'] if i])
        locality = ''
        timezone = ''
        email = accepted['email'][-1].strip()
        raw_login = None
    elif login_type == 'oid':
        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

    # 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()

    if login_type == 'oid':

        # 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

        if configuration.site_enable_gdp:
            base_url = environ.get('REQUEST_URI',
                                   base_url).split('?')[0].replace(
                                       'autocreate', 'gdpman')
        else:
            base_url = environ.get('REQUEST_URI',
                                   base_url).split('?')[0].replace(
                                       'autocreate', 'fileman')
        raw_login = None
        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

    if raw_login:
        openid_names.append(raw_login)

    # 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("'", ' ')

    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': '%s: %s' % ('Existing certificate', comment),
        'openid_names': openid_names,
    }
    user_dict.update(oid_extras)

    # We must receive some ID from the provider

    if not uniq_id and not email:
        if accepted.get('openid.sreg.required', '') and identity:
            output_objects.append({
                'object_type':
                'html_form',
                'text':
                '''<p class="spinner iconleftpad">
Auto log out first to avoid sign up problems ...
</p>'''
            })
            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})
        return (output_objects, returnvalues.CLIENT_ERROR)

    auth = 'unknown'
    if login_type == 'cert':
        auth = 'extcert'
        user_dict['expire'] = int(time.time() + cert_valid_days * 24 * 60 * 60)
        try:
            distinguished_name_to_user(uniq_id)
            user_dict['distinguished_name'] = uniq_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)
    elif login_type == 'oid':
        auth = 'extoid'
        user_dict['expire'] = int(time.time() + oid_valid_days * 24 * 60 * 60)
        fill_distinguished_name(user_dict)
        uniq_id = user_dict['distinguished_name']

    # Save auth access method

    user_dict['auth'] = [auth]

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

    if login_type == 'cert' and configuration.auto_add_cert_user \
        or login_type == 'oid' 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, 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 grid administrators (%s).''' % admin_email
            })
            return (output_objects, returnvalues.SYSTEM_ERROR)

        logger.info('created user account for %s' % uniq_id)
        output_objects.append({
            'object_type':
            'html_form',
            'text':
            '''Created the user account for you -
please open <a href="%s">your personal page</a> to proceed using it.
''' % base_url
        })
        return (output_objects, returnvalues.OK)
Exemple #20
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 })
    identity = extract_client_openid(configuration, environ, lookup_dn=False)
    if client_id and client_id == identity:
        login_type = 'cert'
        base_url = configuration.migserver_https_cert_url
    elif identity:
        login_type = 'oid'
        base_url = configuration.migserver_https_oid_url
        for name in ('openid.sreg.cn', 'openid.sreg.fullname',
                     'openid.sreg.full_name'):
            prefilter_map[name] = filter_commonname
    else:
        output_objects.append(
            {'object_type': 'error_text', 'text': 'Missing user credentials'})
        return (output_objects, returnvalues.CLIENT_ERROR)
    defaults = signature(login_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 invalid input: %s' % (op_name, accepted))
        return (accepted, returnvalues.CLIENT_ERROR)

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

    # Unfortunately OpenID redirect does not use POST
    if login_type != 'oid' and not correct_handler('POST'):
        output_objects.append(
            {'object_type': 'error_text', 'text'
             : 'Only accepting POST requests to prevent unintended updates'})
        return (output_objects, returnvalues.CLIENT_ERROR)

    admin_email = configuration.admin_email
    openid_names, oid_extras = [], {}

    # Extract raw values
    if login_type == 'cert':
        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 = ''
        role = ','.join([i for i in accepted['role'] if i])
        locality = ''
        timezone = ''
        email = accepted['email'][-1].strip()
        raw_login = None
    elif login_type == 'oid':
        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
        role = ','.join([i for i in accepted['openid.sreg.role'] if i])
        locality = accepted['openid.sreg.locality'][-1].strip()
        timezone = accepted['openid.sreg.timezone'][-1].strip()
        email = accepted['openid.sreg.email'][-1].strip()

    # 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()

    if login_type == 'oid':
        # 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.
        # 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'

        # Stay on virtual host - extra useful while we test dual OpenID
        base_url = environ.get('REQUEST_URI',
                               base_url).split('?')[0].replace('autocreate',
                                                               'fileman')
        raw_login = None
        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

    if raw_login:
        openid_names.append(raw_login)

    # 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("'", ' ')

    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,
        'timezone': timezone,
        'password': '',
        'comment': '%s: %s' % ('Existing certificate', comment),
        'openid_names': openid_names,
        }
    user_dict.update(oid_extras)

    # We must receive some ID from the provider
    if not uniq_id and not email:
        output_objects.append(
            {'object_type': 'error_text', 'text'
             : 'No ID information received!'})
        if accepted.get('openid.sreg.required', '') and \
               identity:
            # Stay on virtual host - extra useful while we test dual OpenID
            url = environ.get('REQUEST_URI',
                              base_url).split('?')[0].replace('autocreate',
                                                              'logout')
            output_objects.append(
                {'object_type': 'text', 'text': '''Please note that sign-up
for OpenID access does not work if you are already signed in with your OpenID
provider - and that appears to be the case now.
You probably have to reload this page after you explicitly '''})
            output_objects.append(        
                {'object_type': 'link', 'destination': url,
                 'target': '_blank', 'text': "Logout"
                 })
        return (output_objects, returnvalues.CLIENT_ERROR)

    if login_type == 'cert':
        user_dict['expire'] = int(time.time() + cert_valid_days * 24 * 60 * 60)
        try:
            distinguished_name_to_user(uniq_id)
            user_dict['distinguished_name'] = uniq_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)
    elif login_type == 'oid':
        user_dict['expire'] = int(time.time() + oid_valid_days * 24 * 60 * 60)
        fill_distinguished_name(user_dict)
        uniq_id = user_dict['distinguished_name']

    # If server allows automatic addition of users with a CA validated cert
    # we create the user immediately and skip mail
    
    if login_type == 'cert' and configuration.auto_add_cert_user or \
           login_type == 'oid' 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, 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 grid administrators (%s).''' % \
                 admin_email})
            return (output_objects, returnvalues.SYSTEM_ERROR)

        output_objects.append({'object_type': 'html_form', 'text'
                                   : '''Created the user account for you -
please open <a href="%s">your personal page</a> to proceed using it.
''' % base_url})
        return (output_objects, returnvalues.OK)
Exemple #21
0
def send_email(recipients,
               subject,
               message,
               logger,
               configuration,
               files=[],
               custom_sender=None):
    """Send message to recipients by email:
    Force utf8 encoding to avoid accented characters appearing garbled.

    The optional custom_sender can be used to set the email sender in one of
    two ways depending on the configuration option smtp_send_as_user.
    In case that option is enabled custom_sender will be used as from and
    envelope sender address, the latter of which is used e.g. for bounces. It
    should be noted that doing so comes with the risk of legit mail getting
    spam-filtered because of failed SPF checks.
    If the configuration does not set smtp_send_as_user custom_sender will only
    be used as the Reply-To address which is still convenient when users send out
    invitations and trigger various requests that may receive manual replies. 
    """

    if recipients.find(', ') > -1:
        recipients_list = recipients.split(', ')
    else:
        recipients_list = [recipients]

    sender_email = from_email = configuration.smtp_sender
    reply_to_email = configuration.smtp_reply_to
    if custom_sender:
        # Only use custom envelope sender if specifically configured!
        if configuration.smtp_send_as_user:
            from_email = sender_email = custom_sender
            reply_to_email = ''
        else:
            reply_to_email = custom_sender

    try:
        mime_msg = MIMEMultipart()
        mime_msg['From'] = from_email
        mime_msg['To'] = recipients
        if reply_to_email:
            mime_msg['Reply-To'] = reply_to_email
        mime_msg['Date'] = formatdate(localtime=True)
        mime_msg['Subject'] = subject
        mime_msg.attach(MIMEText(force_utf8(message), "plain", "utf8"))

        for name in files:
            part = MIMEBase('application', "octet-stream")
            part.set_payload(open(name, "rb").read())
            Encoders.encode_base64(part)
            part.add_header(
                'Content-Disposition',
                'attachment; filename="%s"' % os.path.basename(name))
            mime_msg.attach(part)
        logger.debug('sending email from %s to %s:\n%s' %
                     (from_email, recipients, mime_msg.as_string()))
        server = smtplib.SMTP(configuration.smtp_server)
        server.set_debuglevel(0)
        errors = server.sendmail(sender_email, recipients_list,
                                 mime_msg.as_string())
        server.quit()
        if errors:
            logger.warning('Partial error(s) sending email: %s' % errors)
            return False
        else:
            logger.debug('Email was sent to %s' % recipients)
            return True
    except Exception, err:
        logger.error('Sending email to %s through %s failed!: %s' %
                     (recipients, configuration.smtp_server, str(err)))
        return False
Exemple #22
0
    def validate_authentication(self, username, password, handler):
        """Password auth against internal DB built from login_map.

        Please note that we take serious steps to secure against password
        cracking, but that it _may_ still be possible to achieve with a big
        effort.

        The following is checked before granting auth:
        1) Valid username
        2) Valid user (Does user exist and enabled ftps)
        3) Valid 2FA session (if 2FA is enabled)
        4) Hit rate limit (Too many auth attempts)
        5) Valid password (if password enabled)
        """
        secret = None
        disconnect = False
        strict_password_policy = True
        password_offered = None
        password_enabled = False
        invalid_username = False
        invalid_user = False
        valid_password = False
        valid_twofa = False
        exceeded_rate_limit = False
        client_ip = handler.remote_ip
        client_port = handler.remote_port
        username = force_utf8(username)
        daemon_conf = configuration.daemon_conf
        max_user_hits = daemon_conf['auth_limits']['max_user_hits']
        user_abuse_hits = daemon_conf['auth_limits']['user_abuse_hits']
        proto_abuse_hits = daemon_conf['auth_limits']['proto_abuse_hits']
        max_secret_hits = daemon_conf['auth_limits']['max_secret_hits']
        logger.debug("Run authentication of %s from %s" %
                     (username, client_ip))
        logger.info("refresh user %s" % username)
        logger.debug("daemon_conf['allow_password']: %s" %
                     daemon_conf['allow_password'])

        # For e.g. GDP we require all logins to match active 2FA session IP,
        # but otherwise user may freely switch net during 2FA lifetime.
        if configuration.site_twofactor_strict_address:
            enforce_address = client_ip
        else:
            enforce_address = None

        # We don't have a handle_request for server so expire here instead

        if self.last_expire + self.min_expire_delay < time.time():
            self.last_expire = time.time()
            expire_rate_limit(configuration,
                              "ftps",
                              expire_delay=self.min_expire_delay)
        if hit_rate_limit(configuration,
                          'ftps',
                          client_ip,
                          username,
                          max_user_hits=max_user_hits):
            exceeded_rate_limit = True
        elif not default_username_validator(configuration, username):
            invalid_username = True
        elif daemon_conf['allow_password']:
            hash_cache = daemon_conf['hash_cache']
            password_offered = password
            secret = make_scramble(password_offered, None)
            # Only sharelinks should be excluded from strict password policy
            if configuration.site_enable_sharelinks and \
                    possible_sharelink_id(configuration, username):
                strict_password_policy = False
            logger.debug("refresh user %s" % username)
            self._update_logins(configuration, username)
            if not self.has_user(username):
                if not os.path.islink(
                        os.path.join(daemon_conf['root_dir'], username)):
                    invalid_user = True
                entries = []
            else:
                # list of User login objects for username
                entries = [self.user_table[username]]
            for entry in entries:
                if entry['pwd'] is not None:
                    password_enabled = True
                    password_allowed = entry['pwd']
                    logger.debug("Password check for %s" % username)
                    if check_password_hash(configuration, 'ftps', username,
                                           password_offered, password_allowed,
                                           hash_cache, strict_password_policy):
                        valid_password = True
                        break
            if valid_password and check_twofactor_session(
                    configuration, username, enforce_address, 'ftps'):
                valid_twofa = True

        # Update rate limits and write to auth log

        (authorized, disconnect) = validate_auth_attempt(
            configuration,
            'ftps',
            'password',
            username,
            client_ip,
            client_port,
            secret=secret,
            invalid_username=invalid_username,
            invalid_user=invalid_user,
            valid_twofa=valid_twofa,
            authtype_enabled=password_enabled,
            valid_auth=valid_password,
            exceeded_rate_limit=exceeded_rate_limit,
            user_abuse_hits=user_abuse_hits,
            proto_abuse_hits=proto_abuse_hits,
            max_secret_hits=max_secret_hits,
        )

        if disconnect:
            handler._shutdown_connecting_dtp()
        if authorized:
            self.authenticated_user = username
            return True
        else:
            # Must raise AuthenticationFailed exception since version 1.0.0 instead
            # of returning bool
            self.authenticated_user = None
            raise AuthenticationFailed()
Exemple #23
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)

    # 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().title()
    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("'", ' ')

    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 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, 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)