示例#1
0
    async def test_verify_email_contact_method(self):
        async with op.session(self.appctx) as session:
            profile_id = await op.user_profile.\
                create_user_profile('Jesse',
                                    'Jesse Dhillon',
                                    '123foobar^#@',
                                    UserRole.Subscriber,
                                    '*****@*****.**',
                                    '+14155551234').\
                execute(session)
            profile = await op.user_profile.\
                get_user_profile(user_profile_id=profile_id).\
                execute(session)

        body = {
            'principalName': '*****@*****.**',
            'principalType': 'email',
        }
        resp = await self.client.request('POST', '/authn/session', json=body)
        assert resp.status == 200

        resp = await self.client.request(
            'GET',
            f'/users/{profile.user_profile_id}/contactMethods/{profile.email_contact_method_id}/verify'
        )
        j = await resp.json()
        assert resp.status == 200
        assert j['data']['hint'] == 'j****@dhillon.com'

        session = self.get_session()
        vc = session['pending_challenge']

        vcresp = {'challenge_id': vc['challenge_id'], 'passcode': vc['secret']}
        resp = await self.client.request(
            'POST',
            f'/users/{profile.user_profile_id}/contactMethods/{profile.email_contact_method_id}/verify',
            json=vcresp)
        assert resp.status == 200

        session = self.get_session()
        assert session['pending_challenge'] is None

        async with op.session(self.appctx) as session:
            cm = await op.user_profile.\
                    get_contact_method(profile.email_contact_method_id,
                                       user_profile_id=profile.user_profile_id).\
                    execute(session)
            assert cm.verified
示例#2
0
async def get_contact_method_verify_challenge(request, ctx: AppConfig,
                                              session: AuthnSession):
    try:
        user_profile_id = UUID(request.match_info['user_profile_id'])
        contact_method_id = UUID(request.match_info['contact_method_id'])
    except ValueError:
        raise HTTPNotFound()

    if user_profile_id != session.user_profile_id:
        raise HTTPForbidden()

    async with op.session(ctx) as ss:
        cm = await op.user_profile.\
            get_contact_method(contact_method_id, user_profile_id=user_profile_id).\
            execute(ss)

        if cm.verified:
            raise ValueError("contact method is already verified")

        if cm.contact_method_type is ContactMethodType.Email:
            # TODO: deliver challenge
            chtype = AuthnChallengeType.Email

        elif cm.contact_method_type is ContactMethodType.Phone:
            # TODO: deliver challenge
            chtype = AuthnChallengeType.SMS

        session.require_challenge(chtype)
        session.next_challenge_for_contact_method(cm)
        return session.pending_challenge.get_view('public')
示例#3
0
    async def test_get_plan(self):
        async with op.session(self.appctx) as ss:
            plan_id = await op.membership.create_subscription_plan(
                rank=1,
                name="Basic member",
                description="- Ad-free podcast episodes\n"
                            "- Access to episodes one week before non-subscribers\n"
                            "- Monthly members-only episode\n",
                payment_demands=(
                    (PaymentDemandType.Periodic, PaymentDemandPeriod.Quarterly,
                     Decimal('25.0'), 'USD'),
                    (PaymentDemandType.Periodic, PaymentDemandPeriod.Annually,
                     Decimal('90.0'), 'USD'),
                    (PaymentDemandType.Periodic, PaymentDemandPeriod.Monthly,
                     Decimal('10.0'), 'USD'),
                    (PaymentDemandType.Immediate, Decimal('250.0'), 'USD'))).\
                execute(ss)
            assert plan_id is not None

            plans = await op.membership.\
                get_subscription_plans().\
                unmarshal_with(SubscriptionPlan).\
                execute(ss)

            p = plans[0]
            assert len(plans) == 1
            assert len(p.payment_demands) == 4
示例#4
0
async def verify_contact_method(request, ctx: AppConfig,
                                session: AuthnSession):
    try:
        user_profile_id = UUID(request.match_info['user_profile_id'])
        contact_method_id = UUID(request.match_info['contact_method_id'])
    except ValueError:
        raise HTTPNotFound()

    challenge_response = AuthnChallengeResponseRequest.unmarshal_request(
        await request.json())

    if user_profile_id != session.user_profile_id:
        raise HTTPForbidden()

    if challenge_response.challenge_id != session.pending_challenge.challenge_id:
        raise HTTPBadRequest()

    session.pending_challenge.attempts += 1
    session.changed()
    if session.pending_challenge.attempts > security.MaxVerificationChallengeAttempts:
        session.invalidate()
        raise HTTPForbidden(body="Too many invalid attempts")

    if challenge_response.passcode != session.pending_challenge.secret:
        raise HTTPUnauthorized(body="Incorrect passcode")

    async with op.session(ctx) as ss:
        await op.user_profile.\
            mark_contact_method_verified(contact_method_id, user_profile_id=user_profile_id).\
            execute(ss)

    session.clear_pending_challenge()
    return HTTPOk()
示例#5
0
async def verify_authn_challenge(request, ctx: AppConfig,
                                 session: AuthnSession):
    if not session.pending_challenge:
        raise HTTPBadRequest()

    challenge_response =\
        AuthnChallengeResponseRequest.unmarshal_request(await request.json())

    if challenge_response.challenge_id != session.pending_challenge.challenge_id:
        raise ValueError("invalid challenge")

    session.pending_challenge.attempts += 1
    session.changed()
    if session.pending_challenge.attempts > security.MaxVerificationChallengeAttempts:
        session.invalidate()
        raise HTTPForbidden(text="Too many invalid attempts")

    otp_types = [AuthnChallengeType.Email, AuthnChallengeType.SMS]
    if session.pending_challenge.challenge_type in otp_types:
        if challenge_response.passcode != session.pending_challenge.secret:
            raise HTTPUnauthorized(text="Incorrect passcode")
    elif session.pending_challenge.challenge_type is AuthnChallengeType.Password:
        async with op.session(ctx) as ss:
            result = await op.authn.\
                authenticate_user(
                    user_profile_id=session.user_profile_id,
                    password=challenge_response.passcode).\
                execute(ss)
            if not result:
                raise HTTPUnauthorized(text="Incorrect passcode")
    else:
        raise HTTPBadRequest()

    session.clear_pending_challenge()
    async with op.session(ctx) as ss:
        u = await op.user_profile.\
            get_user_profile(user_profile_id=session.user_profile_id).\
            execute(ss)
    if session.required_challenges:
        session.next_challenge_for_user(u)
        return HTTPAccepted(), session.pending_challenge.get_view('public')

    session.authenticated = True
    session.set_role(u.role)
    session.remove_capabilities(Capability.Authenticate)
    return HTTPOk()
示例#6
0
    async def test_get_authn_challenge(self):
        async with op.session(self.appctx) as session:
            profile_id = await op.user_profile.\
                create_user_profile('Jesse', 'Jesse Dhillon', '123foobar^#@',
                                    UserRole.Subscriber, '*****@*****.**', '+14155551234').\
                execute(session)
            profile = await op.user_profile.\
                get_user_profile(user_profile_id=profile_id).\
                execute(session)
            await op.user_profile.\
                mark_contact_method_verified(contact_method_id=profile.email_contact_method_id).\
                execute(session)

        body = {
            'principalName': '*****@*****.**',
            'principalType': 'email',
        }
        await self.client.request('POST', '/authn/session', json=body)
        await self.client.request('GET', '/authn/challenge')

        session = self.get_session()
        assert session['pending_challenge'] is not None

        challenge = session['pending_challenge']
        chresp = {
            'challenge_id': challenge['challenge_id'],
            'passcode': challenge['secret'],
        }
        resp = await self.client.request('POST',
                                         '/authn/challenge',
                                         json={
                                             **chresp, 'passcode': 'foobar'
                                         })
        assert resp.status == 401
        j = await resp.json()
        assert not j['status']['success']
        assert 'Incorrect passcode' in [
            e['message'] for e in j['status']['errors']
        ]

        session = self.get_session()
        challenge = session['pending_challenge']
        assert challenge['attempts'] == 1

        resp = await self.client.request('POST',
                                         '/authn/challenge',
                                         json=chresp)
        session = self.get_session()
        assert resp.status == 200
        # check that we have some expected normie permissions
        assert set(session['capabilities']) >= set(
            ['profile.list', 'payment_method.create'])

        resp = await self.client.request('POST',
                                         '/authn/challenge',
                                         json=chresp)
        assert resp.status == 403
示例#7
0
async def get_subscription_plans(request, ctx: AppConfig,
                                 session: AuthnSession):
    async with op.session(ctx) as ss:
        plans = await op.membership.\
            get_subscription_plans().\
            unmarshal_with(SubscriptionPlan).\
            execute(ss)

    return plans.get_view('default')
示例#8
0
async def get_authn_challenge(request, ctx: AppConfig, session: AuthnSession):
    if session.pending_challenge:
        raise HTTPBadRequest(
            "cannot request a new challenge while challenges are pending")

    async with op.session(ctx) as ss:
        u = await op.user_profile.\
            get_user_profile(user_profile_id=session.user_profile_id).\
            execute(ss)

        if session.required_challenges:
            session.next_challenge_for_user(u)
            return session.pending_challenge.get_view('public')
示例#9
0
    async def test_add_multiple_payment_method(self):
        profile_id = await self.authenticate_as(UserRole.Subscriber)
        bodies = [{
            'processorId': 'com.example',
            'paymentCredential': {
                'methodType': 'credit_card',
                'cardNumber': '4242424242424242',
                'expMonth': 12,
                'expYear': 2023,
                'cvv': '444',
            }
        }, {
            'processorId': 'com.example',
            'paymentCredential': {
                'methodType': 'credit_card',
                'cardNumber': '4000056655665556',
                'expMonth': 12,
                'expYear': 2022,
                'cvv': '555',
            }
        }]

        pm_ids = []
        for b in bodies:
            resp = await self.client.request(
                'POST', f'/users/{profile_id}/paymentMethods', json=b)
            j = await resp.json()
            pm_ids.append(j['data'])

        payments = self.appctx.payments.get()
        mock = payments['com.example']

        async with op.session(self.appctx) as ss:
            pp = await op.user_profile.get_payment_profile(
                    user_profile_id=profile_id,
                    processor_id='com.example').\
                execute(ss)
            pms = await op.user_profile.get_payment_methods(
                    user_profile_id=profile_id,
                    payment_profile_id=pp.payment_profile_id).\
                execute(ss)
        assert pp.processor_customer_profile_id in mock._customers
        for pp_id, pmeths in pms.group_by('payment_profile_id').items():
            assert pp_id == pp.payment_profile_id
            assert len(pmeths) == 2
            for pm in pmeths:
                assert str(pm.payment_method_id) in pm_ids
示例#10
0
    async def test_cannot_get_authn_challenge_with_unverified_email(self):
        async with op.session(self.appctx) as session:
            profile_id = await op.user_profile.create_user_profile(
                'Jesse', 'Jesse Dhillon', '123foobar^#@', UserRole.Subscriber,
                '*****@*****.**', '+14155551234').execute(session)

        body = {
            'principalName': '*****@*****.**',
            'principalType': 'email',
        }
        await self.client.request('POST', '/authn/session', json=body)

        resp = await self.client.request('GET', '/authn/challenge')
        j = await resp.json()
        assert resp.status == 400
        assert "email j****@dhillon.com must be verified first" in\
               [e['message'] for e in j['status']['errors']]
示例#11
0
    async def authenticate_as(self, role):
        async with op.session(self.appctx) as session:
            profile_id = await op.user_profile.\
                create_user_profile('Test',
                                    'Test User',
                                    '123foobar^#@',
                                    role,
                                    '*****@*****.**',
                                    '+14155551234').\
                execute(session)
            profile = await op.user_profile.\
                get_user_profile(user_profile_id=profile_id).\
                execute(session)
            await op.user_profile.\
                mark_contact_method_verified(contact_method_id=profile.email_contact_method_id).\
                execute(session)

        body = {
            'principalName': profile.email_address,
            'principalType': 'email',
        }
        await self.client.request('POST', '/authn/session', json=body)
        await self.client.request('GET', '/authn/challenge')

        cookie = json.loads(self.get_cookie('TEST_SESSION').value)
        session = cookie['session']
        challenge = session['pending_challenge']

        chresp = {
            'challenge_id': challenge['challenge_id'],
            'passcode': challenge['secret'],
        }
        await self.client.request('POST', '/authn/challenge', json=chresp)

        cookie = json.loads(self.get_cookie('TEST_SESSION').value)
        session = cookie['session']
        if session['pending_challenge'] is not None:
            challenge = session['pending_challenge']

            chresp = {
                'challenge_id': challenge['challenge_id'],
                'passcode': '123foobar^#@',
            }
            await self.client.request('POST', '/authn/challenge', json=chresp)

        return profile_id
示例#12
0
    async def test_start_session(self):
        async with op.session(self.appctx) as session:
            profile_id = await op.user_profile.\
                create_user_profile('Jesse', 'Jesse Dhillon', '123foobar^#@',
                                    UserRole.Subscriber, '*****@*****.**', '+14155551234').\
                execute(session)

        body = {
            'principalName': '*****@*****.**',
            'principalType': 'email',
        }
        resp = await self.client.request('POST', '/authn/session', json=body)
        j = await resp.json()

        assert 'TEST_SESSION' in resp.cookies
        session = self.get_session()

        assert 'capabilities' in session
        assert set(session['capabilities']) == set(
            [Capability.Authenticate.value])
示例#13
0
async def create_subscription_plan(request, ctx: AppConfig,
                                   session: AuthnSession):
    plan = CreateSubscriptionPlanRequest.unmarshal_request(await
                                                           request.json())
    async with op.session(ctx) as ss:
        pds = []
        for pd in plan.payment_demands:
            if pd.demand_type is PaymentDemandType.Periodic:
                pds.append((pd.demand_type, pd.period, pd.amount,
                            pd.iso_currency, pd.non_iso_currency))
            elif pd.demand_type is PaymentDemandType.Immediate:
                pds.append((pd.demand_type, pd.amount, pd.iso_currency,
                            pd.non_iso_currency))
            else:
                raise ValueError(pd.demand_type)
        plan_id = await op.membership.\
            create_subscription_plan(rank=plan.rank, name=plan.name,
                                     description=plan.description,
                                     payment_demands=pds).\
            execute(ss)
    return plan_id
示例#14
0
    async def test_create_and_get_plan(self):
        async with op.session(self.appctx) as ss:
            plan_id = await op.membership.create_subscription_plan(
                rank=1,
                name="Basic member",
                description="- Ad-free podcast episodes\n"
                            "- Access to episodes one week before non-subscribers\n"
                            "- Monthly members-only episode\n",
                payment_demands=(
                    (PaymentDemandType.Periodic, PaymentDemandPeriod.Quarterly,
                     Decimal('25.0'), 'USD'),
                    (PaymentDemandType.Periodic, PaymentDemandPeriod.Annually,
                     Decimal('90.0'), 'USD'),
                    (PaymentDemandType.Periodic, PaymentDemandPeriod.Monthly,
                     Decimal('10.0'), 'USD'),
                    (PaymentDemandType.Immediate, Decimal('250.0'), 'USD'))).\
                execute(ss)
            assert plan_id is not None

            plans = await op.membership.get_subscription_plans().execute(ss)
        assert len(plans) == 4

        assert plans[0].demand_type is PaymentDemandType.Periodic
        assert plans[1].demand_type is PaymentDemandType.Periodic
        assert plans[2].demand_type is PaymentDemandType.Periodic
        assert plans[3].demand_type is PaymentDemandType.Immediate

        assert plans[0].period is PaymentDemandPeriod.Monthly
        assert plans[1].period is PaymentDemandPeriod.Quarterly
        assert plans[2].period is PaymentDemandPeriod.Annually
        assert plans[3].period is None

        assert plans[0].amount == Decimal('10.0')
        assert plans[1].amount == Decimal('25.0')
        assert plans[2].amount == Decimal('90.0')
        assert plans[3].amount == Decimal('250.0')

        s = {p.subscription_plan_id for p in plans}
        assert len(s) == 1
示例#15
0
async def init_authn_session(request, ctx: AppConfig, session: AuthnSession):
    sr = InitiateAuthnSessionRequest.unmarshal_request(await request.json())

    async with op.session(ctx) as ss:
        if sr.principal_type is AuthnPrincipalType.Email:
            u = await op.user_profile.get_user_profile(
                email_address=sr.principal_name).execute(ss)
            session.require_challenge(AuthnChallengeType.Email)
        elif sr.principal_type is AuthnPrincipalType.Phone:
            u = await op.user_profile.get_user_profile(
                phone_number=sr.principal_name).execute(ss)
            session.require_challenge(AuthnChallengeType.SMS)
        else:
            raise ValueError(sr.principal_type)

    if u is None:
        raise HTTPUnauthorized()

    session.user_profile_id = u.user_profile_id
    if u.role in {UserRole.Superuser, UserRole.Manager, UserRole.Creator}:
        session.require_challenge(AuthnChallengeType.Password)

    session.add_capabilities(Capability.Authenticate)
    return HTTPOk()
示例#16
0
async def add_payment_method(request, ctx: AppConfig, session: AuthnSession):
    try:
        user_profile_id = UUID(request.match_info['user_profile_id'])
    except ValueError:
        raise HTTPNotFound()

    if user_profile_id != session.user_profile_id:
        raise HTTPForbidden()

    async with op.session(ctx) as ss:
        # fail if user's email address is not verified
        u = await op.user_profile.get_user_profile(
            user_profile_id=user_profile_id).execute(ss)
        if not u.email_contact_method_verified:
            raise ValueError("email address must be verified first")

        addpmrq = AddPaymentMethodRequest.unmarshal_request(await
                                                            request.json())
        payments = ctx.payments.get()
        processor = payments[addpmrq.processor_id]

        pp = await op.user_profile.\
            get_payment_profile(user_profile_id=user_profile_id,
                                processor_id=addpmrq.processor_id).\
            execute(ss)

        if pp is not None:
            pp_id = pp.payment_profile_id
            cust_id = pp.processor_customer_profile_id
        else:
            cust_id = await processor.create_customer_profile(
                u.user_profile_id,
                u.name,
                u.email_address,
                u.phone_number,
                address=None)
            pp_id = await op.user_profile.\
                add_payment_profile(
                    user_profile_id=u.user_profile_id,
                    processor_id=processor.processor_id,
                    processor_customer_profile_id=cust_id).\
                execute(ss)

    async with op.session(ctx) as ss:
        method_id = await processor.\
            add_customer_payment_method(cust_id, addpmrq.payment_credential)

        pm_id = await op.user_profile.\
            add_payment_method(
                user_profile_id=u.user_profile_id,
                payment_profile_id=pp_id,
                processor_payment_method_id=method_id,
                payment_method_type=addpmrq.payment_credential.method_type,
                payment_method_family=addpmrq.payment_credential.method_family,
                display_name=addpmrq.payment_credential.display_name,
                safe_account_number_fragment=\
                    addpmrq.payment_credential.safe_account_number_fragment,
                expires_after=addpmrq.payment_credential.expire_after_date).\
            execute(ss)

    return pm_id
示例#17
0
async def create_subscription(request, ctx: AppConfig, session: AuthnSession):
    subscription_plan_id = request.match_info['subscription_plan_id']
    subreq = CreateSubscriptionRequest.unmarshal_request(await request.json())

    async with op.session(ctx) as ss:
        # TODO: just use and expand the get_payment_profile method
        profiles = await op.user_profile.\
            get_payment_methods(user_profile_id=session.user_profile_id,
                                payment_method_id=subreq.payment_method_id).\
            unmarshal_with(PaymentProfile).\
            execute(ss)
        pp = profiles[0]
        pm = pp.payment_methods.find(
            payment_method_id=subreq.payment_method_id)

        plan = await op.membership.\
            get_subscription_plan(subscription_plan_id=subreq.subscription_plan_id).\
            unmarshal_with(SubscriptionPlan).\
            execute(ss)
        pd = plan.payment_demands.find(
            payment_demand_id=subreq.payment_demand_id)

        payments = ctx.payments.get()
        processor = payments[pp.processor_id]

        if pd.demand_type is PaymentDemandType.Periodic:
            charge_id = await processor.create_recurring_charge(
                vocal_user_profile_id=session.user_profile_id,
                customer_profile_id=pp.processor_customer_profile_id,
                payment_method_id=pm.processor_payment_method_id,
                start_date=datetime.today(),
                period=pd.period,
                amount=pd.amount,
                iso_currency=pd.iso_currency,
                non_iso_currency=pd.non_iso_currency)
            return await op.membership.\
                create_subscription(
                    user_profile_id=session.user_profile_id,
                    subscription_plan_id=plan.subscription_plan_id,
                    payment_demand_id=pd.payment_demand_id,
                    payment_profile_id=pp.payment_profile_id,
                    payment_method_id=pm.payment_method_id,
                    processor_charge_id=charge_id).\
                unmarshal_with(Subscription).\
                execute(ss)
        elif pd.demand_type is PaymentDemandType.Immediate:
            charge_id = await processor.create_immediate_charge(
                vocal_user_profile_id=session.user_profile_id,
                customer_profile_id=pp.processor_customer_profile_id,
                payment_method_id=pm.processor_payment_method_id,
                amount=pd.amount,
                iso_currency=pd.iso_currency,
                non_iso_currency=pd.non_iso_currency)
            return await op.membership.\
                create_subscription(
                    user_profile_id=session.user_profile_id,
                    subscription_plan_id=plan.subscription_plan_id,
                    payment_demand_id=pd.payment_demand_id,
                    payment_profile_id=pp.payment_profile_id,
                    payment_method_id=pm.payment_method_id,
                    processor_charge_id=charge_id).\
                unmarshal_with(Subscription).\
                execute(ss)
        else:
            raise ValueError(pd.demand_type)