Esempio n. 1
0
def login():
    """ This is the login route and processing of information
    """
    # Calls the new function in order to refresh the auth_source list
    login_form = forms.LoginForm.new()

    if current_user.is_authenticated:
        return redirect(url_for('common.index'))
    else:
        if request.method == 'GET':
            return render_template('login.html', title='PostMaster Login', loginForm=login_form)
        elif login_form.validate_on_submit():
            username = login_form.admin.username
            login_user(login_form.admin, remember=False)
            clear_lockout_fields_on_user(username)
            json_logger(
                'auth', username,
                'The administrator "{0}" logged in successfully'.format(
                    username))
            return redirect(request.args.get('next') or url_for(
                'common.index'))
        else:
            wtforms_errors = get_wtforms_errors(login_form)
            if wtforms_errors:
                flash(wtforms_errors)

    return redirect(url_for('common.login'))
Esempio n. 2
0
def delete_user(user_id):
    """ Deletes a user by ID in VirtualUsers, and returns HTTP 204 on success
    """
    user = VirtualUsers.query.get_or_404(user_id)

    aliases = VirtualAliases.query.filter_by(destination=user.email).all()
    if aliases:
        for alias in aliases:
            db.session.delete(alias)
    db.session.delete(user)
    try:
        db.session.commit()
        json_logger(
            'audit', current_user.username,
            'The user "{0}" was deleted successfully'.format(user.email))
    except ValidationError as e:
        raise e
    except Exception as e:
        db.session.rollback()
        json_logger(
            'error', current_user.username,
            'The following error occurred in delete_user: {0}'.format(str(e)))
        raise GenericError('The user could not be deleted')
    finally:
        db.session.close()
    return {}, 204
Esempio n. 3
0
def update_user(user_id):
    """ Updates a user by ID in VirtualUsers, and returns HTTP 200 on success
    """
    user = VirtualUsers.query.get_or_404(user_id)
    json = request.get_json(force=True)

    if 'password' in json:
        minPwdLength = int(Configs.query.filter_by(
            setting='Minimum Password Length').first().value)
        if len(json['password']) < minPwdLength:
            raise ValidationError(
                'The password must be at least {0} characters long'.format(
                    minPwdLength))
        user.password = VirtualUsers().encrypt_password(json['password'])
        auditMessage = 'The user "{0}" had their password changed'.format(
            user.email)
        db.session.add(user)
    else:
        raise ValidationError('The password was not supplied in the request')
    try:
        db.session.commit()
        json_logger('audit', current_user.username, auditMessage)
    except ValidationError as e:
        raise e
    except Exception as e:
        db.session.rollback()
        json_logger(
            'error', current_user.username,
            'The following error occurred in update_user: {0}'.format(str(e)))
        raise GenericError('The user could not be updated')
    finally:
        db.session.close()
    return {}, 200
Esempio n. 4
0
def update_config(config_id):
    """ Updates a config by ID in Configs, and returns HTTP 200 on success
    """
    config = Configs.query.get_or_404(config_id)
    json = request.get_json(force=True)

    if 'value' in json and is_config_update_valid(config.setting, json['value'], config.regex):
        audit_message = 'The setting "{0}" was set from "{1}" to "{2}"'.format(config.setting, config.value,
                                                                               json['value'])
        config.value = json['value']
        db.session.add(config)
    else:
        raise ValidationError('An invalid setting value was supplied')

    try:
        db.session.commit()
        json_logger('audit', current_user.username, audit_message)
    except ValidationError as e:
        raise e
    except Exception as e:
        db.session.rollback()
        json_logger(
            'error', current_user.username,
            'The following error occurred in update_config: {0}'.format(str(
                e)))
        raise GenericError('The configuration could not be updated')
    finally:
        db.session.close()
    return {}, 200
Esempio n. 5
0
def login():
    """ This is the login route and processing of information
    """
    # Calls the new function in order to refresh the auth_source list
    login_form = forms.LoginForm.new()

    if current_user.is_authenticated:
        return redirect(url_for('common.index'))
    else:
        if request.method == 'GET':
            return render_template('login.html',
                                   title='PostMaster Login',
                                   loginForm=login_form)
        elif login_form.validate_on_submit():
            username = login_form.admin.username
            login_user(login_form.admin, remember=False)
            clear_lockout_fields_on_user(username)
            log_msg = ('The administrator "{0}" logged in successfully'.format(
                username))
            json_logger('auth', username, log_msg)
            return redirect(
                request.args.get('next') or url_for('common.index'))
        else:
            wtforms_errors = get_wtforms_errors(login_form)
            if wtforms_errors:
                flash(wtforms_errors)

    return redirect(url_for('common.login'))
Esempio n. 6
0
def verify_qrcode(admin_id):
    """ Verifies if the 2 factor token provided is correct

        This will enable 2 factor for a user.
    """
    admin = Admins.query.get_or_404(admin_id)
    if request.get_json(force=True).get("code"):
        if not admin.otp_secret:
            raise GenericError("The 2 Factor Secret has not been generated yet")
        if admin.verify_totp(request.get_json(force=True).get("code")):
            if not admin.otp_active:
                audit_message = 'The administrator "{0}" enabled 2 factor'.format(admin.username)
                admin.otp_active = True
                try:
                    db.session.add(admin)
                    db.session.commit()
                    json_logger("audit", current_user.username, audit_message)
                except ValidationError as e:
                    raise e
                except Exception as e:
                    db.session.rollback()
                    json_logger(
                        "error",
                        current_user.username,
                        "The following error occurred in verify_qrcode: {0}".format(str(e)),
                    )
                    raise GenericError("The administrator could not be updated")
            return {}, 200
        else:
            raise GenericError("An invalid code was supplied")
    else:
        raise ValidationError("The code was not supplied")
Esempio n. 7
0
def twofactor_disable(admin_id):
    """ Disable 2 factor using API.

        Enabling 2 factor from this route is not possible.
    """
    admin = Admins.query.get_or_404(admin_id)
    status = request.get_json(force=True).get("enabled")
    if status:
        if status.lower() == "false":
            admin.otp_active = False
            try:
                db.session.add(admin)
                db.session.commit()
            except ValidationError as e:
                raise e
            except Exception as e:
                db.session.rollback()
                json_logger(
                    "error",
                    current_user.username,
                    "The following error occurred in twofactor_disable: {0}".format(str(e)),
                )
                raise GenericError("The administrator could not be updated")
            return {}, 200
        elif status.lower() == "true":
            raise GenericError("Cannot enable 2 factor from this route - see docs")
    raise GenericError("An invalid parameter was supplied")
Esempio n. 8
0
def qrcode(admin_id):
    """ Presents the user with a QR code to scan to setup 2 factor authentication
    """
    # render qrcode for FreeTOTP
    admin = Admins.query.get_or_404(admin_id)
    if admin.id != current_user.id:
        raise GenericError("You may not view other admin's QR codes")
    if admin.otp_active:
        return ("", 204)
    admin.generate_otp_secret()
    try:
        db.session.add(admin)
        db.session.commit()
    except ValidationError as e:
        raise e
    except Exception as e:
        db.session.rollback()
        json_logger("error", current_user.username, "The following error occurred in qrcode: {0}".format(str(e)))
        raise GenericError("The administrator could not be updated")
    url = pyqrcode.create(admin.get_totp_uri())
    stream = BytesIO()
    url.svg(stream, scale=5)
    return (
        stream.getvalue(),
        200,
        {
            "Content-Type": "image/svg+xml",
            "Cache-Control": "no-cache, no-store, must-revalidate",
            "Pragma": "no-cache",
            "Expires": "0",
        },
    )
Esempio n. 9
0
def twofactor_disable(admin_id):
    """ Disable 2 factor using API.

        Enabling 2 factor from this route is not possible.
    """
    admin = Admins.query.get_or_404(admin_id)
    status = request.get_json(force=True).get('enabled')
    if status:
        if status.lower() == "false":
            admin.otp_active = False
            try:
                db.session.add(admin)
                db.session.commit()
            except ValidationError as e:
                raise e
            except Exception as e:
                db.session.rollback()
                log_msg = ('The following error occurred in twofactor_disable:'
                           ' {0}'.format(str(e)))
                json_logger('error', current_user.username, log_msg)
                raise GenericError('The administrator could not be updated')
            return {}, 200
        elif status.lower() == 'true':
            raise GenericError(
                'Cannot enable 2 factor from this route - see docs')
    raise GenericError('An invalid parameter was supplied')
Esempio n. 10
0
def update_user(user_id):
    """ Updates a user by ID in VirtualUsers, and returns HTTP 200 on success
    """
    user = VirtualUsers.query.get_or_404(user_id)
    json = request.get_json(force=True)

    if 'password' in json:
        min_pwd_length = int(
            Configs.query.filter_by(
                setting='Minimum Password Length').first().value)
        if len(json['password']) < min_pwd_length:
            raise ValidationError(
                'The password must be at least {0} characters long'.format(
                    min_pwd_length))
        user.password = VirtualUsers().encrypt_password(json['password'])
        audit_msg = 'The user "{0}" had their password changed'.format(
            user.email)
        db.session.add(user)
    else:
        raise ValidationError('The password was not supplied in the request')
    try:
        db.session.commit()
        json_logger('audit', current_user.username, audit_msg)
    except ValidationError as e:
        raise e
    except Exception as e:
        db.session.rollback()
        json_logger(
            'error', current_user.username,
            'The following error occurred in update_user: {0}'.format(str(e)))
        raise GenericError('The user could not be updated')
    finally:
        db.session.close()
    return {}, 200
Esempio n. 11
0
def qrcode(admin_id):
    """ Presents the user with a QR code to scan to setup 2 factor
    authentication
    """
    # render qrcode for FreeTOTP
    admin = Admins.query.get_or_404(admin_id)
    if admin.id != current_user.id:
        raise GenericError('You may not view other admin\'s QR codes')
    if admin.otp_active:
        return '', 204
    admin.generate_otp_secret()
    try:
        db.session.add(admin)
        db.session.commit()
    except ValidationError as e:
        raise e
    except Exception as e:
        db.session.rollback()
        json_logger(
            'error', current_user.username,
            'The following error occurred in qrcode: {0}'.format(str(e)))
        raise GenericError('The administrator could not be updated')
    url = pyqrcode.create(admin.get_totp_uri())
    stream = BytesIO()
    url.svg(stream, scale=5)
    return stream.getvalue(), 200, {
        'Content-Type': 'image/svg+xml',
        'Cache-Control': 'no-cache, no-store, must-revalidate',
        'Pragma': 'no-cache',
        'Expires': '0'
    }
Esempio n. 12
0
def verify_qrcode(admin_id):
    """ Verifies if the 2 factor token provided is correct

        This will enable 2 factor for a user.
    """
    admin = Admins.query.get_or_404(admin_id)
    if request.get_json(force=True).get('code'):
        if not admin.otp_secret:
            raise GenericError(
                "The 2 Factor Secret has not been generated yet")
        if admin.verify_totp(request.get_json(force=True).get('code')):
            if not admin.otp_active:
                audit_message = ('The administrator "{0}" enabled 2 '
                                 'factor'.format(admin.username))
                admin.otp_active = True
                try:
                    db.session.add(admin)
                    db.session.commit()
                    json_logger('audit', current_user.username, audit_message)
                except ValidationError as e:
                    raise e
                except Exception as e:
                    db.session.rollback()
                    log_msg = ('The following error occurred in verify_qrcode:'
                               ' {0}'.format(str(e)))
                    json_logger('error', current_user.username, log_msg)
                    raise GenericError(
                        'The administrator could not be updated')
            return {}, 200
        else:
            raise GenericError("An invalid code was supplied")
    else:
        raise ValidationError("The code was not supplied")
Esempio n. 13
0
    def get_ldap_object(self, sam_account_name, attributes=None):
        """ Returns an LDAP object based on the sAMAccountName and the desired
        attributes
        """
        search_filter = '(sAMAccountName={0})'.format(sam_account_name)
        ldap_object = self.search(search_filter, attributes)[0]
        # Check if a user was returned
        if 'dn' in ldap_object:
            if attributes and 'attributes' in ldap_object:
                for attribute in attributes:
                    if attribute not in ldap_object['attributes']:
                        log_msg = ('The object with account name "{0}" was '
                                   'found in LDAP, but the attribute "{1}" '
                                   'was not'.format(
                                        sam_account_name, attribute))
                        json_logger('error', self.get_loggedin_user(), log_msg)
                        error_msg = ('There was an error searching the LDAP '
                                     'server. Please try again.')
                        raise ADException(error_msg)
            return ldap_object

        log_msg = ('The object with account name "{0}" could not be found in '
                   'LDAP'.format(sam_account_name))
        json_logger('error', self.get_loggedin_user(), log_msg)
        raise ADException(
            'There was an error searching the LDAP server. Please try again.')
Esempio n. 14
0
def delete_user(user_id):
    """ Deletes a user by ID in VirtualUsers, and returns HTTP 204 on success
    """
    user = VirtualUsers.query.get_or_404(user_id)

    aliases = VirtualAliases.query.filter_by(destination=user.email).all()
    if aliases:
        for alias in aliases:
            db.session.delete(alias)
    db.session.delete(user)
    try:
        db.session.commit()
        json_logger(
            'audit', current_user.username,
            'The user "{0}" was deleted successfully'.format(user.email))
    except ValidationError as e:
        raise e
    except Exception as e:
        db.session.rollback()
        json_logger(
            'error', current_user.username,
            'The following error occurred in delete_user: {0}'.format(str(e)))
        raise GenericError('The user could not be deleted')
    finally:
        db.session.close()
    return {}, 204
Esempio n. 15
0
def update_config(config_id):
    """ Updates a config by ID in Configs, and returns HTTP 200 on success
    """
    config = Configs.query.get_or_404(config_id)
    json = request.get_json(force=True)

    if 'value' in json and is_config_update_valid(config.setting,
                                                  json['value'], config.regex):
        audit_message = (
            'The setting "{0}" was set from "{1}" to "{2}"'.format(
                config.setting, config.value, json['value']))
        config.value = json['value']
        db.session.add(config)
    else:
        raise ValidationError('An invalid setting value was supplied')

    try:
        db.session.commit()
        json_logger('audit', current_user.username, audit_message)
    except ValidationError as e:
        raise e
    except Exception as e:
        db.session.rollback()
        log_msg = ('The following error occurred in update_config: {0}'.format(
            str(e)))
        json_logger('error', current_user.username, log_msg)
        raise GenericError('The configuration could not be updated')
    finally:
        db.session.close()
    return {}, 200
Esempio n. 16
0
    def login(self, username, password):
        """ Uses the supplied username and password to bind to LDAP and returns a boolean
        """
        bind_username = self.parse_username_input(username)

        self.ldap_connection.user = str(bind_username)
        self.ldap_connection.password = str(password)
        if self.ldap_connection.bind():
            return True

        json_logger(
            'auth', bind_username,
            'The administrator "{0}" entered an incorrect username or password via LDAP'.format(bind_username))
        raise ADException('The username or password was incorrect')
Esempio n. 17
0
    def login(self, username, password):
        """ Uses the supplied username and password to bind to LDAP and
        returns a boolean
        """
        bind_username = self.parse_username_input(username)

        self.ldap_connection.user = str(bind_username)
        self.ldap_connection.password = str(password)
        if self.ldap_connection.bind():
            return True

        log_msg = ('The administrator "{0}" entered an incorrect username or '
                   'password via LDAP'.format(bind_username))
        json_logger('auth', bind_username, log_msg)
        raise ADException('The username or password was incorrect')
Esempio n. 18
0
    def __init__(self):
        """ The constructor that initializes the ldap_connection object
        """
        ldap_enabled = models.Configs().query.filter_by(
            setting='Enable LDAP Authentication').first()
        if ldap_enabled is not None and ldap_enabled.value == 'True':
            ldap_server = models.Configs().query.filter_by(
                setting='AD Server LDAP String').first()
            domain = models.Configs().query.filter_by(
                setting='AD Domain').first()
            ldap_admin_group = models.Configs().query.filter_by(
                setting='AD PostMaster Group').first()
            ldap_auth_method = models.Configs().query.filter_by(
                setting='LDAP Authentication Method').first()

            ldap_server_regex = r'LDAP[S]?:\/\/(.*?)\:\d+'
            if ldap_server is not None and re.search(
                    ldap_server_regex, ldap_server.value, re.IGNORECASE):
                self.ldap_server = ldap_server.value

                if domain is not None and ldap_admin_group is not None:
                    self.domain = domain.value
                    self.ldap_admin_group = ldap_admin_group.value

                    ad_server = ldap3.Server(
                        ldap_server.value,
                        allowed_referral_hosts=[('*', False)],
                        connect_timeout=3)
                    # Use NTLMv2 authentication so that credentials aren't sent
                    # in the clear if LDAPS is not used
                    self.ldap_connection = ldap3.Connection(
                        ad_server, auto_referrals=False,
                        authentication=ldap_auth_method.value.upper())

                    try:
                        self.ldap_connection.open()
                    except ldap3.core.exceptions.LDAPSocketOpenError:
                        log_msg = ('The LDAP server "{0}" could not be '
                                   'contacted'.format(self.ldap_server))
                        json_logger('error', 'NA', log_msg)
                        raise ADException(
                            'The LDAP server could not be contacted')
                else:
                    log_msg = 'The LDAP Admin Group is not configured properly'
                    json_logger('error', 'NA', log_msg)
                    raise ADException(
                        'LDAP authentication is not properly configured')
            else:
                log_msg = ('The LDAP server string is not configured or '
                           'isn\'t properly formatted')
                json_logger('error', 'NA', log_msg)
                raise ADException('The LDAP server could not be contacted')
        else:
            log_msg = ('An LDAP authentication attempt was made but it is '
                       'currently disabled')
            json_logger('error', 'NA', log_msg)
            raise ADException('LDAP authentication is not enabled')
Esempio n. 19
0
    def search(self, search_filter, attributes=None, search_scope=ldap3.SUBTREE):
        """ Returns LDAP objects based on the search_filter and the desired attributes
        """
        # Check if the ldap_connection is in a logged in state
        if self.ldap_connection.bound:
            if self.ldap_connection.search(
                    self.base_dn, search_filter, search_scope=search_scope, attributes=attributes):
                # Check if anything was returned
                if len(self.ldap_connection.response):
                    return self.ldap_connection.response

            json_logger(
                'error', self.get_loggedin_user(),
                'The LDAP object could not be found using the search filter "{0}"'.format(search_filter))
            raise ADException('There was an error searching the LDAP server. Please try again.')
        else:
            raise ADException('You must be logged into LDAP to search')
Esempio n. 20
0
    def get_primary_group_dn_of_user(self, sam_account_name):
        """ Returns the distinguished name of the primary group of the user
        """
        user = self.get_ldap_object(sam_account_name, ['primaryGroupID'])
        primary_group_id = str(user['attributes']['primaryGroupID'])

        domain_search = self.search('(objectClass=domainDNS)', ['objectSid'], ldap3.BASE)
        if 'dn' in domain_search[0]:
            if domain_search[0] and 'attributes' in domain_search[0] and 'objectSid' in domain_search[0]['attributes']:
                domain_sid = domain_search[0]['attributes']['objectSid']
                group_search_filter = '(&(objectClass=group)(objectSid={0}-{1}))'.format(domain_sid, primary_group_id)
                group_search = self.search(group_search_filter)
                if 'dn' in group_search[0]:
                    return group_search[0]['dn']

        json_logger('error', self.get_loggedin_user(), 'The objectSid of the domain could not be found')
        raise ADException('There was an error searching the LDAP server. Please try again.')
Esempio n. 21
0
def update_admin(admin_id):
    """ Updates an admin user by ID in Admins, and returns HTTP 200 on success
    """
    audit_msg = ''
    admin = Admins.query.get_or_404(admin_id)
    json = request.get_json(force=True)

    if 'username' in json:
        new_user_name = json['username'].lower()
        if Admins.query.filter_by(username=new_user_name).first() is None:
            audit_msg = ('The administrator "{0}" had their username changed '
                         'to "{1}"'.format(admin.username, new_user_name))
            admin.username = new_user_name
        else:
            ValidationError('The username supplied already exists')
    elif 'password' in json:
        admin.set_password(json['password'])
        audit_msg = (
            'The administrator "{0}" had their password changed'.format(
                admin.username))
    elif 'name' in json:
        audit_msg = (
            'The administrator "{0}" had their name changed to "{1}"'.format(
                admin.username, admin.name))
        admin.name = json['name']
    else:
        raise ValidationError(
            'The username, password, or name was not supplied in the request')

    try:
        db.session.add(admin)
        db.session.commit()
        json_logger('audit', current_user.username, audit_msg)
    except ValidationError as e:
        raise e
    except Exception as e:
        db.session.rollback()
        json_logger(
            'error', current_user.username,
            'The following error occurred in update_admin: {0}'.format(str(e)))
        raise GenericError('The administrator could not be updated')
    finally:
        db.session.close()
    return {}, 200
Esempio n. 22
0
def new_admin():
    """ Creates a new admin user in Admins, and returns HTTP 201 on success
    """
    admin = Admins().from_json(request.get_json(force=True))
    db.session.add(admin)
    try:
        db.session.commit()
        json_logger(
            "audit", current_user.username, 'The administrator "{0}" was created successfully'.format(admin.username)
        )
    except ValidationError as e:
        raise e
    except Exception as e:
        db.session.rollback()
        json_logger("error", current_user.username, "The following error occurred in new_admin: {0}".format(str(e)))
        raise GenericError("The admininstrator could not be created")
    finally:
        db.session.close()
    return {}, 201
Esempio n. 23
0
def validate_wtforms_password(form, field):
        """ Validates the password from a wtforms object
        """
        username = form.username.data
        password = form.password.data
        two_factor = form.two_factor.data if form.two_factor.data else None

        try:
            if form.auth_source.data == 'PostMaster User':
                admin = models.Admins.query.filter_by(username=username, source='local').first()
                if admin:
                    if admin.is_unlocked():
                        if bcrypt.check_password_hash(admin.password, password):
                            if admin.otp_active:
                                if not admin.verify_totp(two_factor):
                                    raise WtfStopValidation('The two factor authentication code is incorrect')
                            form.admin = admin
                            return
                        else:
                            increment_failed_login(username)
                    else:
                        raise WtfStopValidation('The user is currently locked out. Please try logging in again later.')
                json_logger(
                    'auth', username,
                    'The administrator "{0}" entered an incorrect username or password'.format(
                        username))
                raise WtfStopValidation('The username or password was incorrect')
            else:
                ad_object = AD()
                if ad_object.login(username, password):
                    if ad_object.check_group_membership():
                        friendly_username = ad_object.get_loggedin_user()
                        display_name = ad_object.get_loggedin_user_display_name()
                        if not models.Admins.query.filter_by(username=friendly_username, source='ldap').first():
                            add_ldap_user_to_db(friendly_username, display_name)
                        admin = models.Admins.query.filter_by(username=friendly_username, source='ldap').first()
                        if admin.otp_active:
                            if not admin.verify_totp(two_factor):
                                raise WtfStopValidation('The two factor authentication code is incorrect')
                        form.admin = admin
        except ADException as e:
            raise WtfStopValidation(e.message)
Esempio n. 24
0
    def check_group_membership(self):
        """ Checks the group membership of the logged on user. This will return True if the user is a member of
        the Administrator group set in the database
        """
        username = self.get_loggedin_user()
        if self.check_nested_group_membership(self.ldap_admin_group, username):
            return True

        admin_group_dn = self.get_distinguished_name(self.ldap_admin_group)
        # If the user was not a member of the group, check to see if the admin group is the primary group
        # of the user which is not included in memberOf (this is typically Domain Users)
        primary_group_dn = self.get_primary_group_dn_of_user(username)
        if admin_group_dn == primary_group_dn:
            return True

        json_logger(
            'auth', username,
            ('The LDAP user "{0}" authenticated but the login failed because they weren\'t '
                'a PostMaster administrator').format(username))
        raise ADException('The user account is not authorized to login to PostMaster')
Esempio n. 25
0
def new_alias():
    """ Creates a new alias in VirtualAliases, and returns HTTP 201 on success
    """
    alias = VirtualAliases().from_json(request.get_json(force=True))
    db.session.add(alias)
    try:
        db.session.commit()
        json_logger(
            'audit', current_user.username,
            'The alias "{0}" was created successfully'.format(alias.source))
    except ValidationError as e:
        raise e
    except Exception as e:
        db.session.rollback()
        json_logger(
            'error', current_user.username,
            'The following error occurred in new_alias: {0}'.format(str(e)))
        raise GenericError('The alias could not be created')
    finally:
        db.session.close()
    return {}, 201
Esempio n. 26
0
    def get_ldap_object(self, sam_account_name, attributes=None):
        """ Returns an LDAP object based on the sAMAccountName and the desired attributes
        """
        search_filter = '(sAMAccountName={0})'.format(sam_account_name)
        ldap_object = self.search(search_filter, attributes)[0]
        # Check if a user was returned
        if 'dn' in ldap_object:
            if attributes and 'attributes' in ldap_object:
                for attribute in attributes:
                    if attribute not in ldap_object['attributes']:
                        json_logger(
                            'error', self.get_loggedin_user(),
                            ('The object with account name "{0}" was found in LDAP, but the attribute "{1}" was'
                             ' not'.format(sam_account_name, attribute)))
                        raise ADException('There was an error searching the LDAP server. Please try again.')
            return ldap_object

        json_logger(
            'error', self.get_loggedin_user(),
            'The object with account name "{0}" could not be found in LDAP'.format(sam_account_name))
        raise ADException('There was an error searching the LDAP server. Please try again.')
Esempio n. 27
0
def new_user():
    """ Creates a new user in VirtualUsers, and returns HTTP 201 on success
    """
    user = VirtualUsers().from_json(request.get_json(force=True))
    db.session.add(user)
    try:
        db.session.commit()
        json_logger(
            'audit', current_user.username,
            'The user "{0}" was created successfully'.format(user.email))
    except ValidationError as e:
        raise e
    except Exception as e:
        db.session.rollback()
        json_logger(
            'error', current_user.username,
            'The following error occurred in new_user: {0}'.format(str(e)))
        raise GenericError('The user could not be created')
    finally:
        db.session.close()
    return {}, 201
Esempio n. 28
0
def delete_alias(alias_id):
    """ Deletes an alias by ID in VirtualAliases, and returns HTTP 204 on success
    """
    alias = VirtualAliases.query.get_or_404(alias_id)
    db.session.delete(alias)
    try:
        db.session.commit()
        json_logger(
            'audit', current_user.username,
            'The alias "{0}" was deleted successfully'.format(alias.source))
    except ValidationError as e:
        raise e
    except Exception as e:
        db.session.rollback()
        json_logger(
            'error', current_user.username,
            'The following error occurred in delete_alias: {0}'.format(str(e)))
        raise GenericError('The alias could not be deleted')
    finally:
        db.session.close()
    return {}, 204
Esempio n. 29
0
def delete_admin(admin_id):
    """ Deletes an admin user by ID in Admins, and returns HTTP 204 on success
    """
    admin = Admins.query.get_or_404(admin_id)
    if db.session.query(Admins).count() == 1:
        raise GenericError("There needs to be at least one adminstrator")
    db.session.delete(admin)
    try:
        db.session.commit()
        json_logger(
            "audit", current_user.username, 'The administrator "{0}" was deleted successfully'.format(admin.username)
        )
    except ValidationError as e:
        raise e
    except Exception as e:
        db.session.rollback()
        json_logger("error", current_user.username, "The following error occurred in delete_admin: {0}".format(str(e)))
        raise GenericError("The administrator could not be deleted")
    finally:
        db.session.close()
    return {}, 204
Esempio n. 30
0
def update_alias(alias_id):
    """ Updates an alias by ID in VirtualAliases, and returns HTTP 200 on
    success
    """
    alias = VirtualAliases.query.get_or_404(alias_id)
    json = request.get_json(force=True)

    if 'source' in json:
        new_source = json['source'].lower()
        if VirtualAliases().validate_source(new_source):
            audit_msg = (
                'The alias "{0}" had their source changed to "{1}"'.format(
                    alias.source, new_source))
            alias.source = new_source
            db.session.add(alias)
    elif 'destination' in json:
        new_destination = json['destination'].lower()
        if VirtualAliases().validate_destination(new_destination):
            audit_msg = ('The alias "{0}" had their destination changed to'
                         ' "{1}"'.format(alias.source, new_destination))
            alias.destination = new_destination
            db.session.add(alias)
    else:
        raise ValidationError(
            'The source or destination was not supplied in the request')

    try:
        db.session.commit()
        json_logger('audit', current_user.username, audit_msg)
    except ValidationError as e:
        raise e
    except Exception as e:
        db.session.rollback()
        json_logger(
            'error', current_user.username,
            'The following error occurred in update_alias: {0}'.format(str(e)))
        raise GenericError('The alias could not be updated')
    finally:
        db.session.close()
    return {}, 200
Esempio n. 31
0
def delete_alias(alias_id):
    """ Deletes an alias by ID in VirtualAliases, and returns HTTP 204 on
    success
    """
    alias = VirtualAliases.query.get_or_404(alias_id)
    db.session.delete(alias)
    try:
        db.session.commit()
        json_logger(
            'audit', current_user.username,
            'The alias "{0}" was deleted successfully'.format(alias.source))
    except ValidationError as e:
        raise e
    except Exception as e:
        db.session.rollback()
        json_logger(
            'error', current_user.username,
            'The following error occurred in delete_alias: {0}'.format(str(e)))
        raise GenericError('The alias could not be deleted')
    finally:
        db.session.close()
    return {}, 204
Esempio n. 32
0
def delete_domain(domain_id):
    """ Deletes a domain by ID in VirtualDomains, and returns HTTP 204 on success
    """
    domain = VirtualDomains.query.get_or_404(domain_id)
    db.session.delete(domain)
    try:
        db.session.commit()
        json_logger(
            'audit', current_user.username,
            'The domain "{0}" was deleted successfully'.format(domain.name))
    except ValidationError as e:
        raise e
    except Exception as e:
        db.session.rollback()
        json_logger(
            'error', current_user.username,
            'The following error occurred in delete_domain: {0}'.format(str(
                e)))
        raise GenericError('The domain could not be deleted')
    finally:
        db.session.close()
    return {}, 204
Esempio n. 33
0
def new_admin():
    """ Creates a new admin user in Admins, and returns HTTP 201 on success
    """
    admin = Admins().from_json(request.get_json(force=True))
    db.session.add(admin)
    try:
        db.session.commit()
        json_logger(
            'audit', current_user.username,
            'The administrator "{0}" was created successfully'.format(
                admin.username))
    except ValidationError as e:
        raise e
    except Exception as e:
        db.session.rollback()
        json_logger(
            'error', current_user.username,
            'The following error occurred in new_admin: {0}'.format(str(e)))
        raise GenericError('The admininstrator could not be created')
    finally:
        db.session.close()
    return {}, 201
Esempio n. 34
0
    def search(self, search_filter, attributes=None,
               search_scope=ldap3.SUBTREE):
        """ Returns LDAP objects based on the search_filter and the desired
        attributes
        """
        # Check if the ldap_connection is in a logged in state
        if self.ldap_connection.bound:
            if self.ldap_connection.search(
                    self.base_dn, search_filter, search_scope=search_scope,
                    attributes=attributes):
                # Check if anything was returned
                if len(self.ldap_connection.response):
                    return self.ldap_connection.response

            log_msg = ('The LDAP object could not be found using the search '
                       'filter "{0}"'.format(search_filter))
            json_logger('error', self.get_loggedin_user(), log_msg)
            error_msg = ('There was an error searching the LDAP server. '
                         'Please try again.')
            raise ADException(error_msg)
        else:
            raise ADException('You must be logged into LDAP to search')
Esempio n. 35
0
def add_ldap_user_to_db(username, display_name):
    """ Adds an LDAP user stub in the Admins table of the database for flask_login
    """
    try:
        new_admin = models.Admins().ldap_user_from_json({
            'username': username,
            'name': display_name
        })
        db.session.add(new_admin)
        db.session.commit()
        json_logger('audit', username,
                    '"{0}" was added as an LDAP admin to the database"'.format(username))
    except ValidationError as e:
        raise e
    except Exception as e:
        db.session.rollback()
        json_logger(
            'error', username,
            'The following error occurred when adding an LDAP admin: {0}'.format(str(e)))
        ValidationError('A database error occurred. Please try again.', 'error')
    finally:
        db.session.close()
Esempio n. 36
0
def update_alias(alias_id):
    """ Updates an alias by ID in VirtualAliases, and returns HTTP 200 on success
    """
    alias = VirtualAliases.query.get_or_404(alias_id)
    json = request.get_json(force=True)

    if 'source' in json:
        newSource = json['source'].lower()
        if VirtualAliases().validate_source(newSource):
            auditMessage = 'The alias "{0}" had their source changed to "{1}"'.format(
                alias.source, newSource)
            alias.source = newSource
            db.session.add(alias)
    elif 'destination' in json:
        newDestination = json['destination'].lower()
        if VirtualAliases().validate_destination(newDestination):
            auditMessage = 'The alias "{0}" had their destination changed to "{1}"'.format(
                alias.source, newDestination)
            alias.destination = newDestination
            db.session.add(alias)
    else:
        raise ValidationError(
            'The source or destination was not supplied in the request')

    try:
        db.session.commit()
        json_logger('audit', current_user.username, auditMessage)
    except ValidationError as e:
        raise e
    except Exception as e:
        db.session.rollback()
        json_logger(
            'error', current_user.username,
            'The following error occurred in update_alias: {0}'.format(str(e)))
        raise GenericError('The alias could not be updated')
    finally:
        db.session.close()
    return {}, 200
Esempio n. 37
0
def update_admin(admin_id):
    """ Updates an admin user by ID in Admins, and returns HTTP 200 on success
    """
    auditMessage = ""
    admin = Admins.query.get_or_404(admin_id)
    json = request.get_json(force=True)

    if "username" in json:
        newUsername = json["username"].lower()
        if Admins.query.filter_by(username=newUsername).first() is None:
            auditMessage = 'The administrator "{0}" had their username changed to "{1}"'.format(
                admin.username, newUsername
            )
            admin.username = newUsername
        else:
            ValidationError("The username supplied already exists")
    elif "password" in json:
        admin.set_password(json["password"])
        auditMessage = 'The administrator "{0}" had their password changed'.format(admin.username)
    elif "name" in json:
        auditMessage = 'The administrator "{0}" had their name changed to "{1}"'.format(admin.username, admin.name)
        admin.name = json["name"]
    else:
        raise ValidationError("The username, password, or name was not supplied in the request")

    try:
        db.session.add(admin)
        db.session.commit()
        json_logger("audit", current_user.username, auditMessage)
    except ValidationError as e:
        raise e
    except Exception as e:
        db.session.rollback()
        json_logger("error", current_user.username, "The following error occurred in update_admin: {0}".format(str(e)))
        raise GenericError("The administrator could not be updated")
    finally:
        db.session.close()
    return {}, 200
Esempio n. 38
0
def delete_domain(domain_id):
    """ Deletes a domain by ID in VirtualDomains, and returns HTTP 204 on
    success
    """
    domain = VirtualDomains.query.get_or_404(domain_id)
    db.session.delete(domain)
    try:
        db.session.commit()
        json_logger(
            'audit', current_user.username,
            'The domain "{0}" was deleted successfully'.format(domain.name))
    except ValidationError as e:
        raise e
    except Exception as e:
        db.session.rollback()
        json_logger(
            'error', current_user.username,
            'The following error occurred in delete_domain: {0}'.format(str(
                e)))
        raise GenericError('The domain could not be deleted')
    finally:
        db.session.close()
    return {}, 204
Esempio n. 39
0
    def check_group_membership(self):
        """ Checks the group membership of the logged on user. This will return
        True if the user is a member of the Administrator group set in the
        database
        """
        username = self.get_loggedin_user()
        if self.check_nested_group_membership(self.ldap_admin_group, username):
            return True

        admin_group_dn = self.get_distinguished_name(self.ldap_admin_group)
        # If the user was not a member of the group, check to see if the admin
        # group is the primary group of the user which is not included in
        # memberOf (this is typically Domain Users)
        primary_group_dn = self.get_primary_group_dn_of_user(username)
        if admin_group_dn == primary_group_dn:
            return True

        log_msg = ('The LDAP user "{0}" authenticated but the login failed '
                   'because they weren\'t a PostMaster administrator'
                   .format(username))
        json_logger('auth', username, log_msg)
        raise ADException(
            'The user account is not authorized to login to PostMaster')
Esempio n. 40
0
def clear_lockout_fields_on_user(username):
    """ Clears the lockout fields (failed_attempts, last_failed_date, unlock_date) on a user. This is used
    to unlock a user, or when a user logs in successfully.
    """
    admin = models.Admins.query.filter_by(username=username).first()

    if not admin:
        raise ValidationError('The admin does not exist in the database.')

    try:
        admin.clear_lockout_fields()
        db.session.add(admin)
        db.session.commit()
    except ValidationError as e:
        raise e
    except Exception as e:
        db.session.rollback()
        json_logger(
            'error', username,
            'The following error occurred when try to clear out the lockout fields: {0}'.format(str(e)))
        ValidationError('A database error occurred. Please try again.', 'error')
    finally:
        db.session.close()
Esempio n. 41
0
    def get_primary_group_dn_of_user(self, sam_account_name):
        """ Returns the distinguished name of the primary group of the user
        """
        user = self.get_ldap_object(sam_account_name, ['primaryGroupID'])
        primary_group_id = str(user['attributes']['primaryGroupID'])

        domain_search = self.search(
            '(objectClass=domainDNS)', ['objectSid'], ldap3.BASE)
        if 'dn' in domain_search[0]:
            if domain_search[0] and 'attributes' in domain_search[0] \
                    and 'objectSid' in domain_search[0]['attributes']:
                domain_sid = domain_search[0]['attributes']['objectSid']
                group_search_filter = \
                    ('(&(objectClass=group)(objectSid={0}-{1}))'
                     .format(domain_sid, primary_group_id))
                group_search = self.search(group_search_filter)
                if 'dn' in group_search[0]:
                    return group_search[0]['dn']

        json_logger('error', self.get_loggedin_user(),
                    'The objectSid of the domain could not be found')
        raise ADException(
            'There was an error searching the LDAP server. Please try again.')
Esempio n. 42
0
def reset_admin_password(username, new_password):
    """ Resets an admin's password with one supplied
    """
    admin = models.Admins.query.filter_by(username=username).first()

    if not admin:
        raise ValidationError('The admin does not exist in the database.')

    admin.set_password(new_password)

    try:
        db.session.add(admin)
        db.session.commit()
        json_logger('audit', 'CLI', ('The administrator "{0}" had their password changed via the CLI'.format(username)))
    except ValidationError as e:
        raise e
    except Exception as e:
        db.session.rollback()
        json_logger(
            'error', username,
            'The following error occurred when try to reset an admin\'s password: {0}'.format(str(e)))
        ValidationError('A database error occurred. Please try again.', 'error')
    finally:
        db.session.close()
Esempio n. 43
0
def increment_failed_login(username):
    """ Increments the failed_attempts value, updates the last_failed_date value, and sets the unlock_date value
    if necessary on the admin
    """
    admin = models.Admins.query.filter_by(username=username).first()
    if not admin:
        raise ValidationError('The admin does not exist in the database.')

    account_lockout_threshold = int(models.Configs.query.filter_by(setting='Account Lockout Threshold').first().value)
    reset_account_lockout_counter = int(models.Configs.query.filter_by(
        setting='Reset Account Lockout Counter in Minutes').first().value)
    account_lockout_duration = int(models.Configs.query.filter_by(
        setting='Account Lockout Duration in Minutes').first().value)

    admin.increment_failed_login(account_lockout_threshold, reset_account_lockout_counter, account_lockout_duration)

    try:
        db.session.add(admin)
        db.session.commit()

        # If the account is locked out, log an audit message
        if not admin.is_unlocked():
            audit_message = '"{0}" is now locked out and will be unlocked in {1} minute(s)'.format(
                username, account_lockout_duration)
            json_logger('audit', username, audit_message)
    except ValidationError as e:
        raise e
    except Exception as e:
        db.session.rollback()
        json_logger(
            'error', username,
            'The following error occurred when incrementing the failed login attempts field on "{0}": {1}'.format(
                username, str(e)))
        ValidationError('A database error occurred. Please try again.', 'error')
    finally:
        db.session.close()
Esempio n. 44
0
def delete_admin(admin_id):
    """ Deletes an admin user by ID in Admins, and returns HTTP 204 on success
    """
    admin = Admins.query.get_or_404(admin_id)
    if db.session.query(Admins).count() == 1:
        raise GenericError('There needs to be at least one adminstrator')
    db.session.delete(admin)
    try:
        db.session.commit()
        json_logger(
            'audit', current_user.username,
            'The administrator "{0}" was deleted successfully'.format(
                admin.username))
    except ValidationError as e:
        raise e
    except Exception as e:
        db.session.rollback()
        json_logger(
            'error', current_user.username,
            'The following error occurred in delete_admin: {0}'.format(str(e)))
        raise GenericError('The administrator could not be deleted')
    finally:
        db.session.close()
    return {}, 204
Esempio n. 45
0
    def __init__(self):
        """ The constructor that initializes the ldap_connection object
        """
        ldap_enabled = models.Configs().query.filter_by(setting='Enable LDAP Authentication').first()
        if ldap_enabled is not None and ldap_enabled.value == 'True':
            ldap_server = models.Configs().query.filter_by(setting='AD Server LDAP String').first()
            domain = models.Configs().query.filter_by(setting='AD Domain').first()
            ldap_admin_group = models.Configs().query.filter_by(setting='AD PostMaster Group').first()
            ldap_auth_method = models.Configs().query.filter_by(setting='LDAP Authentication Method').first()

            if ldap_server is not None and re.search(r'LDAP[S]?:\/\/(.*?)\:\d+', ldap_server.value, re.IGNORECASE):
                self.ldap_server = ldap_server.value

                if domain is not None and ldap_admin_group is not None:
                    self.domain = domain.value
                    self.ldap_admin_group = ldap_admin_group.value

                    ad_server = ldap3.Server(
                        ldap_server.value, allowed_referral_hosts=[('*', False)],  connect_timeout=3)
                    # Use NTLMv2 authentication so that credentials aren't set in the clear if LDAPS is not used
                    self.ldap_connection = ldap3.Connection(
                        ad_server, auto_referrals=False, authentication=ldap_auth_method.value.upper())

                    try:
                        self.ldap_connection.open()
                    except ldap3.core.exceptions.LDAPSocketOpenError:
                        json_logger(
                            'error', 'NA',
                            'The LDAP server "{0}" could not be contacted'.format(self.ldap_server))
                        raise ADException('The LDAP server could not be contacted')
                else:
                    json_logger('error', 'NA', 'The LDAP Admin Group is not configured properly')
                    raise ADException('LDAP authentication is not properly configured')
            else:
                json_logger('error', 'NA',
                            'The LDAP server string is not configured or isn\'t properly formatted')
                raise ADException('The LDAP server could not be contacted')
        else:
            json_logger('error', 'NA', 'An LDAP authentication attempt was made but it is currently disabled')
            raise ADException('LDAP authentication is not enabled')