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")
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
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)
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
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)
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, )
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)
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)