def forwards(self, orm):
        field = EncryptedCharField()

        for account in orm['email.EmailAccount'].objects.all():
            account.username = field.to_python(account.username)
            account.password = field.to_python(account.password)
            account.save()
Esempio n. 2
0
class AwsCredential(models.Model):
    user_profile = models.OneToOneField('UserProfile')
    aws_user_id = models.CharField(max_length=200)
    aws_email = models.EmailField(blank=True, null=True)
    ssh_key = EncryptedTextField()
    aws_key_id = EncryptedCharField(max_length=200)
    aws_secret_key = EncryptedCharField(max_length=200)

    def __unicode__(self):
        return self.aws_user_id + ' (' + str(self.aws_email) + ')'
Esempio n. 3
0
    def get_db_prep_value(self, value, connection=None, prepared=False):
        if value is None:
            return None

        return EncryptedCharField.get_db_prep_value(self, value,
                                                    connection=connection,
                                                    prepared=prepared)
Esempio n. 4
0
    def get_db_prep_value(self, value, connection=None, prepared=False):
        if value is None:
            return None

        return EncryptedCharField.get_db_prep_value(self, value,
                                                    connection=connection,
                                                    prepared=prepared)
Esempio n. 5
0
class Migration(migrations.Migration):

    dependencies = [
        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
    ]

    operations = [
        migrations.CreateModel(
            name='Result',
            fields=[
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
                ('status', models.CharField(max_length=100, verbose_name='Status')),
                ('host', models.CharField(max_length=15, null=True, verbose_name='Host', blank=True)),
                ('crdate', models.DateTimeField(auto_now_add=True, verbose_name='Date created')),
                ('successful', models.BooleanField(verbose_name='Successful')),
            ],
            options={
            },
            bases=(models.Model,),
        ),
        migrations.CreateModel(
            name='Service',
            fields=[
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
                ('username', EncryptedCharField(max_length=100, verbose_name='Username')),
                ('password', EncryptedCharField(max_length=100, verbose_name='Password')),
                ('hostname', models.CharField(max_length=100, verbose_name='Hostname')),
                ('services', models.CharField(max_length=100, null=True, verbose_name='Services', blank=True)),
                ('crdate', models.DateTimeField(auto_now_add=True, verbose_name='Date created')),
                ('tstamp', models.DateTimeField(auto_now=True, verbose_name='Date edited')),
                ('update', models.DateTimeField(null=True, verbose_name='Date updated', blank=True)),
                ('enabled', models.BooleanField(default=True, verbose_name='Enabled')),
                ('waiting', models.BooleanField(default=True, verbose_name='Waiting')),
                ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
            ],
            options={
            },
            bases=(models.Model,),
        ),
        migrations.AddField(
            model_name='result',
            name='service',
            field=models.ForeignKey(to='dnsalloc.Service'),
            preserve_default=True,
        ),
    ]
Esempio n. 6
0
class UserProfile(models.Model):
    class Meta:
        app_label = 'main'

    def __unicode__(self):
        return "Encrypted email and HRSA ID for user %d" % self.user.id

    user = models.OneToOneField(User, unique=True, on_delete=models.CASCADE)
    encrypted_email = EncryptedEmailField()
    hrsa_id = EncryptedCharField()

    @staticmethod
    def find_user_profiles_by_plaintext_email(plaintext_email):
        """ this isn't scaleable but should be fine in the short term."""
        try:
            return [
                x for x in UserProfile.objects.all()
                if x.encrypted_email == plaintext_email
            ]
        except DjangoUnicodeDecodeError:
            raise ImproperlyConfigured(
                """Looks like the setting for encryption /decryption
                doesn't match one of the values in the database.""")
Esempio n. 7
0
class CBSCredential(BaseCredential):

    api_key = EncryptedCharField(max_length=128,
                                 blank=False,
                                 null=False,
                                 db_index=True)
    api_secret = EncryptedCharField(max_length=256,
                                    blank=False,
                                    null=False,
                                    db_index=True)

    def get_credential_abbrev(self):
        return 'CBS'

    def get_credential_to_display(self):
        return 'Coinbase'

    def get_login_link(self):
        return 'https://coinbase.com/signin'

    def is_coinbase_credential(self):
        return True

    def get_balance(self):
        """
        Return acount balance in satoshis

        We return False when there's an API fail
        This prevents the front-end from breaking while still passing the neccessary info back

        """
        BALANCE_URL = 'https://coinbase.com/api/v1/account/balance'
        r = get_cb_request(url=BALANCE_URL,
                           api_key=self.api_key,
                           api_secret=self.api_secret)

        # Log the API call
        APICall.objects.create(api_name=APICall.COINBASE_BALANCE,
                               url_hit=BALANCE_URL,
                               response_code=r.status_code,
                               post_params=None,
                               api_results=r.content,
                               merchant=self.merchant,
                               credential=self)

        status_code_is_valid = self.handle_status_code(r.status_code)

        if not status_code_is_valid:
            return False

        resp_json = json.loads(r.content)

        if 'currency' not in resp_json and resp_json['currency'] != 'BTC':
            self.mark_failure()
            return False

        satoshis = btc_to_satoshis(resp_json['amount'])

        # Record the balance results
        BaseBalance.objects.create(satoshis=satoshis, credential=self)

        return satoshis

    def list_recent_purchases_and_sales(self):
        # TODO: add DB logging?
        LIST_FIAT_URL = 'https://coinbase.com/api/v1/transfers'

        url_to_hit = LIST_FIAT_URL + '?limit=100'

        r = get_cb_request(
            url=url_to_hit,
            api_key=self.api_key,
            api_secret=self.api_secret,
        )

        # Log the API call
        APICall.objects.create(api_name=APICall.COINBASE_LIST_PURCHASE_SALE,
                               url_hit=url_to_hit,
                               response_code=r.status_code,
                               api_results=r.content,
                               merchant=self.merchant,
                               credential=self)

        self.handle_status_code(r.status_code)

        return json.loads(r.content)['transfers']

    def list_recent_btc_transactions(self):
        ''' Limits to 30, add pagination if you want more '''
        LIST_TX_URL = 'https://coinbase.com/api/v1/transactions'

        r = get_cb_request(
            url=LIST_TX_URL,
            api_key=self.api_key,
            api_secret=self.api_secret,
        )

        # Log the API call
        APICall.objects.create(api_name=APICall.COINBASE_LIST_BTC_TRANSACTIONS,
                               url_hit=LIST_TX_URL,
                               response_code=r.status_code,
                               post_params=None,
                               api_results=r.content,
                               merchant=self.merchant,
                               credential=self)

        self.handle_status_code(r.status_code)

        json_resp = json.loads(r.content)

        # Record the balance
        BaseBalance.objects.create(satoshis=btc_to_satoshis(
            json_resp['balance']['amount']),
                                   credential=self)

        # Return transactions
        return json_resp['transactions']

    def request_cashout(self, satoshis_to_sell):
        SELL_URL = 'https://coinbase.com/api/v1/sells'

        btc_to_sell = satoshis_to_btc(satoshis_to_sell)
        body_to_use = 'qty=%s' % btc_to_sell

        r = get_cb_request(
            url=SELL_URL,
            api_key=self.api_key,
            api_secret=self.api_secret,
            body=body_to_use,
        )

        # Log the API call
        APICall.objects.create(api_name=APICall.COINBASE_CASHOUT_BTC,
                               url_hit=SELL_URL,
                               response_code=r.status_code,
                               api_results=r.content,
                               post_params={'qty': btc_to_sell},
                               merchant=self.merchant,
                               credential=self)

        self.handle_status_code(r.status_code)

        resp_json = json.loads(r.content)

        success = resp_json['success']
        assert success is True, '%s: %s' % (success, resp_json.get('errors'))

        transfer = resp_json['transfer']

        status = transfer['status']
        assert status.lower() in ('pending', 'created'), status

        btc_obj = transfer['btc']
        assert btc_obj['currency'] == 'BTC', btc_obj

        satoshis = btc_to_satoshis(btc_obj['amount'])
        assert satoshis == satoshis_to_sell, btc_obj['amount']

        currency_to_recieve = transfer['total']['currency']

        fiat_fees_in_cents = 0
        for fee_key in transfer['fees']:
            fee = transfer['fees'][fee_key]
            fiat_fees_in_cents += int(fee['cents'])
            msg = '%s != %s' % (fee['currency_iso'], currency_to_recieve)
            assert fee['currency_iso'] == currency_to_recieve, msg
        fiat_fees = fiat_fees_in_cents / 100.0

        cbs_sell_btc = CBSSellBTC.objects.create(
            coinbase_code=transfer['code'])

        return CBSSellBTC.objects.create(
            credential=self,
            cbs_sell_btc=cbs_sell_btc,
            satoshis=satoshis,
            currency_code=currency_to_recieve,
            fees_in_fiat=fiat_fees,
            to_receive_in_fiat=float(transfer['total']['amount']),
        )

    def send_btc(self,
                 satoshis_to_send,
                 destination_btc_address,
                 destination_email_address=None,
                 notes=None):
        """
        Send satoshis to a destination address or email address.
        CB requires a fee for txns < .001 BTC, so this method will
        automatically include txn fees for those.

        Returns a tuple of the form (some or all may be none):
            btc_txn, sent_btc_obj, api_call, err_str
        """

        msg = "Can't have both a destination email and BTC address. %s | %s" % (
            destination_email_address, destination_btc_address)
        assert not (destination_email_address and destination_btc_address), msg

        msg = 'Must send to a destination email OR BTC address'
        assert destination_email_address or destination_btc_address, msg

        dest_addr_to_use = None

        if destination_btc_address:
            dest_addr_to_use = destination_btc_address
            send_btc_dict = {
                'destination_btc_address': destination_btc_address
            }

            msg = '%s is not a valid bitcoin address' % destination_btc_address
            assert is_valid_btc_address(destination_btc_address), msg

        if destination_email_address:
            dest_addr_to_use = destination_email_address
            send_btc_dict = {'destination_email': destination_email_address}

            msg = '%s is not a valid email address' % destination_email_address
            # FIXME: implement

        btc_to_send = satoshis_to_btc(satoshis_to_send)

        SEND_URL = 'https://coinbase.com/api/v1/transactions/send_money'

        body = 'transaction[to]=%s&transaction[amount]=%s' % (dest_addr_to_use,
                                                              btc_to_send)
        post_params = {'to': dest_addr_to_use, 'amount': btc_to_send}

        if satoshis_to_send <= btc_to_satoshis(
                .001) and not destination_email_address:
            # https://coinbase.com/api/doc/1.0/transactions/send_money.html
            # Coinbase pays transaction fees on payments greater than or equal to 0.001 BTC.
            # But for smaller amounts you have to add your own
            # For some reason, coinbase requires 2x fees of .2 mBTC vs (.1 mBTC)
            body += '&transaction[user_fee]=0.0002'
            post_params['user_fee'] = 0.0002

        if notes:
            # TODO: url encode this?
            body += '&transaction[notes]=' + notes
            post_params['notes'] = notes

        r = get_cb_request(url=SEND_URL,
                           api_key=self.api_key,
                           api_secret=self.api_secret,
                           body=body)

        # Log the API call
        api_call = APICall.objects.create(api_name=APICall.COINBASE_SEND_BTC,
                                          url_hit=SEND_URL,
                                          response_code=r.status_code,
                                          post_params=post_params,
                                          api_results=r.content,
                                          merchant=self.merchant,
                                          credential=self)

        self.handle_status_code(r.status_code)

        resp_json = json.loads(r.content)

        if resp_json.get('error') or resp_json.get('errors'):
            err_str = resp_json.get('error')
            # combine the two
            if resp_json.get('errors'):
                if err_str:
                    err_str += ' %s' % resp_json.get('errors')
                else:
                    err_str = resp_json.get('errors')

            # this assumes all error messages here are safe to display to the user
            return None, None, api_call, err_str

        transaction = resp_json['transaction']

        satoshis = -1 * btc_to_satoshis(transaction['amount']['amount'])

        txn_hash = transaction['hsh']

        # Record the Send
        send_btc_dict.update({
            'credential': self,
            'txn_hash': txn_hash,
            'satoshis': satoshis,
            'transaction_id': transaction['id'],
            'notes': notes,
        })
        sent_btc_obj = CBSSentBTC.objects.create(**send_btc_dict)

        if txn_hash:
            return BTCTransaction.objects.create(
                txn_hash=txn_hash, satoshis=satoshis,
                conf_num=0), sent_btc_obj, api_call, None
        else:
            # Coinbase seems finicky about transaction hashes
            return None, sent_btc_obj, api_call, None

    def get_new_receiving_address(self, set_as_merchant_address=False):
        """
        Generates a new receiving address
        """
        ADDRESS_URL = 'https://coinbase.com/api/v1/account/generate_receive_address'

        label = 'CoinSafe Address %s' % now().strftime("%Y-%m-%d %H:%M:%S")
        body = 'address[label]=%s' % label

        r = get_cb_request(
            url=ADDRESS_URL,
            api_key=self.api_key,
            api_secret=self.api_secret,
            body=body,
        )

        # Log the API call
        APICall.objects.create(api_name=APICall.COINBASE_NEW_ADDRESS,
                               url_hit=ADDRESS_URL,
                               response_code=r.status_code,
                               post_params={'label': label},
                               api_results=r.content,
                               merchant=self.merchant,
                               credential=self)

        self.handle_status_code(r.status_code)

        resp_json = json.loads(r.content)

        assert resp_json['success'] is True, resp_json

        address = resp_json['address']

        msg = '%s is not a valid bitcoin address' % address
        assert is_valid_btc_address(address), msg

        BaseAddressFromCredential.objects.create(credential=self,
                                                 b58_address=address,
                                                 retired_at=None)

        if set_as_merchant_address:
            self.merchant.set_destination_address(dest_address=address,
                                                  credential_used=self)

        return address

    def get_best_receiving_address(self, set_as_merchant_address=False):
        " Get a new receiving address "
        return self.get_new_receiving_address(
            set_as_merchant_address=set_as_merchant_address)
Esempio n. 8
0
class APNService(BaseService):
    """
    Represents an Apple Notification Service either for live
    or sandbox notifications.

    `private_key` is optional if both the certificate and key are provided in
    `certificate`.
    """
    certificate = models.TextField()
    private_key = models.TextField()
    passphrase = EncryptedCharField(
        null=True, blank=True, help_text='Passphrase for the private key',
        block_type='MODE_CBC')

    PORT = 2195
    fmt = '!cH32sH%ds'

    def _connect(self):
        """
        Establishes an encrypted SSL socket connection to the service.
        After connecting the socket can be written to or read from.
        """
        return super(APNService, self)._connect(self.certificate, self.private_key, self.passphrase)

    def push_notification_to_devices(self, notification, devices=None, chunk_size=100):
        """
        Sends the specific notification to devices.
        if `devices` is not supplied, all devices in the `APNService`'s device
        list will be sent the notification.
        """
        if devices is None:
            devices = self.device_set.filter(is_active=True)
        self._write_message(notification, devices, chunk_size)

    def _write_message(self, notification, devices, chunk_size):
        """
        Writes the message for the supplied devices to
        the APN Service SSL socket.
        """
        if not isinstance(notification, Notification):
            raise TypeError('notification should be an instance of ios_notifications.models.Notification')

        if not isinstance(chunk_size, int) or chunk_size < 1:
            raise ValueError('chunk_size must be an integer greater than zero.')

        payload = notification.payload

        # Split the devices into manageable chunks.
        # Chunk sizes being determined by the `chunk_size` arg.
        device_length = devices.count() if isinstance(devices, models.query.QuerySet) else len(devices)
        chunks = [devices[i:i + chunk_size] for i in xrange(0, device_length, chunk_size)]

        for index in xrange(len(chunks)):
            chunk = chunks[index]
            self._connect()

            for device in chunk:
                if not device.is_active:
                    continue
                try:
                    self.connection.send(self.pack_message(payload, device))
                except (OpenSSL.SSL.WantWriteError, socket.error) as e:
                    if isinstance(e, socket.error) and isinstance(e.args, tuple) and e.args[0] != errno.EPIPE:
                        raise e  # Unexpected exception, raise it.
                    self._disconnect()
                    i = chunk.index(device)
                    self.set_devices_last_notified_at(chunk[:i])
                    # Start again from the next device.
                    # We start from the next device since
                    # if the device no longer accepts push notifications from your app
                    # and you send one to it anyways, Apple immediately drops the connection to your APNS socket.
                    # http://stackoverflow.com/a/13332486/1025116
                    self._write_message(notification, chunk[i + 1:])

            self._disconnect()

            self.set_devices_last_notified_at(chunk)

        if notification.pk or notification.persist:
            notification.last_sent_at = dt_now()
            notification.save()

    def set_devices_last_notified_at(self, devices):
        # Rather than do a save on every object,
        # fetch another queryset and use it to update
        # the devices in a single query.
        # Since the devices argument could be a sliced queryset
        # we can't rely on devices.update() even if devices is
        # a queryset object.
        Device.objects.filter(pk__in=[d.pk for d in devices]).update(last_notified_at=dt_now())

    def pack_message(self, payload, device):
        """
        Converts a notification payload into binary form.
        """
        if len(payload) > 256:
            raise NotificationPayloadSizeExceeded
        if not isinstance(device, Device):
            raise TypeError('device must be an instance of ios_notifications.models.Device')

        msg = struct.pack(self.fmt % len(payload), chr(0), 32, unhexlify(device.token), len(payload), payload)
        return msg

    def __unicode__(self):
        return self.name

    class Meta:
        unique_together = ('name', 'hostname')
Esempio n. 9
0
class BCICredential(BaseCredential):

    username = EncryptedCharField(max_length=64,
                                  blank=False,
                                  null=False,
                                  db_index=True)
    main_password = EncryptedCharField(max_length=128,
                                       blank=False,
                                       null=False,
                                       db_index=True)
    second_password = EncryptedCharField(max_length=128,
                                         blank=True,
                                         null=True,
                                         db_index=True)

    def get_credential_abbrev(self):
        return 'BCI'

    def get_credential_to_display(self):
        return 'blockchain.info'

    def get_login_link(self):
        return 'https://blockchain.info/wallet/%s' % self.username

    def is_blockchain_credential(self):
        return True

    def get_balance(self):
        """
        Return acount balance in satoshis
        """

        BASE_URL = 'https://blockchain.info/merchant/%s/balance?password=%s'
        BALANCE_URL = BASE_URL % (self.username,
                                  urllib.quote(self.main_password))

        r = requests.get(BALANCE_URL)

        # Log the API call
        APICall.objects.create(api_name=APICall.BLOCKCHAIN_WALLET_BALANCE,
                               url_hit=BALANCE_URL,
                               response_code=r.status_code,
                               post_params=None,
                               api_results=r.content,
                               merchant=self.merchant,
                               credential=self)

        status_code_is_valid = self.handle_status_code(r.status_code)

        if not status_code_is_valid:
            return False

        resp_json = json.loads(r.content)

        if 'error' in resp_json:
            self.mark_failure()
            print 'Blockchain Error: %s' % resp_json['error']
            return False

        satoshis = int(resp_json['balance'])

        # Record the balance results
        BaseBalance.objects.create(satoshis=satoshis, credential=self)

        return satoshis

    def request_cashout(self, satoshis_to_sell, limit_order_price=None):
        raise Exception('Not Possible')

    def send_btc(self, satoshis_to_send, destination_btc_address):
        """
        Returns a tuple of the form (some or all may be none):
            btc_txn, sent_btc_obj, api_call, err_str
        """

        msg = '%s is not a valid bitcoin address' % destination_btc_address
        assert is_valid_btc_address(destination_btc_address), msg

        BASE_URL = 'https://blockchain.info/merchant/%s/payment?password=%s&to=%s&amount=%s&shared=false'
        SEND_URL = BASE_URL % (self.username, urllib.quote(
            self.main_password), destination_btc_address, satoshis_to_send)

        if self.second_password:
            SEND_URL += '&second_password=%s' % urllib.quote(
                self.second_password)

        r = requests.get(SEND_URL)

        # Log the API call
        api_call = APICall.objects.create(
            api_name=APICall.BLOCKCHAIN_WALLET_SEND_BTC,
            url_hit=SEND_URL,
            response_code=r.status_code,
            post_params=None,
            api_results=r.content,
            merchant=self.merchant,
            credential=self)

        self.handle_status_code(r.status_code)

        resp_json = json.loads(r.content)

        if 'error' in resp_json:
            # TODO: this assumes all error messages here are safe to display to the user
            return None, None, api_call, resp_json['error']

        if 'tx_hash' not in resp_json:
            # TODO: this assumes all error messages here are safe to display to the user
            return None, None, api_call, 'No Transaction Hash Received from Blockchain.Info'

        tx_hash = resp_json['tx_hash']

        # Record the Send
        sent_btc_obj = BCISentBTC.objects.create(
            credential=self,
            satoshis=satoshis_to_send,
            destination_btc_address=destination_btc_address,
            txn_hash=tx_hash,
        )

        return BTCTransaction.objects.create(
            txn_hash=tx_hash, satoshis=satoshis_to_send,
            conf_num=0), sent_btc_obj, api_call, None

    def get_new_receiving_address(self, set_as_merchant_address=False):
        """
        Generates a new receiving address
        """
        label = urllib.quote('CoinSafe Address %s' %
                             now().strftime("%Y-%m-%d %H.%M.%S"))

        BASE_URL = 'https://blockchain.info/merchant/%s/new_address?password=%s&label=%s'
        ADDRESS_URL = BASE_URL % (self.username,
                                  urllib.quote(self.main_password), label)

        if self.second_password:
            ADDRESS_URL += '&second_password=%s' % urllib.quote(
                self.second_password)

        r = requests.get(url=ADDRESS_URL)

        # Log the API call
        api_call = APICall.objects.create(
            api_name=APICall.BLOCKCHAIN_WALLET_NEW_ADDRESS,
            url_hit=ADDRESS_URL,
            response_code=r.status_code,
            post_params=None,
            api_results=r.content,
            merchant=self.merchant,
            credential=self)

        self.handle_status_code(r.status_code)

        resp_json = json.loads(r.content)

        address = resp_json['address']

        msg = '%s is not a valid bitcoin address' % address
        assert is_valid_btc_address(address), msg

        BaseAddressFromCredential.objects.create(credential=self,
                                                 b58_address=address,
                                                 retired_at=None)

        if set_as_merchant_address:
            self.merchant.set_destination_address(dest_address=address,
                                                  credential_used=self)

        return address

    def get_best_receiving_address(self, set_as_merchant_address=False):
        " Get a new receiving address "
        return self.get_new_receiving_address(
            set_as_merchant_address=set_as_merchant_address)

    @classmethod
    def create_wallet_credential(cls,
                                 user_password,
                                 merchant,
                                 user_email=None):
        """
        Create a wallet object and return its (newly created) bitcoin address
        """
        BASE_URL = 'https://blockchain.info/api/v2/create_wallet'
        label = 'CoinSafe Address %s' % now().strftime("%Y%m%d %H%M%S")

        post_params = {
            'password': user_password,
            'api_code': BCI_SECRET_KEY,
            'label': label,
        }

        if user_email:
            post_params['email'] = user_email

        r = requests.post(BASE_URL, data=post_params)

        # Log the API call
        APICall.objects.create(
            api_name=APICall.BLOCKCHAIN_CREATE_WALLET,
            url_hit=BASE_URL,
            response_code=r.status_code,
            post_params=post_params,
            api_results=r.content,
            merchant=merchant,
        )

        resp_json = json.loads(r.content)

        guid = resp_json['guid']
        btc_address = resp_json['address']

        msg = '%s is not a valid bitcoin address' % btc_address
        assert is_valid_btc_address(btc_address), msg

        cls.objects.create(merchant=merchant,
                           username=guid,
                           main_password=user_password,
                           second_password=None)

        return btc_address
Esempio n. 10
0
class Datasource(models.Model):
    """Defines a specific database and connection parameters owned by a specific user to connect with a
    particular db.
    """
    user = models.ForeignKey(User)
    name = models.CharField(max_length=100)
    dbtype = models.CharField(max_length=100,
                              choices=(('MYSQL', 'MySQL'), ),
                              default='mysql')
    dbname = models.CharField(max_length=100)
    dbusername = models.CharField(max_length=100)
    dbpassword = EncryptedCharField(max_length=100)
    dbhost = models.CharField(
        max_length=100)  # Must either be an IP address or URL
    # Pickled dict of db table names and sqlalchemy Table objects
    pickled_tables = models.TextField(null=True)
    pickled_measures = models.TextField(null=True)
    pickled_dimensions = models.TextField(null=True)
    time_introspected = models.DateTimeField(null=True)

    @property
    def engine(self):
        """ Returns the SQLAlchemy engine for the datasource. Creates one if there isn't one already.
        """

        # Every instance gets a different engine. Can be optimized further. See the NOTE in TableBases.
        try:
            return self._engine
        except AttributeError:
            conn_param = {
                'username': self.dbusername,
                'password': self.dbpassword,
                'host': self.dbhost,
                'dbname': self.dbname,
            }
            conn_template = "%(dialect_driver)s://%(username)s:%(password)s@%(host)s/%(dbname)s"

            if self.dbtype == 'MYSQL':
                conn_param['dialect_driver'] = 'mysql+mysqldb'
            else:
                raise UnsupportedDatabaseError(
                    "This database is not supported")
            conn_string = conn_template % conn_param
            self._engine = sqlalchemy.create_engine(conn_string,
                                                    echo=settings.ECHO)
        return self._engine

    def _pickle_tables(self):
        """Reflects the database pointed to by the datasource, pickles all the Table objects returned
        and sets the ``pickled_tables`` field.

        See Also: tables
        """
        metadata = sqlalchemy.MetaData()
        metadata.reflect(bind=self.engine)
        pickled_tables = pickle.dumps(dict(metadata.tables.items()))
        # NOTE: Need to base64 encode it since django tries to convert to Unicode covert stuff by
        # default which causes issues which storing and retrieving from database.
        # See the implementation of ``django.sessions.base`` for an example of base64 encoding a pickled
        # object before saving in db.
        self.pickled_tables = base64.b64encode(pickled_tables)

    def _pickle_measures_and_dimensions(self):
        """Reflects the database columns and sets the ``pickled_measures`` and ``pickled_dimensions`` fields.

        The logic is rather simple minded ... if a column is an integer or numeric (float or double), then
        the column is considered a measure. If it is a string type, it is considered a dimension. Note that
        we conveniently ignore date and time columns. Oh well.

        See Also: _pickle_tables()
        """
        measures, dimensions = defaultdict(list), defaultdict(list)
        for table_name, table in self.tables.items():
            for column in table.columns:
                if isinstance(column.type,
                              (sqlalchemy.types.Integer, sqlalchemy.Numeric)):
                    measures[table_name].append(column.name)
                elif isinstance(column.type, sqlalchemy.types.String):
                    dimensions[table_name].append(column.name)
        # NOTE: see note in _pickle_tables() for an explanation of why we need to base64 encode.
        self.pickled_measures = base64.b64encode(
            pickle.dumps(OrderedDict(measures)))
        self.pickled_dimensions = base64.b64encode(
            pickle.dumps(OrderedDict(dimensions)))

    def _pickle_all(self):
        """Introspects the db and pickles the tables and the measures and dimensions.
        """
        self._pickle_tables()
        self._pickle_measures_and_dimensions()

    @property
    def tables(self):
        """Reads the ``pickled_tables`` field and unpickles the data and returns a dict of table names
        and corresponding sqlalchemy.schema.Table objects. If the ``pickled_tables`` field is empty,
        introspects db first and then returns the tables.

        See Also: _pickle_tables()
        """
        try:
            return self._tables
        except AttributeError:
            if self.pickled_tables is None:
                self._pickle_tables()
            # NOTE: see note in _pickle_tables() for an explanation of why pickled fields are
            # base64 encoded.
            self._tables = pickle.loads(base64.b64decode(self.pickled_tables))
        return self._tables

    @property
    def measures(self):
        """Reads the ``pickled_measures`` field and unpickles the data and returns a dict of table names and
        and list of column names that are measures. If the ``pickled_measures`` field is empty,
        introspects db first and then returns the measures.

        See Also: _pickle_measures_and_dimensions()
        """
        try:
            return self._measures
        except AttributeError:
            if self.pickled_measures is None:
                self._picke_measures_and_dimensions()
            self._measures = pickle.loads(
                base64.b64decode(self.pickled_measures))
        return self._measures

    @property
    def dimensions(self):
        """Reads the ``pickled_dimensions`` field and unpickles the data and returns a dict of table names and
        and list of column names that are dimensions. If the ``pickled_dimensions`` field is empty,
        introspects db first and then returns the dimensions.

        See Also: _pickle_measures_and_dimensions()
        """
        try:
            return self._dimensions
        except AttributeError:
            if self.pickled_dimensions is None:
                self._picke_measures_and_dimensions()
            self._dimensions = pickle.loads(
                base64.b64decode(self.pickled_dimensions))
        return self._dimensions

    @property
    def bases(self):
        """Returns a TableBases collection. Lazily creates an loads a (declarative) Base object mapped
         to the corresponding table name.

        See Also: TableBases class.
        """
        try:
            return self._bases
        except AttributeError:
            self._bases = TableBases(self)
        return self._bases

    @bases.setter
    def bases(self, value):
        raise AttributeError("Cannot set bases")

    @bases.deleter
    def bases(self):
        raise AttributeError("Cannot delete bases")

    def __unicode__(self):
        return self.name

    def clean(self):
        """Tries to connect to the database based on the supplied parameters.

        Raises ValidationError if it can't connect to the database.

        NOTE: This method is called automatically by Django during the validation phase. In effect,
        if the supplied parameters are incorrect, a new datasource record will not be created.
        """
        try:
            self.engine.connect()
        except (sqlalchemy.exc.OperationalError, UnsupportedDatabaseError):
            raise ValidationError(
                "Something wrong with the parameters. Can't connect to the DB."
            )

    @property
    def session(self):
        """Returns a session (``sqlalchemy.orm.session``) corresponding to the datasource.
        """
        # NOTE: Each instance gets its own session. Can be optimized further. See note in
        # TableBases.__getitem__
        try:
            return self._session
        except AttributeError:
            Session = orm.sessionmaker()
            Session.configure(bind=self.engine)
            self._session = Session()
            return self._session
Esempio n. 11
0
class BTSCredential(BaseCredential):

    customer_id = EncryptedCharField(max_length=32,
                                     blank=False,
                                     null=False,
                                     db_index=True)
    api_key = EncryptedCharField(max_length=64,
                                 blank=False,
                                 null=False,
                                 db_index=True)
    api_secret = EncryptedCharField(max_length=128,
                                    blank=False,
                                    null=False,
                                    db_index=True)

    def get_credential_abbrev(self):
        return 'BTS'

    def get_credential_to_display(self):
        return 'BitStamp'

    def get_login_link(self):
        return 'https://www.bitstamp.net/account/login/'

    def is_bitstamp_credential(self):
        return True

    def get_trading_obj(self):
        return Trading(username=self.customer_id,
                       key=self.api_key,
                       secret=self.api_secret)

    def get_balance(self):
        """
        Return acount balance in satoshis
        """
        BALANCE_URL = 'https://www.bitstamp.net/api/balance/'
        trading_obj = self.get_trading_obj()

        try:
            balance_dict = trading_obj.account_balance()
            # Log the API call
            APICall.objects.create(
                api_name=APICall.BITSTAMP_BALANCE,
                url_hit=BALANCE_URL,
                response_code=200,
                post_params=None,  # not accurate
                api_results=balance_dict,
                merchant=self.merchant,
                credential=self)

            self.mark_success()

        except Exception as e:
            # Log the API call
            APICall.objects.create(
                api_name=APICall.BITSTAMP_BALANCE,
                url_hit=BALANCE_URL,
                response_code=0,  # this is not accurate
                post_params=None,  # not accurate
                api_results=str(e),
                merchant=self.merchant,
                credential=self)

            self.mark_failure()
            print 'Error was: %s' % e
            return False

        satoshis = btc_to_satoshis(balance_dict['btc_available'])

        # Record the balance results
        BaseBalance.objects.create(satoshis=satoshis, credential=self)

        return satoshis

    def list_recent_transactions(self):
        '''
        Only does most recent transactions. Add pagination if you want more.

        Both BTC and USD (I think)
        '''
        LIST_TX_URL = 'https://www.bitstamp.net/api/user_transactions/'
        trading_obj = self.get_trading_obj()

        try:
            txn_list = trading_obj.user_transactions()
            # Log the API call
            APICall.objects.create(
                api_name=APICall.BITSTAMP_LIST_TRANSACTIONS,
                url_hit=LIST_TX_URL,
                response_code=200,
                post_params=None,  # not accurate
                api_results=str(txn_list),
                merchant=self.merchant,
                credential=self)

            self.mark_success()

        except Exception as e:
            # Log the API call
            APICall.objects.create(
                api_name=APICall.BITSTAMP_LIST_TRANSACTIONS,
                url_hit=LIST_TX_URL,
                response_code=0,  # not accurate
                post_params=None,  # not accurate
                api_results=str(e),
                merchant=self.merchant,
                credential=self)

            self.mark_failure()
            print 'Error was: %s' % e

        return txn_list

    def request_cashout(self, satoshis_to_sell, limit_order_price=None):
        raise Exception('Not Implemented Yet')

    def send_btc(self, satoshis_to_send, destination_btc_address):
        """
        Returns a tuple of the form (some or all may be none):
            btc_txn, sent_btc_obj, api_call, err_str
        """

        msg = '%s is not a valid bitcoin address' % destination_btc_address
        assert is_valid_btc_address(destination_btc_address), msg

        btc_to_send = satoshis_to_btc(satoshis_to_send)
        SEND_URL = 'https://www.bitstamp.net/api/bitcoin_withdrawal/'

        trading_obj = self.get_trading_obj()

        try:
            post_params = {
                'amount': btc_to_send,
                'address': destination_btc_address
            }
            withdrawal_info = trading_obj.bitcoin_withdrawal(**post_params)

            withdrawal_id = withdrawal_info['id']
            msg = "%s is not an int, it's a %s" % (withdrawal_id,
                                                   type(withdrawal_id))
            assert type(withdrawal_id) is int, msg

            # Log the API call
            api_call = APICall.objects.create(
                api_name=APICall.BITSTAMP_SEND_BTC,
                url_hit=SEND_URL,
                response_code=200,
                post_params=post_params,
                api_results=str(withdrawal_info),
                merchant=self.merchant,
                credential=self)

            self.mark_success()

        except Exception as e:
            # Log the API Call
            api_call = APICall.objects.create(
                api_name=APICall.BITSTAMP_SEND_BTC,
                url_hit=SEND_URL,
                response_code=0,  # not accurate
                post_params=post_params,
                api_results=str(e),
                merchant=self.merchant,
                credential=self)

            self.mark_failure()
            print 'Error was: %s' % e
            # TODO: this assumes all error messages here are safe to display to the user
            return None, None, api_call, str(e)

        sent_btc_obj = BTSSentBTC.objects.create(
            credential=self,
            satoshis=satoshis_to_send,
            destination_btc_address=destination_btc_address,
            withdrawal_id=withdrawal_id,
            status='0',
        )

        # This API doesn't return a TX hash on sending bitcoin :(
        return None, sent_btc_obj, api_call, None

    def get_receiving_address(self, set_as_merchant_address=False):
        """
        Gets current receiving address.
        There is no way to generate a new one via API (can be done manually)
        """
        ADDRESS_URL = 'https://www.bitstamp.net/api/bitcoin_deposit_address/'
        trading_obj = self.get_trading_obj()

        try:
            address = trading_obj.bitcoin_deposit_address()

            msg = '%s is not a valid bitcoin address' % address
            assert is_valid_btc_address(address), msg

            # Log the API call
            APICall.objects.create(
                api_name=APICall.BITSTAMP_BTC_ADDRESS,
                url_hit=ADDRESS_URL,
                response_code=200,
                post_params=None,  # not accurate
                api_results=address,
                merchant=self.merchant,
                credential=self)

            BaseAddressFromCredential.objects.create(credential=self,
                                                     b58_address=address,
                                                     retired_at=None)

            self.mark_success()

            if set_as_merchant_address:
                self.merchant.set_destination_address(dest_address=address,
                                                      credential_used=self)

            return address

        except Exception as e:
            # Log the API call
            APICall.objects.create(
                api_name=APICall.BITSTAMP_BTC_ADDRESS,
                url_hit=ADDRESS_URL,
                response_code=0,  # this is not accurate
                post_params=None,  # not accurate
                api_results=str(e),
                merchant=self.merchant,
                credential=self)

            self.mark_failure()

            print 'Error was: %s' % e

            return None

    def get_best_receiving_address(self, set_as_merchant_address=False):
        " Get existing receiving address (no way to get a new one with BTS) "
        return self.get_receiving_address(
            set_as_merchant_address=set_as_merchant_address)
Esempio n. 12
0
class APNService(BaseService):
    """
    Represents an Apple Notification Service either for live
    or sandbox notifications.

    `private_key` is optional if both the certificate and key are provided in
    `certificate`.
    """
    certificate = models.TextField()
    private_key = models.TextField()
    passphrase = EncryptedCharField(null=True,
                                    blank=True,
                                    help_text='Passphrase for the private key')

    PORT = 2195
    fmt = '!cH32sH%ds'

    def connect(self):
        """
        Establishes an encrypted SSL socket connection to the service.
        After connecting the socket can be written to or read from.
        """
        return super(APNService,
                     self).connect(self.certificate, self.private_key,
                                   self.passphrase)

    def push_notification_to_devices(self, notification, devices=None):
        """
        Sends the specific notification to devices.
        if `devices` is not supplied, all devices in the `APNService`'s device
        list will be sent the notification.
        """
        if devices is None:
            devices = self.device_set.filter(is_active=True)
        if self.connect():
            self._write_message(notification, devices)
            self.disconnect()

    def _write_message(self, notification, devices):
        """
        Writes the message for the supplied devices to
        the APN Service SSL socket.
        """
        if not isinstance(notification, Notification):
            raise TypeError(
                'notification should be an instance of ios_notifications.models.Notification'
            )

        if self.connection is None:
            if not self.connect():
                return

        payload = self.get_payload(notification)

        for device in devices:
            try:
                self.connection.send(self.pack_message(payload, device))
            except OpenSSL.SSL.WantWriteError:
                self.disconnect()
                i = devices.index(device)
                if isinstance(devices, models.query.QuerySet):
                    devices.update(last_notified_at=datetime.datetime.now())
                devices[:i].update(last_notified_at=datetime.datetime.now())
                return self._write_message(
                    notification, devices[i:]) if self.connect() else None
        if isinstance(devices, models.query.QuerySet):
            devices.update(last_notified_at=datetime.datetime.now())
        else:
            for device in devices:
                device.last_notified_at = datetime.datetime.now()
                device.save()
        notification.last_sent_at = datetime.datetime.now()
        notification.save()

    def get_payload(self, notification):
        aps = {'alert': notification.message}
        if notification.badge is not None:
            aps['badge'] = notification.badge
        if notification.sound is not None:
            aps['sound'] = notification.sound

        message = {'aps': aps}
        payload = json.dumps(message, separators=(',', ':'))

        if len(payload) > 256:
            raise NotificationPayloadSizeExceeded

        return payload

    def pack_message(self, payload, device):
        """
        Converts a notification payload into binary form.
        """
        if len(payload) > 256:
            raise NotificationPayloadSizeExceeded
        if not isinstance(device, Device):
            raise TypeError(
                'device must be an instance of ios_notifications.models.Device'
            )

        msg = struct.pack(self.fmt % len(payload), chr(0), 32,
                          unhexlify(device.token), len(payload), payload)
        return msg

    def __unicode__(self):
        return u'APNService %s' % self.name

    class Meta:
        unique_together = ('name', 'hostname')