Example #1
0
    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
Example #2
0
 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
Example #3
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.
Example #4
0
    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
Example #5
0
    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
Example #6
0
 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())
Example #7
0
    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()
Example #8
0
    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)
Example #9
0
    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()
Example #10
0
    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)
Example #11
0
    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))
Example #12
0
    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
Example #13
0
 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)
Example #14
0
    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)
Example #15
0
    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))
Example #16
0
 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])
Example #17
0
    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)
Example #18
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.
Example #19
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
Example #20
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
Example #21
0
 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)
Example #22
0
 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)
Example #23
0
 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)
Example #24
0
 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)