Ejemplo n.º 1
0
    def get_logs(self, ctx, member_id, dhcp=False) -> List[str]:
        """
        User story: As an admin, I can retrieve the logs of a member, so I can help him troubleshoot their connection
        issues.

        :raise MemberNotFound
        """
        # Check that the user exists in the system.
        member = self.member_repository.get_by_id(ctx, member_id)
        if not member:
            raise MemberNotFoundError(member_id)

        # Do the actual log fetching.
        try:
            devices = self.device_repository.search_by(
                ctx,
                limit=100,
                offset=0,
                device_filter=DeviceFilter(member=member.id))[0]
            logs = self.logs_repository.get_logs(ctx,
                                                 username=member.username,
                                                 devices=devices,
                                                 dhcp=dhcp)

            return list(map(lambda x: "{} {}".format(x[0], x[1]), logs))

        except LogFetchError:
            LOG.warning("log_fetch_failed",
                        extra=log_extra(ctx, username=member.username))
            return []  # We fail open here.
Ejemplo n.º 2
0
    def wrapper(*args,**kwargs):
        """
        Wrap http_api function.
        """
        s = session_handler.session() if session_handler else db.session()

        if "token_info" not in connexion.context:
            LOG.warning('could_not_extract_token_info_kwargs')
            raise UnauthenticatedError("Not token informations")

        token_info = connexion.context["token_info"]
        testing = current_app.config["TESTING"]
        ctx = build_context(
            session=s,
            testing=testing,
            request_id=str(uuid.uuid4()),  # Generates an unique ID on this request so we can track it.
            url=request.url,
            admin=token_info.get("uid", ""),
            roles=token_info.get("scope", [])
        )
        kwargs["ctx"] = ctx
        try:
            result = f(*args, **kwargs)

            # It makes things clearer and less error-prone.
            if not isinstance(result, tuple) or len(result) <= 1:
                raise ValueError("Please always pass the result AND the HTTP code.")

            status_code = result[1]
            msg = result[0]
            if result[0] == NoContent:
                msg = None
            if status_code and (200 <= status_code <= 299 or status_code == 302):
                s.commit()
            else:
                LOG.info("rollback_sql_transaction_non_200_http_code",
                         extra=log_extra(ctx, code=status_code, message=msg))
                s.rollback()
            return result

        except Exception as e:
            LOG.error("rollback_sql_transaction_exception_caught",
                      extra=log_extra(ctx, exception=str(e), traceback=traceback.format_exc()))
            s.rollback()
            raise

        finally:
            # When running unit tests, we don't close the session so tests can actually perform some work on that
            # session.
            if not testing:
                s.close()
Ejemplo n.º 3
0
    def get_statuses(self, ctx, member_id) -> List[MemberStatus]:
        # Check that the user exists in the system.
        member = self.member_repository.get_by_id(ctx, member_id)
        if not member:
            raise MemberNotFoundError(member_id)

        # Do the actual log fetching.
        try:
            devices = self.device_repository.search_by(
                ctx,
                limit=100,
                offset=0,
                device_filter=DeviceFilter(member=member.id))[0]
            logs = self.logs_repository.get_logs(ctx,
                                                 username=member.username,
                                                 devices=devices,
                                                 dhcp=False)
            device_to_statuses = {}
            last_ok_login_mac = {}

            def add_to_statuses(status, timestamp, mac):
                if mac not in device_to_statuses:
                    device_to_statuses[mac] = {}
                if status not in device_to_statuses[mac] or device_to_statuses[
                        mac][status].last_timestamp < timestamp:
                    device_to_statuses[mac][status] = MemberStatus(
                        status=status, last_timestamp=timestamp, comment=mac)

            prev_log = ["", ""]
            for log in logs:
                if "Login OK" in log[1]:
                    match = re.search(
                        r'.*?Login OK:\s*\[(.*?)\].*?cli ([a-f0-9|-]+)\).*',
                        log[1])
                    if match is not None:
                        login, mac = match.group(1), match.group(2).upper()
                        if mac not in last_ok_login_mac or last_ok_login_mac[
                                mac] < log[0]:
                            last_ok_login_mac[mac] = log[0]
                if "EAP sub-module failed" in prev_log[1] \
                        and "mschap: MS-CHAP2-Response is incorrect" in log[1] \
                        and (prev_log[0] - log[0]).total_seconds() < 1:
                    match = re.search(
                        r'.*?EAP sub-module failed\):\s*\[(.*?)\].*?cli ([a-f0-9\-]+)\).*',
                        prev_log[1])
                    if match:
                        login, mac = match.group(1), match.group(2).upper()
                        if login != member.username:
                            add_to_statuses("LOGIN_INCORRECT_WRONG_USER",
                                            log[0], mac)
                        else:
                            add_to_statuses("LOGIN_INCORRECT_WRONG_PASSWORD",
                                            log[0], mac)
                if 'rlm_python' in log[1]:
                    match = re.search(
                        r'.*?rlm_python: Fail (.*?) ([a-f0-9A-F\-]+) with (.+)',
                        log[1])
                    if match is not None:
                        login, mac, reason = match.group(1), match.group(
                            2).upper(), match.group(3)
                        if 'MAC not found and not association period' in reason:
                            add_to_statuses("LOGIN_INCORRECT_WRONG_MAC",
                                            log[0], mac)
                        if 'Adherent not found' in reason:
                            add_to_statuses("LOGIN_INCORRECT_WRONG_USER",
                                            log[0], mac)
                if "TLS Alert" in log[
                        1]:  # @TODO Difference between TLS Alert read and TLS Alert write ??
                    # @TODO a read access denied means the user is validating the certificate
                    # @TODO a read/write protocol version is ???
                    # @TODO a write unknown CA means the user is validating the certificate
                    # @TODO a write decryption failed is ???
                    # @TODO a read internal error is most likely not user-related
                    # @TODO a write unexpected_message is ???
                    match = re.search(
                        r'.*?TLS Alert .*?\):\s*\[(.*?)\].*?cli ([a-f0-9\-]+)\).*',
                        log[1])
                    if match is not None:
                        login, mac = match.group(1), match.group(2).upper()
                        add_to_statuses("LOGIN_INCORRECT_SSL_ERROR", log[0],
                                        mac)
                prev_log = log

            all_statuses = []
            for mac, statuses in device_to_statuses.items():
                for _, object in statuses.items():
                    if mac in last_ok_login_mac and object.last_timestamp < last_ok_login_mac[
                            mac]:
                        continue
                    all_statuses.append(object)
            return all_statuses

        except LogFetchError:
            LOG.warning("log_fetch_failed",
                        extra=log_extra(ctx, username=member.username))
            return []  # We fail open here.
Ejemplo n.º 4
0
    def update_subscription(self, ctx, member_id: int,
                            body: SubscriptionBody) -> None:
        member = self.member_repository.get_by_id(ctx, member_id)
        if not member:
            raise MemberNotFoundError(member_id)

        subscription = self.latest_subscription(ctx=ctx, member_id=member_id)
        if not subscription:
            raise MembershipNotFoundError()

        if subscription.status in [
                MembershipStatus.COMPLETE, MembershipStatus.CANCELLED,
                MembershipStatus.ABORTED
        ]:
            raise MembershipStatusNotAllowed(
                subscription.status,
                "membership already completed, cancelled or aborted")

        state = MembershipStatus(subscription.status)

        if state == MembershipStatus.PENDING_RULES:
            date_signed_minet = self.charter_repository.get(
                ctx, member_id=member_id, charter_id=1)
            if date_signed_minet is not None and date_signed_minet != "":
                LOG.debug(
                    "create_membership_record_switch_status_to_pending_payment_initial"
                )
                state = MembershipStatus.PENDING_PAYMENT_INITIAL
            else:
                raise CharterNotSigned(str(member_id))

        if body.duration is not None and body.duration != 0:
            if body.duration not in self.duration_price:
                LOG.warning("create_membership_record_no_price_defined",
                            extra=log_extra(ctx, duration=body.duration))
                raise NoPriceAssignedToThatDuration(body.duration)

        if state == MembershipStatus.PENDING_PAYMENT_INITIAL:
            if body.duration is not None:
                LOG.debug(
                    "create_membership_record_switch_status_to_pending_payment"
                )
                state = MembershipStatus.PENDING_PAYMENT

        if body.account is not None:
            account = self.account_repository.get_by_id(ctx, body.account)
            if not account:
                raise AccountNotFoundError(body.account)
        if body.payment_method is not None:
            payment_method = self.payment_method_repository.get_by_id(
                ctx, body.payment_method)
            if not payment_method:
                raise PaymentMethodNotFoundError(body.payment_method)

        if state == MembershipStatus.PENDING_PAYMENT:
            if body.account is not None and body.payment_method is not None:
                LOG.debug(
                    "create_membership_record_switch_status_to_pending_payment_validation"
                )
                state = MembershipStatus.PENDING_PAYMENT_VALIDATION

        try:
            self.membership_repository.update(ctx, subscription.uuid, body,
                                              state)
        except Exception:
            raise
Ejemplo n.º 5
0
    def create_subscription(self, ctx, member_id: int,
                            body: SubscriptionBody) -> Membership:
        """
        Core use case of ADH. Registers a membership.

        User story: As an admin, I can create a new membership record, so that a member can have internet access.
        :param ctx: context
        :param member_id: member_id
        :param membership: entity AbstractMembership

        :raise IntMustBePositiveException
        :raise NoPriceAssignedToThatDurationException
        :raise MemberNotFound
        :raise UnknownPaymentMethod
        """

        member = self.member_repository.get_by_id(ctx, member_id)
        if not member:
            raise MemberNotFoundError(member_id)

        latest_subscription = self.latest_subscription(ctx=ctx,
                                                       member_id=member_id)

        if latest_subscription and latest_subscription.status not in [
                MembershipStatus.COMPLETE.value,
                MembershipStatus.CANCELLED.value,
                MembershipStatus.ABORTED.value
        ]:
            raise MembershipAlreadyExist(latest_subscription.status)

        state = MembershipStatus.PENDING_RULES

        if state == MembershipStatus.PENDING_RULES:
            date_signed_minet = self.charter_repository.get(
                ctx, member_id=member_id, charter_id=1)
            if date_signed_minet is not None and date_signed_minet != "":
                LOG.debug(
                    "create_membership_record_switch_status_to_pending_payment_initial"
                )
                state = MembershipStatus.PENDING_PAYMENT_INITIAL

        if state == MembershipStatus.PENDING_PAYMENT_INITIAL:
            if body.duration is not None and body.duration != 0:
                if body.duration not in self.duration_price:
                    LOG.warning("create_membership_record_no_price_defined",
                                extra=log_extra(ctx, duration=body.duration))
                    raise NoPriceAssignedToThatDuration(body.duration)
                LOG.debug(
                    "create_membership_record_switch_status_to_pending_payment"
                )
                state = MembershipStatus.PENDING_PAYMENT

        if state == MembershipStatus.PENDING_PAYMENT:
            if body.account is not None and body.payment_method is not None:
                account = self.account_repository.get_by_id(ctx, body.account)
                if not account:
                    raise AccountNotFoundError(body.account)
                payment_method = self.payment_method_repository.get_by_id(
                    ctx, body.payment_method)
                if not payment_method:
                    raise PaymentMethodNotFoundError(body.payment_method)
                LOG.debug(
                    "create_membership_record_switch_status_to_pending_payment_validation"
                )
                state = MembershipStatus.PENDING_PAYMENT_VALIDATION

        try:
            membership_created = self.membership_repository.create(
                ctx, body, state)
        except UnknownPaymentMethod:
            LOG.warning("create_membership_record_unknown_payment_method",
                        extra=log_extra(ctx,
                                        payment_method=body.payment_method))
            raise

        LOG.info("create_membership_record",
                 extra=log_extra(ctx,
                                 membership_uuis=membership_created.uuid,
                                 membership_status=membership_created.status))

        return membership_created