Esempio n. 1
0
def test_parse_date():
    assert parse_date("2020-01-01") is not None
    assert parse_date("1900-01-01") is not None
    assert parse_date("2099-01-01") is not None
    assert not parse_date("2019-02-29")
    assert not parse_date("2019-22-01")
    assert not parse_date("2020-1-01")
    assert not parse_date("20-01-01")
    assert not parse_date("01-01-2020")
    assert not parse_date("2020/01/01")
Esempio n. 2
0
def test_BanUser(db):
    with session_scope() as session:
        super_user, super_token = generate_user(is_superuser=True)
        normal_user, normal_token = generate_user()

        with real_admin_session(super_token) as api:
            res = api.BanUser(admin_pb2.BanUserReq(user=normal_user.username))
        assert res.user_id == normal_user.id
        assert res.username == normal_user.username
        assert res.email == normal_user.email
        assert res.gender == normal_user.gender
        assert parse_date(res.birthdate) == normal_user.birthdate
        assert res.banned
        assert not res.deleted
Esempio n. 3
0
    def ChangeUserBirthdate(self, request, context):
        with session_scope() as session:
            user = session.execute(
                select(User).where_username_or_email_or_id(
                    request.user)).scalar_one_or_none()
            if not user:
                context.abort(grpc.StatusCode.NOT_FOUND, errors.USER_NOT_FOUND)
            user.birthdate = parse_date(request.birthdate)

            notify(
                user_id=user.id,
                topic="birthdate",
                key="",
                action="change",
                icon="wrench",
                title=f"An admin changed your birth date",
                link=urls.account_settings_link(),
            )

            return _user_to_details(user)
Esempio n. 4
0
def test_ChangeUserBirthdate(db):
    with session_scope() as session:
        super_user, super_token = generate_user(is_superuser=True)
        normal_user, normal_token = generate_user(
            birthdate=date(year=2000, month=1, day=1))

        with real_admin_session(super_token) as api:
            res = api.GetUserDetails(
                admin_pb2.GetUserDetailsReq(user=normal_user.username))
            assert parse_date(res.birthdate) == date(year=2000, month=1, day=1)

            res = api.ChangeUserBirthdate(
                admin_pb2.ChangeUserBirthdateReq(user=normal_user.username,
                                                 birthdate="1990-05-25"))

        assert res.user_id == normal_user.id
        assert res.username == normal_user.username
        assert res.email == normal_user.email
        assert res.birthdate == "1990-05-25"
        assert res.gender == normal_user.gender
        assert not res.banned
        assert not res.deleted
Esempio n. 5
0
    def CreateHostRequest(self, request, context):
        with session_scope() as session:
            if request.to_user_id == context.user_id:
                context.abort(grpc.StatusCode.INVALID_ARGUMENT,
                              errors.CANT_REQUEST_SELF)
            # just to check the host exists
            host = session.query(User).filter(
                User.id == request.to_user_id).one_or_none()
            if not host:
                context.abort(grpc.StatusCode.NOT_FOUND, errors.USER_NOT_FOUND)

            from_date = parse_date(request.from_date)
            to_date = parse_date(request.to_date)

            if not from_date or not to_date:
                context.abort(grpc.StatusCode.INVALID_ARGUMENT,
                              errors.INVALID_DATE)

            today = today_in_timezone(host.timezone)

            # request starts from the past
            if from_date < today:
                context.abort(grpc.StatusCode.INVALID_ARGUMENT,
                              errors.DATE_FROM_BEFORE_TODAY)

            # from_date is not >= to_date
            if from_date >= to_date:
                context.abort(grpc.StatusCode.INVALID_ARGUMENT,
                              errors.DATE_FROM_AFTER_TO)

            # No need to check today > to_date

            if from_date - today > timedelta(days=365):
                context.abort(grpc.StatusCode.INVALID_ARGUMENT,
                              errors.DATE_FROM_AFTER_ONE_YEAR)

            if to_date - from_date > timedelta(days=365):
                context.abort(grpc.StatusCode.INVALID_ARGUMENT,
                              errors.DATE_TO_AFTER_ONE_YEAR)

            conversation = Conversation()
            session.add(conversation)
            session.flush()

            session.add(
                Message(
                    conversation_id=conversation.id,
                    author_id=context.user_id,
                    message_type=MessageType.chat_created,
                ))

            message = Message(
                conversation_id=conversation.id,
                author_id=context.user_id,
                text=request.text,
                message_type=MessageType.text,
            )
            session.add(message)
            session.flush()

            host_request = HostRequest(
                conversation_id=conversation.id,
                from_user_id=context.user_id,
                to_user_id=host.id,
                from_date=from_date,
                to_date=to_date,
                status=HostRequestStatus.pending,
                from_last_seen_message_id=message.id,
                # TODO: tz
                # timezone=host.timezone,
            )
            session.add(host_request)
            session.flush()

            send_host_request_email(host_request)

            return requests_pb2.CreateHostRequestRes(
                host_request_id=host_request.conversation_id)
Esempio n. 6
0
    def SignupFlow(self, request, context):
        with session_scope() as session:
            if request.email_token:
                # the email token can either be for verification or just to find an existing signup
                flow = session.execute(
                    select(SignupFlow).
                    where(SignupFlow.email_verified == False).where(
                        SignupFlow.email_token == request.email_token).where(
                            SignupFlow.token_is_valid)).scalar_one_or_none()
                if flow:
                    # find flow by email verification token and mark it as verified
                    flow.email_verified = True
                    flow.email_token = None
                    flow.email_token_expiry = None

                    session.flush()
                else:
                    # just try to find the flow by flow token, no verification is done
                    flow = session.execute(
                        select(SignupFlow).where(
                            SignupFlow.flow_token ==
                            request.email_token)).scalar_one_or_none()
                    if not flow:
                        context.abort(grpc.StatusCode.NOT_FOUND,
                                      errors.INVALID_TOKEN)
            else:
                if not request.flow_token:
                    # fresh signup
                    if not request.HasField("basic"):
                        context.abort(grpc.StatusCode.INVALID_ARGUMENT,
                                      errors.SIGNUP_FLOW_BASIC_NEEDED)
                    # TODO: unique across both tables
                    existing_user = session.execute(
                        select(User).where(User.email == request.basic.email)
                    ).scalar_one_or_none()
                    if existing_user:
                        context.abort(grpc.StatusCode.FAILED_PRECONDITION,
                                      errors.SIGNUP_FLOW_EMAIL_TAKEN)
                    existing_flow = session.execute(
                        select(SignupFlow).where(
                            SignupFlow.email ==
                            request.basic.email)).scalar_one_or_none()
                    if existing_flow:
                        send_signup_email(existing_flow)
                        session.commit()
                        context.abort(grpc.StatusCode.FAILED_PRECONDITION,
                                      errors.SIGNUP_FLOW_EMAIL_STARTED_SIGNUP)

                    if not is_valid_email(request.basic.email):
                        context.abort(grpc.StatusCode.INVALID_ARGUMENT,
                                      errors.INVALID_EMAIL)
                    if not is_valid_name(request.basic.name):
                        context.abort(grpc.StatusCode.INVALID_ARGUMENT,
                                      errors.INVALID_NAME)

                    flow_token = cookiesafe_secure_token()

                    flow = SignupFlow(
                        flow_token=flow_token,
                        name=request.basic.name,
                        email=request.basic.email,
                    )
                    session.add(flow)
                    session.flush()
                else:
                    # not fresh signup
                    flow = session.execute(
                        select(SignupFlow).where(
                            SignupFlow.flow_token ==
                            request.flow_token)).scalar_one_or_none()
                    if not flow:
                        context.abort(grpc.StatusCode.NOT_FOUND,
                                      errors.INVALID_TOKEN)
                    if request.HasField("basic"):
                        context.abort(grpc.StatusCode.FAILED_PRECONDITION,
                                      errors.SIGNUP_FLOW_BASIC_FILLED)

                # we've found and/or created a new flow, now sort out other parts
                if request.HasField("account"):
                    if flow.account_is_filled:
                        context.abort(grpc.StatusCode.FAILED_PRECONDITION,
                                      errors.SIGNUP_FLOW_ACCOUNT_FILLED)

                    # check username validity
                    if not is_valid_username(request.account.username):
                        context.abort(grpc.StatusCode.INVALID_ARGUMENT,
                                      errors.INVALID_USERNAME)

                    if not self._username_available(request.account.username):
                        context.abort(grpc.StatusCode.FAILED_PRECONDITION,
                                      errors.USERNAME_NOT_AVAILABLE)

                    abort_on_invalid_password(request.account.password,
                                              context)
                    hashed_password = hash_password(request.account.password)

                    birthdate = parse_date(request.account.birthdate)
                    if not birthdate or birthdate >= minimum_allowed_birthdate(
                    ):
                        context.abort(grpc.StatusCode.FAILED_PRECONDITION,
                                      errors.INVALID_BIRTHDATE)

                    if not request.account.hosting_status:
                        context.abort(grpc.StatusCode.INVALID_ARGUMENT,
                                      errors.HOSTING_STATUS_REQUIRED)

                    if request.account.lat == 0 and request.account.lng == 0:
                        context.abort(grpc.StatusCode.INVALID_ARGUMENT,
                                      errors.INVALID_COORDINATE)

                    if not request.account.accept_tos:
                        context.abort(grpc.StatusCode.INVALID_ARGUMENT,
                                      errors.MUST_ACCEPT_TOS)

                    flow.username = request.account.username
                    flow.hashed_password = hashed_password
                    flow.birthdate = birthdate
                    flow.gender = request.account.gender
                    flow.hosting_status = hostingstatus2sql[
                        request.account.hosting_status]
                    flow.city = request.account.city
                    flow.geom = create_coordinate(request.account.lat,
                                                  request.account.lng)
                    flow.geom_radius = request.account.radius
                    flow.accepted_tos = TOS_VERSION
                    session.flush()

                if request.HasField("feedback"):
                    if flow.filled_feedback:
                        context.abort(grpc.StatusCode.FAILED_PRECONDITION,
                                      errors.SIGNUP_FLOW_FEEDBACK_FILLED)
                    form = request.feedback

                    flow.filled_feedback = True
                    flow.ideas = form.ideas
                    flow.features = form.features
                    flow.experience = form.experience
                    flow.contribute = contributeoption2sql[form.contribute]
                    flow.contribute_ways = form.contribute_ways
                    flow.expertise = form.expertise
                    session.flush()

                if request.HasField("accept_community_guidelines"):
                    if not request.accept_community_guidelines.value:
                        context.abort(grpc.StatusCode.INVALID_ARGUMENT,
                                      errors.MUST_ACCEPT_COMMUNITY_GUIDELINES)
                    flow.accepted_community_guidelines = GUIDELINES_VERSION
                    session.flush()

                # send verification email if needed
                if not flow.email_sent:
                    send_signup_email(flow)

                session.flush()

            # finish the signup if done
            if flow.is_completed:
                user = User(
                    name=flow.name,
                    email=flow.email,
                    username=flow.username,
                    hashed_password=flow.hashed_password,
                    birthdate=flow.birthdate,
                    gender=flow.gender,
                    hosting_status=flow.hosting_status,
                    city=flow.city,
                    geom=flow.geom,
                    geom_radius=flow.geom_radius,
                    accepted_tos=flow.accepted_tos,
                    accepted_community_guidelines=flow.
                    accepted_community_guidelines,
                    onboarding_emails_sent=1,
                    last_onboarding_email_sent=func.now(),
                )

                session.add(user)

                form = ContributorForm(
                    user=user,
                    ideas=flow.ideas or None,
                    features=flow.features or None,
                    experience=flow.experience or None,
                    contribute=flow.contribute or None,
                    contribute_ways=flow.contribute_ways,
                    expertise=flow.expertise or None,
                )

                session.add(form)

                user.filled_contributor_form = form.is_filled

                session.delete(flow)
                session.commit()

                enforce_community_memberships_for_user(session, user)

                if form.is_filled:
                    user.filled_contributor_form = True

                maybe_send_contributor_form_email(form)

                send_onboarding_email(user, email_number=1)

                token, expiry = create_session(context, session, user, False)
                context.send_initial_metadata([
                    ("set-cookie", create_session_cookie(token, expiry)),
                ])
                return auth_pb2.SignupFlowRes(auth_res=_auth_res(user), )
            else:
                return auth_pb2.SignupFlowRes(
                    flow_token=flow.flow_token,
                    need_account=not flow.account_is_filled,
                    need_feedback=not flow.filled_feedback,
                    need_verify_email=not flow.email_verified,
                    need_accept_community_guidelines=flow.
                    accepted_community_guidelines < GUIDELINES_VERSION,
                )
Esempio n. 7
0
    def CompleteSignup(self, request, context):
        """
        Completes user sign up by creating the user in question, then logs them in.

        TODO: nice error handling for dupe username/email?
        """
        with session_scope() as session:
            signup_token = (
                session.query(SignupToken)
                .filter(SignupToken.token == request.signup_token)
                .filter(SignupToken.is_valid)
                .one_or_none()
            )
            if not signup_token:
                context.abort(grpc.StatusCode.NOT_FOUND, errors.INVALID_TOKEN)

            birthdate = parse_date(request.birthdate)

            if not birthdate or birthdate >= minimum_allowed_birthdate():
                context.abort(grpc.StatusCode.INVALID_ARGUMENT, errors.INVALID_BIRTHDATE)

            # check email again
            if not is_valid_email(signup_token.email):
                context.abort(grpc.StatusCode.INVALID_ARGUMENT, errors.INVALID_EMAIL)

            # check username validity
            if not is_valid_username(request.username):
                context.abort(grpc.StatusCode.INVALID_ARGUMENT, errors.INVALID_USERNAME)

            # check name validity
            if not is_valid_name(request.name):
                context.abort(grpc.StatusCode.INVALID_ARGUMENT, errors.INVALID_NAME)

            if not request.hosting_status:
                context.abort(grpc.StatusCode.INVALID_ARGUMENT, errors.HOSTING_STATUS_REQUIRED)

            if not self._username_available(request.username):
                context.abort(grpc.StatusCode.INVALID_ARGUMENT, errors.USERNAME_NOT_AVAILABLE)

            if request.lat == 0 and request.lng == 0:
                context.abort(grpc.StatusCode.INVALID_ARGUMENT, errors.INVALID_COORDINATE)

            user = User(
                email=signup_token.email,
                username=request.username,
                name=request.name,
                gender=request.gender,
                birthdate=birthdate,
                hosting_status=hostingstatus2sql[request.hosting_status],
                city=request.city,
                geom=create_coordinate(request.lat, request.lng),
                geom_radius=request.radius,
                accepted_tos=1 if request.accept_tos else 0,
            )

            # happens in same transaction
            session.delete(signup_token)

            # enforces email/username uniqueness
            session.add(user)
            session.commit()

            token, expiry = self._create_session(context, session, user, False)
            context.send_initial_metadata(
                [
                    ("set-cookie", create_session_cookie(token, expiry)),
                ]
            )
            return auth_pb2.AuthRes(jailed=user.is_jailed)
Esempio n. 8
0
    def CreateHostRequest(self, request, context):
        with session_scope() as session:
            if request.host_user_id == context.user_id:
                context.abort(grpc.StatusCode.INVALID_ARGUMENT,
                              errors.CANT_REQUEST_SELF)

            # just to check host exists and is visible
            host = session.execute(
                select(User).where_users_visible(context).where(
                    User.id == request.host_user_id)).scalar_one_or_none()
            if not host:
                context.abort(grpc.StatusCode.NOT_FOUND, errors.USER_NOT_FOUND)

            from_date = parse_date(request.from_date)
            to_date = parse_date(request.to_date)

            if not from_date or not to_date:
                context.abort(grpc.StatusCode.INVALID_ARGUMENT,
                              errors.INVALID_DATE)

            today = today_in_timezone(host.timezone)

            # request starts from the past
            if from_date < today:
                context.abort(grpc.StatusCode.INVALID_ARGUMENT,
                              errors.DATE_FROM_BEFORE_TODAY)

            # from_date is not >= to_date
            if from_date >= to_date:
                context.abort(grpc.StatusCode.INVALID_ARGUMENT,
                              errors.DATE_FROM_AFTER_TO)

            # No need to check today > to_date

            if from_date - today > timedelta(days=365):
                context.abort(grpc.StatusCode.INVALID_ARGUMENT,
                              errors.DATE_FROM_AFTER_ONE_YEAR)

            if to_date - from_date > timedelta(days=365):
                context.abort(grpc.StatusCode.INVALID_ARGUMENT,
                              errors.DATE_TO_AFTER_ONE_YEAR)

            conversation = Conversation()
            session.add(conversation)
            session.flush()

            session.add(
                Message(
                    conversation_id=conversation.id,
                    author_id=context.user_id,
                    message_type=MessageType.chat_created,
                ))

            message = Message(
                conversation_id=conversation.id,
                author_id=context.user_id,
                text=request.text,
                message_type=MessageType.text,
            )
            session.add(message)
            session.flush()

            host_request = HostRequest(
                conversation_id=conversation.id,
                surfer_user_id=context.user_id,
                host_user_id=host.id,
                from_date=from_date,
                to_date=to_date,
                status=HostRequestStatus.pending,
                surfer_last_seen_message_id=message.id,
                # TODO: tz
                # timezone=host.timezone,
            )
            session.add(host_request)
            session.commit()

            send_new_host_request_email(host_request)

            notify(
                user_id=host_request.host_user_id,
                topic="host_request",
                action="create",
                key=str(host_request.surfer_user_id),
                avatar_key=host_request.surfer.avatar.thumbnail_url
                if host_request.surfer.avatar else None,
                title=
                f"**{host_request.surfer.name}** sent you a hosting request",
                content=request.text,
                link=urls.host_request_link_host(),
            )

            return requests_pb2.CreateHostRequestRes(
                host_request_id=host_request.conversation_id)