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