Example #1
0
    def make_participant(self, username, **kw):
        platform = kw.pop('elsewhere', 'github')
        domain = kw.pop('domain', '')
        kw2 = {}
        for key in ('last_bill_result', 'balance', 'mangopay_wallet_id'):
            if key in kw:
                kw2[key] = kw.pop(key)

        kind = kw.setdefault('kind', 'individual')
        if kind not in ('group', 'community'):
            kw.setdefault('password', 'x')
            kw.setdefault('session_token', username)
            i = next(self.seq)
            kw.setdefault('mangopay_user_id', -i)
        kw.setdefault('status', 'active')
        if username:
            kw['username'] = username
        if 'join_time' not in kw:
            kw['join_time'] = utcnow()
        cols, vals = zip(*kw.items())
        cols = ', '.join(cols)
        placeholders = ', '.join(['%s']*len(vals))
        participant = self.db.one("""
            INSERT INTO participants
                        ({0})
                 VALUES ({1})
              RETURNING participants.*::participants
        """.format(cols, placeholders), vals)

        self.db.run("""
            INSERT INTO elsewhere
                        (platform, user_id, user_name, participant, domain)
                 VALUES (%s,%s,%s,%s,%s)
        """, (platform, participant.id, username, participant.id, domain))

        if kind not in ('group', 'community') and participant.mangopay_user_id:
            wallet_id = kw2.get('mangopay_wallet_id', -participant.id)
            zero = ZERO[participant.main_currency]
            self.db.run("""
                INSERT INTO wallets
                            (remote_id, balance, owner, remote_owner_id)
                     VALUES (%s, %s, %s, %s)
            """, (wallet_id, zero, participant.id, participant.mangopay_user_id))

        if 'email' in kw:
            self.db.run("""
                INSERT INTO emails
                            (participant, address, verified, verified_time)
                     VALUES (%s, %s, true, now())
            """, (participant.id, kw['email']))
        if 'last_bill_result' in kw2:
            ExchangeRoute.insert(
                participant, 'mango-cc', '-1', kw2['last_bill_result'],
                currency=participant.main_currency
            )
        if 'balance' in kw2 and kw2['balance'] != 0:
            self.make_exchange('mango-cc', kw2['balance'], 0, participant)

        return participant
Example #2
0
 def test_log_in_with_old_session(self):
     alice = self.make_participant('alice')
     alice.update_session('x', utcnow() - timedelta(days=1))
     alice.authenticated = True
     cookies = SimpleCookie()
     alice.sign_in(cookies)
     print(cookies)
     self.check_with_about_me('alice', cookies)
Example #3
0
def to_age(dt, **kw):
    if isinstance(dt, datetime):
        delta = Age(dt - utcnow())
    else:
        delta = Age(dt - date.today())
        kw.setdefault('granularity', 'day')
    delta.format_args = kw
    return delta
Example #4
0
 def rotate_key(self):
     """Generate a new key and send it to the secrets manager.
     """
     keys = b' '.join([Fernet.generate_key()] + self.fernet_keys).decode()
     if self.secrets_manager:
         self.secrets_manager.update_secret(SecretId='Fernet', SecretString=keys)
     else:
         keys = utcnow().date().isoformat() + ' ' + keys
         print("No secrets manager, updating the key storage is up to you.")
     return keys
Example #5
0
def to_age(dt, **kw):
    kw.setdefault('add_direction', True)
    if isinstance(dt, datetime):
        delta = Age(dt - utcnow())
    elif isinstance(dt, timedelta):
        delta = Age(dt)
    else:
        delta = Age(dt - date.today())
        kw.setdefault('granularity', 'day')
    delta.format_args = kw
    return delta
Example #6
0
    def rotate_stored_data(self, wait=True):
        """Re-encrypt all the sensitive information stored in our database.

        This function is a special kind of "cron job" that returns one of two
        constants from the `liberapay.cron` module: `CRON_ENCORE`, indicating
        that the function needs to be run again to continue its work, or
        `CRON_STOP`, indicating that all the ciphertexts are up-to-date (or that
        it isn't time to rotate yet).

        Rows are processed in batches of 50. Timestamps are used to keep track of
        progress and to avoid overwriting new data with re-encrypted old data.

        The update only starts one week after the new key was generated, unless
        `wait` is set to `False`. This delay is to "ensure" that the previous
        key is no longer being used to encrypt new data.
        """
        update_start = self.fernet_rotation_start + self.KEY_ROTATION_DELAY
        if wait:
            if utcnow().date() < update_start:
                return CRON_STOP

        with website.db.get_cursor() as cursor:
            batch = cursor.all("""
                SELECT id, info
                  FROM identities
                 WHERE (info).ts <= %s
              ORDER BY (info).ts ASC
                 LIMIT 50
            """, (update_start,))
            if not batch:
                return CRON_STOP

            sql = """
                UPDATE identities
                   SET info = ('fernet', %s, current_timestamp)::encrypted
                 WHERE id = %s
                   AND (info).ts = %s;
            """
            args_list = [
                (self.rotate_message(r.info.payload), r.id, r.info.ts)
                for r in batch
            ]
            execute_batch(cursor, sql, args_list)

        return CRON_ENCORE
Example #7
0
def set_cookie(cookies, key, value, expires=None, httponly=True, path='/'):
    key = ensure_str(key)
    cookies[key] = ensure_str(value)
    cookie = cookies[key]
    if expires:
        if isinstance(expires, timedelta):
            expires += utcnow()
        if isinstance(expires, datetime):
            expires = to_rfc822(expires)
        cookie[str('expires')] = ensure_str(expires)
    if httponly:
        cookie[str('httponly')] = True
    if path:
        cookie[str('path')] = ensure_str(path)
    if website.cookie_domain:
        cookie[str('domain')] = ensure_str(website.cookie_domain)
    if website.canonical_scheme == 'https':
        cookie[str('secure')] = True
Example #8
0
    def encrypt_dict(self, dic, allow_single_key=False):
        """Serialize and encrypt a dictionary for storage in the database.

        Encrypting partially predictable data may help an attacker break the
        encryption key, so to make our data less predictable we randomize the
        order of the dict's items before serializing it.

        For this to be effective the CBOR serializer must not sort the items
        again in an attempt to produce Canonical CBOR, so we explicitly pass
        `canonical=False` to the `cbor.dumps` function.

        In addition, the dict must not contain only one key if that key is
        predictable, so a `CryptoWarning` is emitted when `dic` only contains
        one key, unless `allow_single_key` is set to `True`.
        """
        dic = self.randomize_dict(dic, allow_single_key=allow_single_key)
        serialized = cbor.dumps(dic, canonical=False)
        encrypted = self.fernet.encrypt(serialized)
        return Encrypted(dict(scheme='fernet', payload=encrypted, ts=utcnow()))
Example #9
0
def build_s3_object_url(key):
    now = utcnow()
    timestamp = now.strftime('%Y%m%dT%H%M%SZ')
    today = timestamp.split('T', 1)[0]
    region = website.app_conf.s3_region
    access_key = website.app_conf.s3_public_access_key
    endpoint = website.app_conf.s3_endpoint
    assert endpoint.startswith('https://')
    host = endpoint[8:]
    querystring = (
        "X-Amz-Algorithm=AWS4-HMAC-SHA256&"
        "X-Amz-Credential={access_key}%2F{today}%2F{region}%2Fs3%2Faws4_request&"
        "X-Amz-Date={timestamp}&"
        "X-Amz-Expires=86400&"
        "X-Amz-SignedHeaders=host"
    ).format(**locals())
    canonical_request = (
        "GET\n"
        "/{key}\n"
        "{querystring}\n"
        "host:{host}\n"
        "\n"
        "host\n"
        "UNSIGNED-PAYLOAD"
    ).format(**locals()).encode()
    canonical_request_hash = sha256(canonical_request).hexdigest()
    string_to_sign = (
        "AWS4-HMAC-SHA256\n"
        "{timestamp}\n"
        "{today}/{region}/s3/aws4_request\n"
        "{canonical_request_hash}"
    ).format(**locals()).encode()
    aws4_secret_key = b"AWS4" + website.app_conf.s3_secret_key.encode()
    sig_key = hmac.new(aws4_secret_key, today.encode(), sha256).digest()
    sig_key = hmac.new(sig_key, region.encode(), sha256).digest()
    sig_key = hmac.new(sig_key, b"s3", sha256).digest()
    sig_key = hmac.new(sig_key, b"aws4_request", sha256).digest()
    signature = hmac.new(sig_key, string_to_sign, sha256).hexdigest()
    return endpoint + "/" + key + "?" + querystring + "&X-Amz-Signature=" + signature
 def make_connect_token(self):
     token = uuid.uuid4().hex
     expires = utcnow() + CONNECT_TOKEN_TIMEOUT
     return self.save_connect_token(token, expires)
Example #11
0
def update_payin(
    db, payin_id, remote_id, status, error,
    amount_settled=None, fee=None, intent_id=None, refunded_amount=None,
):
    """Update the status and other attributes of a charge.

    Args:
        payin_id (int): the ID of the charge in our database
        remote_id (str): the ID of the charge in the payment processor's database
        status (str): the new status of the charge
        error (str): if the charge failed, an error message to show to the payer

    Returns:
        Record: the row updated in the `payins` table

    """
    with db.get_cursor() as cursor:
        payin = cursor.one("""
            UPDATE payins
               SET status = %(status)s
                 , error = %(error)s
                 , remote_id = coalesce(remote_id, %(remote_id)s)
                 , amount_settled = coalesce(amount_settled, %(amount_settled)s)
                 , fee = coalesce(fee, %(fee)s)
                 , intent_id = coalesce(intent_id, %(intent_id)s)
                 , refunded_amount = coalesce(%(refunded_amount)s, refunded_amount)
             WHERE id = %(payin_id)s
         RETURNING *
                 , (SELECT status FROM payins WHERE id = %(payin_id)s) AS old_status
        """, locals())
        if not payin:
            return
        if remote_id and payin.remote_id != remote_id:
            raise AssertionError(f"the remote IDs don't match: {payin.remote_id!r} != {remote_id!r}")

        if status != payin.old_status:
            cursor.run("""
                INSERT INTO payin_events
                       (payin, status, error, timestamp)
                VALUES (%s, %s, %s, current_timestamp)
            """, (payin_id, status, error))

        if status in ('pending', 'succeeded'):
            cursor.run("""
                UPDATE exchange_routes
                   SET status = 'consumed'
                 WHERE id = %s
                   AND one_off IS TRUE
            """, (payin.route,))

        # Lock to avoid concurrent updates
        cursor.run("SELECT * FROM participants WHERE id = %s FOR UPDATE",
                   (payin.payer,))

        # Update scheduled payins, if appropriate
        if status in ('pending', 'succeeded'):
            sp = cursor.one("""
                SELECT *
                  FROM scheduled_payins
                 WHERE payer = %s
                   AND payin = %s
            """, (payin.payer, payin.id))
            if not sp:
                # Try to find a scheduled renewal that matches this payin.
                # It doesn't have to be an exact match.
                schedule = cursor.all("""
                    SELECT *
                      FROM scheduled_payins
                     WHERE payer = %s
                       AND payin IS NULL
                       AND mtime < %s
                """, (payin.payer, payin.ctime))
                today = utcnow().date()
                schedule.sort(key=lambda sp: abs((sp.execution_date - today).days))
                payin_tippees = set(cursor.all("""
                    SELECT coalesce(team, recipient) AS tippee
                      FROM payin_transfers
                     WHERE payer = %s
                       AND payin = %s
                """, (payin.payer, payin.id)))
                for sp in schedule:
                    if any((tr['tippee_id'] in payin_tippees) for tr in sp.transfers):
                        cursor.run("""
                            UPDATE scheduled_payins
                               SET payin = %s
                                 , mtime = current_timestamp
                             WHERE id = %s
                        """, (payin.id, sp.id))
                        break

    return payin
Example #12
0
    AD AE AF AG AI AL AM AO AQ AR AS AT AU AW AX AZ BA BB BD BE BF BG BH BI BJ
    BL BM BN BO BQ BR BS BT BV BW BY BZ CA CC CD CF CG CH CI CK CL CM CN CO CR
    CU CV CW CX CY CZ DE DJ DK DM DO DZ EC EE EG EH ER ES ET FI FJ FK FM FO FR
    GA GB GD GE GF GG GH GI GL GM GN GP GQ GR GS GT GU GW GY HK HM HN HR HT HU
    ID IE IL IM IN IO IQ IR IS IT JE JM JO JP KE KG KH KI KM KN KP KR KW KY KZ
    LA LB LC LI LK LR LS LT LU LV LY MA MC MD ME MF MG MH MK ML MM MN MO MP MQ
    MR MS MT MU MV MW MX MY MZ NA NC NE NF NG NI NL NO NP NR NU NZ OM PA PE PF
    PG PH PK PL PM PN PR PS PT PW PY QA RE RO RS RU RW SA SB SC SD SE SG SH SI
    SJ SK SL SM SN SO SR SS ST SV SX SY SZ TC TD TF TG TH TJ TK TL TM TN TO TR
    TT TV TW TZ UA UG UM US UY UZ VA VC VE VG VI VN VU WF WS YE YT ZA ZM ZW
""".split()

COUNTRIES = make_sorted_dict(COUNTRY_CODES, Locale('en').territories)

CURRENCIES_MAP = {}
today = utcnow().date()
for country, currencies in babel.core.get_global('territory_currencies').items():
    for currency, start_date, end_date, tender in currencies:
        if currency not in CURRENCIES:
            continue
        if start_date:
            start_date = date(*start_date)
        if end_date:
            end_date = date(*end_date)
        if (start_date is None or start_date <= today) and (end_date is None or end_date >= today):
            assert country not in CURRENCIES_MAP
            CURRENCIES_MAP[country] = currency
del today

LANGUAGE_CODES_2 = """
    aa af ak am ar as az be bg bm bn bo br bs ca cs cy da de dz ee el en eo es
Example #13
0
def update_payin(
    db,
    payin_id,
    remote_id,
    status,
    error,
    amount_settled=None,
    fee=None,
    intent_id=None,
    refunded_amount=None,
):
    """Update the status and other attributes of a charge.

    Args:
        payin_id (int): the ID of the charge in our database
        remote_id (str): the ID of the charge in the payment processor's database
        status (str): the new status of the charge
        error (str): if the charge failed, an error message to show to the payer

    Returns:
        Record: the row updated in the `payins` table

    """
    with db.get_cursor() as cursor:
        payin = cursor.one(
            """
            WITH old AS (
                SELECT * FROM payins WHERE id = %(payin_id)s
            )
            UPDATE payins
               SET status = %(status)s
                 , error = %(error)s
                 , remote_id = coalesce(%(remote_id)s, remote_id)
                 , amount_settled = COALESCE(%(amount_settled)s, amount_settled)
                 , fee = COALESCE(%(fee)s, fee)
                 , intent_id = coalesce(%(intent_id)s, intent_id)
                 , refunded_amount = coalesce(%(refunded_amount)s, refunded_amount)
             WHERE id = %(payin_id)s
               AND ( status <> %(status)s OR
                     coalesce_currency_amount(%(refunded_amount)s, amount::currency) <>
                     coalesce_currency_amount(refunded_amount, amount::currency)
                   )
         RETURNING *
                 , (SELECT status FROM old) AS old_status
        """, locals())
        if not payin:
            return cursor.one("SELECT * FROM payins WHERE id = %s",
                              (payin_id, ))

        if payin.status != payin.old_status:
            cursor.run(
                """
                INSERT INTO payin_events
                       (payin, status, error, timestamp)
                VALUES (%s, %s, %s, current_timestamp)
            """, (payin_id, status, error))

        if payin.status in ('pending', 'succeeded'):
            cursor.run(
                """
                UPDATE exchange_routes
                   SET status = 'consumed'
                 WHERE id = %s
                   AND one_off IS TRUE
            """, (payin.route, ))

        # Lock to avoid concurrent updates
        cursor.run("SELECT * FROM participants WHERE id = %s FOR UPDATE",
                   (payin.payer, ))

        # Update scheduled payins, if appropriate
        if payin.status in ('pending', 'succeeded'):
            sp = cursor.one(
                """
                SELECT *
                  FROM scheduled_payins
                 WHERE payer = %s
                   AND payin = %s
            """, (payin.payer, payin.id))
            if not sp:
                schedule = cursor.all(
                    """
                    SELECT *
                      FROM scheduled_payins
                     WHERE payer = %s
                       AND payin IS NULL
                """, (payin.payer, ))
                today = utcnow().date()
                schedule.sort(
                    key=lambda sp: abs((sp.execution_date - today).days))
                payin_tippees = set(
                    cursor.all(
                        """
                    SELECT coalesce(team, recipient) AS tippee
                      FROM payin_transfers
                     WHERE payer = %s
                       AND payin = %s
                """, (payin.payer, payin.id)))
                for sp in schedule:
                    matching_tippees_count = 0
                    other_transfers = []
                    for tr in sp.transfers:
                        if tr['tippee_id'] in payin_tippees:
                            matching_tippees_count += 1
                        else:
                            other_transfers.append(tr)
                    if matching_tippees_count > 0:
                        if other_transfers:
                            cursor.run(
                                """
                                UPDATE scheduled_payins
                                   SET payin = %s
                                     , mtime = current_timestamp
                                 WHERE id = %s
                            """, (payin.id, sp.id))
                            other_transfers_sum = Money.sum(
                                (Money(**tr['amount'])
                                 for tr in other_transfers), sp['amount'].
                                currency) if sp['amount'] else None
                            cursor.run(
                                """
                                INSERT INTO scheduled_payins
                                            (ctime, mtime, execution_date, payer,
                                             amount, transfers, automatic,
                                             notifs_count, last_notif_ts,
                                             customized, payin)
                                     VALUES (%(ctime)s, now(), %(execution_date)s, %(payer)s,
                                             %(amount)s, %(transfers)s, %(automatic)s,
                                             %(notifs_count)s, %(last_notif_ts)s,
                                             %(customized)s, NULL)
                            """,
                                dict(
                                    sp._asdict(),
                                    amount=other_transfers_sum,
                                    transfers=json.dumps(other_transfers),
                                ))
                        else:
                            cursor.run(
                                """
                                UPDATE scheduled_payins
                                   SET payin = %s
                                     , mtime = current_timestamp
                                 WHERE id = %s
                            """, (payin.id, sp.id))
                        break
        elif payin.status == 'failed':
            cursor.run(
                """
                UPDATE scheduled_payins
                   SET payin = NULL
                 WHERE payer = %s
                   AND payin = %s
            """, (payin.payer, payin.id))

        return payin
Example #14
0
def to_age(dt):
    if isinstance(dt, datetime):
        return dt - utcnow()
    return datedelta(dt - date.today())
Example #15
0
def get_start_of_current_utc_day():
    """Returns a `datetime` for the start of the current day in the UTC timezone.
    """
    now = utcnow()
    return datetime(now.year, now.month, now.day, tzinfo=utc)
Example #16
0
    def make_participant(self, username, **kw):
        platform = kw.pop('elsewhere', 'github')
        domain = kw.pop('domain', '')
        kw2 = {}
        for key in ('route_status', 'balance', 'mangopay_wallet_id'):
            if key in kw:
                kw2[key] = kw.pop(key)

        kind = kw.setdefault('kind', 'individual')
        is_person = kind not in ('group', 'community')
        if is_person:
            i = next(self.seq)
            kw.setdefault('mangopay_user_id', -i)
        kw.setdefault('status', 'active')
        if username:
            kw['username'] = username
        if 'join_time' not in kw:
            kw['join_time'] = utcnow()
        kw.setdefault('email_lang', 'en')
        kw.setdefault('main_currency', 'EUR')
        kw.setdefault('accepted_currencies', kw['main_currency'])

        cols, vals = zip(*kw.items())
        cols = ', '.join(cols)
        placeholders = ', '.join(['%s']*len(vals))
        participant = self.db.one("""
            INSERT INTO participants
                        ({0})
                 VALUES ({1})
              RETURNING participants.*::participants
        """.format(cols, placeholders), vals)

        self.db.run("""
            INSERT INTO elsewhere
                        (platform, user_id, user_name, participant, domain)
                 VALUES (%s,%s,%s,%s,%s)
        """, (platform, participant.id, username, participant.id, domain))

        if is_person and participant.mangopay_user_id:
            wallet_id = kw2.get('mangopay_wallet_id', -participant.id)
            zero = ZERO[participant.main_currency]
            self.db.run("""
                INSERT INTO wallets
                            (remote_id, balance, owner, remote_owner_id)
                     VALUES (%s, %s, %s, %s)
            """, (wallet_id, zero, participant.id, participant.mangopay_user_id))

        if 'email' in kw:
            self.db.run("""
                INSERT INTO emails
                            (participant, address, verified, verified_time)
                     VALUES (%s, %s, true, now())
            """, (participant.id, kw['email']))
        if 'route_status' in kw2:
            ExchangeRoute.insert(
                participant, 'mango-cc', '-1', kw2['route_status'],
                currency=participant.main_currency
            )
        if 'balance' in kw2 and kw2['balance'] != 0:
            self.make_exchange('mango-cc', kw2['balance'], 0, participant)

        return participant
Example #17
0
    def upsert(cls, i):
        """Insert or update a user's info.
        """

        i.info_fetched_at = utcnow()

        # Clean up avatar_url
        if i.avatar_url:
            scheme, netloc, path, query, fragment = urlsplit(i.avatar_url)
            fragment = ''
            if netloc.endswith('githubusercontent.com') or \
               netloc.endswith('gravatar.com') or \
               netloc.endswith('libravatar.org'):
                query = AVATAR_QUERY
            i.avatar_url = urlunsplit((scheme, netloc, path, query, fragment))

        # Serialize extra_info
        if isinstance(i.extra_info, ET.Element):
            i.extra_info = xmltodict.parse(ET.tostring(i.extra_info))
        i.extra_info = json.dumps(i.extra_info)

        d = dict(i.__dict__)
        d.pop('email', None)
        cols, vals = zip(*d.items())
        cols = ', '.join(cols)
        placeholders = ', '.join(['%s'] * len(vals))

        def update():
            return cls.db.one(
                """
                UPDATE elsewhere
                   SET ({0}) = ({1})
                 WHERE platform=%s AND domain=%s AND user_id=%s
             RETURNING elsewhere.*::elsewhere_with_participant
            """.format(cols, placeholders),
                vals + (i.platform, i.domain, i.user_id))

        account = update() if i.user_id else None
        if not account:
            try:
                # Try to insert the account
                # We do this with a transaction so that if the insert fails, the
                # participant we reserved for them is rolled back as well.
                with cls.db.get_cursor() as cursor:
                    account = cursor.one(
                        """
                        WITH p AS (
                                 INSERT INTO participants DEFAULT VALUES RETURNING id
                             )
                        INSERT INTO elsewhere
                                    (participant, {0})
                             VALUES ((SELECT id FROM p), {1})
                          RETURNING elsewhere.*::elsewhere_with_participant
                    """.format(cols, placeholders), vals)
            except IntegrityError:
                # The account is already in the DB, update it instead
                if i.user_name and i.user_id:
                    # Set user_id if it was missing
                    cls.db.run(
                        """
                        UPDATE elsewhere
                           SET user_id = %s
                         WHERE platform=%s AND domain=%s AND lower(user_name)=%s
                           AND user_id IS NULL
                    """,
                        (i.user_id, i.platform, i.domain, i.user_name.lower()))
                elif not i.user_id:
                    return cls._from_thing('user_name', i.platform,
                                           i.user_name, i.domain)
                account = update()
                if not account:
                    raise

        # Return account after propagating avatar_url to participant
        account.participant.update_avatar()
        return account
 def make_connect_token(self):
     token = uuid.uuid4().hex
     expires = utcnow() + CONNECT_TOKEN_TIMEOUT
     return self.save_connect_token(token, expires)
Example #19
0
    AD AE AF AG AI AL AM AO AQ AR AS AT AU AW AX AZ BA BB BD BE BF BG BH BI BJ
    BL BM BN BO BQ BR BS BT BV BW BY BZ CA CC CD CF CG CH CI CK CL CM CN CO CR
    CU CV CW CX CY CZ DE DJ DK DM DO DZ EC EE EG EH ER ES ET FI FJ FK FM FO FR
    GA GB GD GE GF GG GH GI GL GM GN GP GQ GR GS GT GU GW GY HK HM HN HR HT HU
    ID IE IL IM IN IO IQ IR IS IT JE JM JO JP KE KG KH KI KM KN KP KR KW KY KZ
    LA LB LC LI LK LR LS LT LU LV LY MA MC MD ME MF MG MH MK ML MM MN MO MP MQ
    MR MS MT MU MV MW MX MY MZ NA NC NE NF NG NI NL NO NP NR NU NZ OM PA PE PF
    PG PH PK PL PM PN PR PS PT PW PY QA RE RO RS RU RW SA SB SC SD SE SG SH SI
    SJ SK SL SM SN SO SR SS ST SV SX SY SZ TC TD TF TG TH TJ TK TL TM TN TO TR
    TT TV TW TZ UA UG UM US UY UZ VA VC VE VG VI VN VU WF WS YE YT ZA ZM ZW
""".split()

COUNTRIES = make_sorted_dict(COUNTRY_CODES, Locale('en').territories)

CURRENCIES_MAP = {}
today = utcnow().date()
for country, currencies in babel.core.get_global('territory_currencies').items():
    for currency, start_date, end_date, tender in currencies:
        if currency not in CURRENCIES:
            continue
        if start_date:
            start_date = date(*start_date)
        if end_date:
            end_date = date(*end_date)
        if (start_date is None or start_date <= today) and (end_date is None or end_date >= today):
            assert country not in CURRENCIES_MAP
            CURRENCIES_MAP[country] = currency
del today

LANGUAGE_CODES_2 = """
    aa af ak am ar as az be bg bm bn bo br bs ca cs cy da de dz ee el en eo es
 def check_connect_token(self, token):
     return (self.connect_token
             and constant_time_compare(self.connect_token, token)
             and self.connect_expires > utcnow())
    def upsert(cls, i):
        """Insert or update a user's info.
        """

        i.info_fetched_at = utcnow()

        # Clean up avatar_url
        if i.avatar_url:
            scheme, netloc, path, query, fragment = urlsplit(i.avatar_url)
            fragment = ''
            if netloc.endswith('githubusercontent.com') or \
               netloc.endswith('gravatar.com') or \
               netloc.endswith('libravatar.org'):
                query = AVATAR_QUERY
            i.avatar_url = urlunsplit((scheme, netloc, path, query, fragment))

        d = dict(i.__dict__)
        d.pop('email', None)
        cols, vals = zip(*d.items())
        cols = ', '.join(cols)
        placeholders = ', '.join(['%s'] * len(vals))

        def update():
            return cls.db.one(
                """
                UPDATE elsewhere
                   SET ({0}) = ({1})
                 WHERE platform=%s AND domain=%s AND user_id=%s
             RETURNING elsewhere.*::elsewhere_with_participant
            """.format(cols, placeholders),
                vals + (i.platform, i.domain, i.user_id))

        # Check for and handle a possible user_name reallocation
        if i.user_name:
            conflicts_with = cls.db.one(
                """
                SELECT e.*::elsewhere_with_participant
                  FROM elsewhere e
                 WHERE e.platform = %s
                   AND e.domain = %s
                   AND lower(e.user_name) = %s
                   AND e.user_id <> %s
            """, (i.platform, i.domain, i.user_name.lower(), i.user_id))
            if conflicts_with is not None:
                try:
                    conflicts_with.refresh_user_info()
                except (UnableToRefreshAccount, UserNotFound):
                    cls.db.run(
                        """
                        UPDATE elsewhere
                           SET user_name = null
                         WHERE id = %s
                           AND platform = %s
                           AND domain = %s
                           AND user_name = %s
                    """, (conflicts_with.id, i.platform, i.domain,
                          conflicts_with.user_name))
            del conflicts_with

        account = update() if i.user_id else None
        if not account:
            try:
                # Try to insert the account
                # We do this with a transaction so that if the insert fails, the
                # participant we reserved for them is rolled back as well.
                with cls.db.get_cursor() as cursor:
                    account = cursor.one(
                        """
                        WITH p AS (
                                 INSERT INTO participants DEFAULT VALUES RETURNING id
                             )
                        INSERT INTO elsewhere
                                    (participant, {0})
                             VALUES ((SELECT id FROM p), {1})
                          RETURNING elsewhere.*::elsewhere_with_participant
                    """.format(cols, placeholders), vals)
            except IntegrityError:
                # The account is already in the DB, update it instead
                if i.user_name and i.user_id:
                    # Set user_id if it was missing
                    cls.db.run(
                        """
                        UPDATE elsewhere
                           SET user_id = %s
                         WHERE platform=%s AND domain=%s AND lower(user_name)=%s
                           AND user_id IS NULL
                    """,
                        (i.user_id, i.platform, i.domain, i.user_name.lower()))
                elif not i.user_id:
                    return cls._from_thing('user_name', i.platform,
                                           i.user_name, i.domain)
                account = update()
                if not account:
                    raise

        # Return account after propagating avatar_url to participant
        avatar_url = account.avatar_url
        if avatar_url and avatar_url.startswith('https://pbs.twimg.com/'):
            avatar_url = 'https://nitter.net/pic/' + avatar_url[22:].replace(
                '/', '%2F')
        account.participant.update_avatar(check=False)
        return account
Example #22
0
def _handle_ses_notification(msg):
    # Doc: https://docs.aws.amazon.com/ses/latest/DeveloperGuide/notification-contents.html
    data = json.loads(json.loads(msg.body)['Message'])
    notif_type = data['notificationType']
    transient = False
    if notif_type == 'Bounce':
        bounce = data['bounce']
        report_id = bounce['feedbackId']
        recipients = bounce['bouncedRecipients']
        if bounce.get('bounceType') == 'Transient':
            transient = True
            bounce_subtype = bounce.get('bounceSubType')
            if bounce_subtype not in ('General', 'MailboxFull'):
                website.warning("unhandled bounce subtype: %r" %
                                bounce_subtype)
    elif notif_type == 'Complaint':
        complaint = data['complaint']
        report_id = complaint['feedbackId']
        recipients = complaint['complainedRecipients']
        complaint_type = complaint.get('complaintFeedbackType')
        if complaint.get('complaintSubType') == 'OnAccountSuppressionList':
            pass
        elif complaint_type is None:
            # This complaint is invalid, ignore it.
            logging.info(
                "Received an invalid email complaint without a Feedback-Type. ID: %s"
                % report_id)
            msg.delete()
            return
        elif complaint_type not in ('abuse', 'fraud'):
            # We'll figure out how to deal with that when it happens.
            raise ValueError(complaint_type)
    else:
        raise ValueError(notif_type)
    for recipient in recipients:
        address = recipient['emailAddress']
        if address[-1] == '>':
            address = address[:-1].rsplit('<', 1)[1]
        if notif_type == 'Bounce':
            # Check the reported delivery status
            # Spec: https://tools.ietf.org/html/rfc3464#section-2.3.3
            action = recipient.get('action')
            if action is None:
                # This isn't a standard bounce. It may be a misdirected automatic reply.
                continue
            elif action == 'failed':
                # This is the kind of DSN we're interested in.
                pass
            elif action == 'delivered':
                # The reporting MTA claims that the message has been successfully delivered.
                continue
            elif action in ('delayed', 'relayed', 'expanded'):
                # Ignore non-final DSNs.
                continue
            else:
                # This is a new or non-standard type of DSN, ignore it.
                continue
        # Check for recurrent "transient" errors
        if transient:
            ignore_after = utcnow() + timedelta(days=5)
            n_previous_bounces = website.db.one(
                """
                SELECT count(*)
                  FROM email_blacklist
                 WHERE lower(address) = lower(%s)
                   AND ts > (current_timestamp - interval '90 days')
                   AND reason = 'bounce'
            """, (address, ))
            if n_previous_bounces >= 2:
                ignore_after = utcnow() + timedelta(days=180)
        else:
            ignore_after = None
        # Add the address to our blacklist
        r = website.db.one(
            """
            INSERT INTO email_blacklist
                        (address, reason, ses_data, report_id, ignore_after)
                 VALUES (%s, %s, %s, %s, %s)
            ON CONFLICT (report_id, address) DO NOTHING
              RETURNING *
        """, (address, notif_type.lower(), json.dumps(data), report_id,
              ignore_after))
        if r is None:
            # Already done
            continue
        # Attempt to notify the user(s)
        bounce_message = get_bounce_message(r.reason, r.ses_data, r.details)
        participants = website.db.all(
            """
            SELECT p
              FROM emails e
              JOIN participants p ON p.id = e.participant
             WHERE lower(e.address) = lower(%s)
               AND (p.email IS NULL OR lower(p.email) = lower(e.address))
        """, (address, ))
        for p in participants:
            try:
                p.notify(
                    'email_blacklisted',
                    email=False,
                    web=True,
                    type='warning',
                    blacklisted_address=address,
                    reason=r.reason,
                    ignore_after=r.ignore_after,
                    bounce_message=bounce_message,
                )
            except DuplicateNotification:
                continue
    msg.delete()
Example #23
0
def record_payin_refund(
    db,
    payin_id,
    remote_id,
    amount,
    reason,
    description,
    status,
    error=None,
    ctime=None,
):
    """Record a charge refund.

    Args:
        payin_id (int): the ID of the refunded payin in our database
        remote_id (int): the ID of the refund in the payment processor's database
        amount (Money): the refund amount, must be less or equal to the payin amount
        reason (str): why this refund was initiated (`refund_reason` SQL type)
        description (str): details of the circumstances of this refund
        status (str): the current status of the refund (`refund_status` SQL type)
        error (str): error message, if the refund has failed
        ctime (datetime): when the refund was initiated

    Returns:
        Record: the row inserted in the `payin_refunds` table

    """
    refund = db.one(
        """
        INSERT INTO payin_refunds
               (payin, remote_id, amount, reason, description,
                status, error, ctime)
        VALUES (%(payin_id)s, %(remote_id)s, %(amount)s, %(reason)s, %(description)s,
                %(status)s, %(error)s, coalesce(%(ctime)s, current_timestamp))
   ON CONFLICT (payin, remote_id) DO UPDATE
           SET amount = excluded.amount
             , reason = excluded.reason
             , description = excluded.description
             , status = excluded.status
             , error = excluded.error
     RETURNING *
             , ( SELECT old.status
                   FROM payin_refunds old
                  WHERE old.payin = %(payin_id)s
                    AND old.remote_id = %(remote_id)s
               ) AS old_status
    """, locals())
    notify = (refund.status in ('pending', 'succeeded')
              and refund.status != refund.old_status and refund.ctime >
              (utcnow() - timedelta(hours=24)))
    if notify:
        payin = db.one("SELECT * FROM payins WHERE id = %s", (refund.payin, ))
        payer = db.Participant.from_id(payin.payer)
        payer.notify(
            'payin_refund_initiated',
            payin_amount=payin.amount,
            payin_ctime=payin.ctime,
            refund_amount=refund.amount,
            refund_reason=refund.reason,
        )
    return refund
 def check_connect_token(self, token):
     return (
         self.connect_token and
         constant_time_compare(self.connect_token, token) and
         self.connect_expires > utcnow()
     )