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()
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) + ')'
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)
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, ), ]
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.""")
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)
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')
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
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
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)
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')