def create(self, ctx, body: DeviceBody) -> Device: if body.mac is None or not is_mac_address(body.mac): raise InvalidMACAddress(body.mac) if body.member is None: raise MemberNotFoundError(None) member = self.member_repository.get_by_id(ctx, body.member) if not member: raise MemberNotFoundError(body.member) room = self.room_repository.get_from_member(ctx, body.member) if not room: raise RoomNotFoundError(f"for member {member.username}") if not body.connection_type: raise ValueError() body.mac = str(body.mac).upper().replace(':', '-') d = self.device_repository.get_by_mac(ctx, body.mac) _, count = self.device_repository.search_by(ctx, limit=DEFAULT_LIMIT, offset=0, device_filter=DeviceFilter(member=body.member)) if d: raise DeviceAlreadyExists() elif count >= 20: raise DevicesLimitReached() device = self.device_repository.create(ctx, body) vlan = self.vlan_repository.get_vlan(ctx, vlan_number=room.vlan) if vlan is None: raise VLANNotFoundError(room.vlan) if body.connection_type == DeviceType.wired.name: self._allocate_or_unallocate_ip(ctx, device, vlan.ipv4_network if vlan.ipv4_network else "", vlan.ipv6_network if vlan.ipv6_network else "") else: self._allocate_or_unallocate_ip(ctx, device, member.subnet if member.subnet else "", vlan.ipv6_network if vlan.ipv6_network else "") return device
def get_by_login(self, ctx, login: str): member = self.member_repository.get_by_login(ctx, login) if not member or not member.id: raise MemberNotFoundError(id) latest_sub = self.latest_subscription(ctx, member.id) member.membership = latest_sub.status if latest_sub else MembershipStatus.INITIAL.value return member
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 update_subnet( self, ctx, member_id ) -> Optional[Tuple[IPv4Network, Union[IPv4Address, None]]]: member = self.member_repository.get_by_id(ctx, member_id) if not member: raise MemberNotFoundError(member_id) if not is_member_active(ctx, member): return used_wireles_public_ips = self.member_repository.used_wireless_public_ips( ctx) subnet = None ip = None if len(used_wireles_public_ips) < len( SUBNET_PUBLIC_ADDRESSES_WIRELESS): for i, s in SUBNET_PUBLIC_ADDRESSES_WIRELESS.items(): if i not in used_wireles_public_ips: subnet = s ip = i if subnet is None: raise NoSubnetAvailable("wireless") member = self.member_repository.update( ctx, AbstractMember(id=member_id, subnet=str(subnet), ip=str(ip))) self.device_manager.allocate_wireless_ips(ctx, member_id, str(subnet)) return subnet, ip
def get_by_id(self, ctx, id: int) -> Member: member = self.member_repository.get_by_id(ctx, id) if not member: raise MemberNotFoundError(id) latest_sub = self.latest_subscription(ctx, id) member.membership = latest_sub.status if latest_sub else MembershipStatus.INITIAL.value return member
def test_member_not_found(self, ctx, mock_member_repository: MemberRepository, sample_member: Member, member_manager: MemberManager): mock_member_repository.get_by_id = MagicMock( return_value=(None), side_effect=MemberNotFoundError("")) # When... with pytest.raises(MemberNotFoundError): member_manager.create_subscription(ctx, sample_member.id, SubscriptionBody())
def test_unknown_member(self, ctx, mock_member_repository: MemberRepository, member_manager: MemberManager): mock_member_repository.get_by_id = MagicMock( side_effect=MemberNotFoundError("")) with raises(MemberNotFoundError): member_manager.validate_subscription(ctx, 0, False) mock_member_repository.get_by_id.assert_called_once()
def test_unknown_member(self, ctx, mock_member_repository: MemberRepository, sample_subscription_empty: SubscriptionBody, sample_member: Member, member_manager: MemberManager): mock_member_repository.get_by_id = MagicMock( return_value=(sample_member), side_effect=MemberNotFoundError("")) with raises(MemberNotFoundError): member_manager.create_subscription(ctx, sample_member.id, sample_subscription_empty)
def test_unknown_member(self, ctx, mock_membership_repository: MembershipRepository, mock_member_repository: MemberRepository, sample_member: Member, member_manager: MemberManager): mock_member_repository.get_by_id = MagicMock( return_value=(sample_member), side_effect=MemberNotFoundError("")) with raises(MemberNotFoundError): member_manager.update_subscription(ctx, sample_member.id, SubscriptionBody()) mock_member_repository.get_by_id.assert_called_once() mock_membership_repository.update.assert_not_called()
def test_not_found(self, ctx, sample_member, mock_member_repository: MemberRepository, member_manager: MemberManager): # Given... mock_member_repository.get_by_id = MagicMock( return_value=(None), side_effect=MemberNotFoundError("")) # When... with raises(MemberNotFoundError): member_manager.get_by_id(ctx, id=sample_member.id) # Expect... mock_member_repository.get_by_id.assert_called_once_with( ctx, sample_member.id)
def test_member_not_found(self, ctx, mock_member_repository: MemberRepository, member_manager: MemberManager): # Given... mock_member_repository.get_by_id = MagicMock( return_value=(None), side_effect=MemberNotFoundError("")) # When... with pytest.raises(MemberNotFoundError): member_manager.get_profile(ctx) # Expect... mock_member_repository.get_by_id.assert_called_once_with( ctx, ctx.get(CTX_ADMIN))
def change_password(self, ctx, member_id, password: str, hashed_password): # 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) from binascii import hexlify import hashlib pw = hashed_password or hexlify( hashlib.new('md4', password.encode('utf-16le')).digest()) self.member_repository.update_password(ctx, member_id, pw) return True
def sign(self, ctx, charter_id: int, member_id: int): m = self.member_repository.get_by_id(ctx, member_id) if not m: raise MemberNotFoundError(member_id) subscriptions, _ = self.membership_repository.search( ctx, limit=1, filter_=AbstractMembership( member=member_id, status=MembershipStatus.PENDING_RULES.value)) if not subscriptions: raise MembershipNotFoundError(member_id) self.charter_repository.update(ctx, charter_id, member_id) if subscriptions[0].status == MembershipStatus.PENDING_RULES.value: self.membership_repository.update( ctx, subscriptions[0].uuid, SubscriptionBody(), MembershipStatus.PENDING_PAYMENT_INITIAL)
def validate_subscription(self, ctx, member_id: int, free: bool): 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(None) if subscription.status != MembershipStatus.PENDING_PAYMENT_VALIDATION.value: raise MembershipStatusNotAllowed( subscription.status, "status cannot be used to validate a membership") self.membership_repository.validate(ctx, subscription.uuid) self.add_membership_payment_record(ctx, subscription, free) self.member_repository.add_duration(ctx, subscription.member, subscription.duration) self.update_subnet(ctx, member_id)
def update(self, ctx, id: int, body: MemberBody) -> None: member = self.member_repository.get_by_id(ctx, id) if not member: raise MemberNotFoundError(id) latest_sub = self.latest_subscription(ctx, id) if not latest_sub or latest_sub.status not in [ MembershipStatus.CANCELLED.value, MembershipStatus.ABORTED.value, MembershipStatus.COMPLETE.value ]: raise UpdateImpossible(f'member {member.username}', 'membership not validated') member = self.member_repository.update( ctx, AbstractMember(id=id, email=body.mail, username=body.username, first_name=body.first_name, last_name=body.last_name))
def create(self, ctx, identifier: str, roles: List[str], auth: str = AuthenticationMethod.USER.value) -> None: method = AuthenticationMethod(auth) if method == AuthenticationMethod.API_KEY: raise UpdateImpossible( "api key", "The roles for an api key cannot be changed. You might want to delete the key and recreate one" ) if method == AuthenticationMethod.USER: try: t = self.member_manager.get_by_login(ctx=ctx, login=identifier) if not t: raise MemberNotFoundError() except Exception as e: raise e self.role_repository.create(method=method, identifier=identifier, roles=[Roles(r) for r in roles])
def create(self, ctx, abstract_account: Account) -> object: session: Session = ctx.get(CTX_SQL_SESSION) LOG.debug("sql_account_repository_create_called", extra=log_extra(ctx, account_type=abstract_account.account_type)) now = datetime.now() account_type_query = session.query(AccountType) if abstract_account.account_type is not None: LOG.debug("sql_account_repository_search_account_type", extra=log_extra(ctx, account_type=abstract_account.account_type)) account_type_query = account_type_query.filter(AccountType.id == abstract_account.account_type) else: account_type_query = account_type_query.filter(AccountType.name == "Adherent") account_type = account_type_query.one_or_none() if account_type is None: raise AccountNotFoundError(abstract_account.account_type) adherent = None if abstract_account.member is not None: adherent = session.query(Adherent).filter(Adherent.id == abstract_account.member).one_or_none() if not adherent: raise MemberNotFoundError(abstract_account.member) account = SQLAccount( name=abstract_account.name, actif=abstract_account.actif, type=account_type.id, creation_date=now, compte_courant=abstract_account.compte_courant, pinned=abstract_account.pinned, adherent_id=adherent.id if adherent else None ) with track_modifications(ctx, session, account): session.add(account) session.flush() LOG.debug("sql_account_repository_create_finished", extra=log_extra(ctx, account_id=account.id)) return _map_account_sql_to_entity(account)
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
def get_profile(self, ctx) -> Tuple[AbstractMember, List[str]]: user = ctx.get(CTX_ADMIN) m = self.member_repository.get_by_id(ctx, user) if not m: raise MemberNotFoundError(id) return m, ctx.get(CTX_ROLES)
def get_member_mailinglist(self, ctx, member_id: int) -> int: m = self.member_repository.get_by_id(ctx, member_id) if not m: raise MemberNotFoundError(member_id) return self.mailinglist_repository.get_from_member(ctx, member_id)
def update_member_mailinglist(self, ctx, member_id: int, value: int) -> None: m = self.member_repository.get_by_id(ctx, member_id) if not m: raise MemberNotFoundError(member_id) self.mailinglist_repository.update_from_member(ctx, member_id, value)
def get(self, ctx, charter_id: int, member_id: int) -> Union[datetime, None]: m = self.member_repository.get_by_id(ctx, member_id) if not m: raise MemberNotFoundError(member_id) return self.charter_repository.get(ctx, charter_id, member_id)