def get(self): """ Return the salt and iteration count (for PBKDF2) for this user. Parameters required from the client: * username [text] * check [text] - 6 hexadecimal digits Returns: * salt [text] - hexadecimal digits * iterations [integer] """ username = request.args.get('username').encode('utf8') bcheck = request.args.get('check').encode('ascii') if not username or len(bcheck) != 6: return current_app.encode_error(Errors.MissingArguments) user = current_app.session.query(db.User).filter( db.User.username == username, db.User.user_check == bcheck).one_or_none() if user is None: return current_app.encode_error(Errors.UserNotFound) result = {'salt': user.salt, 'iterations': user.itercount} return current_app.encode_success(result)
def post(self): """ Return the balance for a given wallet. The bitcoin balance is only available when the wallet has a server-controlled cosigner. Parameters required from the client: * id [text] - wallet ID """ wallet_id = g.payload.get('id', '').encode('ascii') if not wallet_id: return current_app.encode_error(Errors.MissingArguments) # Get the cosigner for this wallet for this user. record = current_app.session.query(db.CosignerWallet).filter( db.CosignerWallet.user_id == current_user.id, db.CosignerWallet.wallet_id == wallet_id).one_or_none() if record is None: return current_app.encode_error(Errors.CosignerNotFound) # Send the request to the cosigning server. logging.info('still here') resp = current_app.cosigner('/balance', wallet=record.wallet) logging.info('result %s', resp) if 'balance' in resp: data = {'btc': resp['balance']} result = current_app.encode_success(data) else: logging.error(resp) result = current_app.encode_error(Errors.CosignerError) return result
def put(self): # XXX not implemented in the client yet. """ Update an existing blob for this user. Parameters required from the client: * id [text] - blob UUID * blob [text] - blob to replace the one previously stored Returns: the new blob """ blob = g.payload.get('blob', '').encode('ascii') blob_id = g.payload.get('id', '').encode('ascii') if not blob or not blob_id: # No blob specified. return current_app.encode_error(Errors.MissingArguments) elif len(blob) > MAX_BLOBLEN: # Blob size is too big. return current_app.encode_error(Errors.BlobTooLong) # Update the blob that belongs to this user only if this new one # is greater (in size) than the current one. record = current_app.session.query(db.WalletBlob).filter( db.WalletBlob.user_id == current_user.id, db.WalletBlob.updates_left > 0, db.func.char_length(db.WalletBlob.blob) < len(blob)).update({ 'updates_left': db.WalletBlob.updates_left - 1, 'blob': blob }) result = format_blob(record).next() return current_app.encode_success(result)
def put(self): # XXX not implemented in the client yet. """ Update an existing blob for this user. Parameters required from the client: * id [text] - blob UUID * blob [text] - blob to replace the one previously stored Returns: the new blob """ blob = g.payload.get('blob', '').encode('ascii') blob_id = g.payload.get('id', '').encode('ascii') if not blob or not blob_id: # No blob specified. return current_app.encode_error(Errors.MissingArguments) elif len(blob) > MAX_BLOBLEN: # Blob size is too big. return current_app.encode_error(Errors.BlobTooLong) # Update the blob that belongs to this user only if this new one # is greater (in size) than the current one. record = current_app.session.query(db.WalletBlob).filter( db.WalletBlob.user_id == current_user.id, db.WalletBlob.updates_left > 0, db.func.char_length(db.WalletBlob.blob) < len(blob)).update({ 'updates_left': db.WalletBlob.updates_left - 1, 'blob': blob}) result = format_blob(record).next() return current_app.encode_success(result)
def post(self): """ Create new address(es). This only available when the wallet has a server-controlled cosigner. Parameters required from the client: * id [text] - wallet ID Optional parameters: * num [number] - number of addresses to obtain (max: 100, default: 1) """ num = int(g.payload.get('num', 1)) wallet_id = g.payload.get('id', '').encode('ascii') if not wallet_id: return current_app.encode_error(Errors.MissingArguments) if num <= 0 or num > MAX_NEWADDRESS: return current_app.encode_error(Errors.InvalidAddressCount) # Get the cosigner for this wallet for this user. record = current_app.session.query(db.CosignerWallet).filter( db.CosignerWallet.user_id == current_user.id, db.CosignerWallet.wallet_id == wallet_id).one_or_none() if record is None: return current_app.encode_error(Errors.CosignerNotFound) # Send the request to the cosigning server. resp = current_app.cosigner('/address/new', num=num, wallet=record.wallet) if 'address' in resp: keys = ['address', 'path', 'createdOn'] if isinstance(resp['address'], dict): # Single address derived. keys.append('walletId') data = {key: resp['address'][key] for key in keys} else: # Multiple addresses. data = { 'walletId': resp['address'][0]['walletId'], 'result': None } data['result'] = [{key: entry[key] for key in keys} for entry in resp['address']] result = current_app.encode_success(data) else: logging.error(resp) result = current_app.encode_error(Errors.CosignerError) return result
def post(self): """ Create new address(es). This only available when the wallet has a server-controlled cosigner. Parameters required from the client: * id [text] - wallet ID Optional parameters: * num [number] - number of addresses to obtain (max: 100, default: 1) """ num = int(g.payload.get('num', 1)) wallet_id = g.payload.get('id', '').encode('ascii') if not wallet_id: return current_app.encode_error(Errors.MissingArguments) if num <= 0 or num > MAX_NEWADDRESS: return current_app.encode_error(Errors.InvalidAddressCount) # Get the cosigner for this wallet for this user. record = current_app.session.query(db.CosignerWallet).filter( db.CosignerWallet.user_id == current_user.id, db.CosignerWallet.wallet_id == wallet_id).one_or_none() if record is None: return current_app.encode_error(Errors.CosignerNotFound) # Send the request to the cosigning server. resp = current_app.cosigner( '/address/new', num=num, wallet=record.wallet) if 'address' in resp: keys = ['address', 'path', 'createdOn'] if isinstance(resp['address'], dict): # Single address derived. keys.append('walletId') data = {key: resp['address'][key] for key in keys} else: # Multiple addresses. data = { 'walletId': resp['address'][0]['walletId'], 'result': None } data['result'] = [{key: entry[key] for key in keys} for entry in resp['address']] result = current_app.encode_success(data) else: logging.error(resp) result = current_app.encode_error(Errors.CosignerError) return result
def post(self): # XXX blob update not implemented yet. """ Store or update a blob for this user. Parameters required from the client: * id [text] - an UUID (assumed to be a wallet ID) * blob [text] - blob to be stored Optional parameters: * maxchanges [integer] - maximum number of changes accepted for this blob, no more than 32 Returns: * id [text] - id for the blob stored * blob [text] - the blob stored * created_at [integer] - creation time as a unix timestamp """ maxchanges = min(int(g.payload.get('maxchanges', '32')), 32) blob = g.payload.get('blob', '').encode('ascii') # Blob ID is used internally as a reference to the Wallet record # stored in the MongoDB by bitcore-wallet-service. # The default client implementation passes that UUID as this id, # but it is not enforced. blob_id = g.payload.get('id', '').encode('ascii') if not blob or not blob_id or len(blob_id) != 36: # No blob specified. return current_app.encode_error(Errors.MissingArguments) elif len(blob) > MAX_BLOBLEN: # Blob size is too big. return current_app.encode_error(Errors.BlobTooLong) blob_count = current_app.session.query(db.WalletBlob).filter( db.WalletBlob.user_id == current_user.id).count() if blob_count >= MAX_BLOBCOUNT: # This user has stored too many blobs already. return current_app.encode_error(Errors.TooManyBlobs) record = db.WalletBlob(id=blob_id, user_id=current_user.id, updates_left=maxchanges, blob=blob) current_app.session.add(record) current_app.session.commit() result = format_blob(record).next() return current_app.encode_success(result)
def jws_preprocessor(request, status=400): """Deserialize the JWS received in the request.""" url = request.base_url data = request.data.encode('utf8') try: header, payload = bitjws.validate_deserialize(data, requrl=url) except Exception: logging.exception("Failed to validate and deserialize message") return current_app.encode_error(Errors.InvalidMessage, status) if header is None or payload is None: logging.error("Signature validation failed") return current_app.encode_error(Errors.InvalidSignature, status) return {'header': header, 'data': payload}
def signup_insert(records): session = current_app.session session.add_all(records) try: session.commit() return current_app.encode_success() except Exception as err: logging.exception("Failed to commit user records") session.rollback() if (isinstance(err, db.IntegrityError) and 'username' in err.orig.message): # Username already in use resp = current_app.encode_error(Errors.InvalidUsername) else: resp = current_app.encode_error(Errors.GenericError) return resp finally: session.close()
def authenticate(request): """ Authenticate user based on the JWS message received. If it succeeds, then g.payload will contain the decoded JWS payload. If it fails, then g.auth_err contains a Response encoded in JWS signed by this server. """ resp = jws_preprocessor(request, 401) if isinstance(resp, Response): # Validation failed. g.auth_err = resp return None # Store the decoded payload so it can be used for processing the request. g.payload = resp['data'] publickey = resp['header']['kid'].encode('ascii') userkey = current_app.session.query(db.UserKey).filter( db.UserKey.key == publickey).one_or_none() if userkey is None: # No user found. logging.error("No user found for key {}".format(publickey)) g.auth_err = current_app.encode_error(Errors.UserNotFound, 401) return None # Check last nonce used. nonce = int(resp['data'].get('iat', 0)) if nonce <= userkey.last_nonce: logging.error("Nonce {} is not greater than the last one {}".format( nonce, userkey.last_nonce)) g.auth_err = current_app.encode_error(Errors.InvalidNonce, 401) return None # Update last nonce. userkey.last_nonce = nonce current_app.session.add(userkey) current_app.session.commit() return User(userkey.user.id, userkey.user.username, publickey)
def post(self): """ Add a cosigner controlled by this server to a given wallet. Parameters required from the client: * secret [text] - secret required to join the wallet * id [text] - wallet ID for this cosigner to join """ join_secret = g.payload.get('secret', '').encode('ascii') wallet_id = g.payload.get('id', '').encode('ascii') if not wallet_id or not join_secret: return current_app.encode_error(Errors.MissingArguments) # Check that the wallet belongs to this user. count = current_app.session.query(db.WalletBlob).filter( db.WalletBlob.user_id == current_user.id, db.WalletBlob.id == wallet_id).count() if not count: return current_app.encode_error(Errors.WalletNotFound) # Send the join request to the cosigning server. resp = current_app.cosigner( '/join', secret=join_secret, walletId=wallet_id) if resp is None: # Cosigner server is not available. return current_app.encode_error(Errors.CosigningDisabled) if 'wallet' not in resp: logging.info(resp) err = ErrorCode(COSIGNER_ERR, resp['error']) return current_app.encode_error(err) # Store the cosigner wallet. cowallet = db.CosignerWallet( wallet_id=wallet_id, user_id=current_user.id, wallet=resp['wallet']) result = insert(cowallet) return result
def insert(record): session = current_app.session session.add(record) try: session.commit() return current_app.encode_success() except Exception: logging.exception("Failed to commit cowallet record") session.rollback() resp = current_app.encode_error(Errors.GenericError) return resp finally: session.close()
def post(self): """ Add a cosigner controlled by this server to a given wallet. Parameters required from the client: * secret [text] - secret required to join the wallet * id [text] - wallet ID for this cosigner to join """ join_secret = g.payload.get('secret', '').encode('ascii') wallet_id = g.payload.get('id', '').encode('ascii') if not wallet_id or not join_secret: return current_app.encode_error(Errors.MissingArguments) # Check that the wallet belongs to this user. count = current_app.session.query(db.WalletBlob).filter( db.WalletBlob.user_id == current_user.id, db.WalletBlob.id == wallet_id).count() if not count: return current_app.encode_error(Errors.WalletNotFound) # Send the join request to the cosigning server. resp = current_app.cosigner('/join', secret=join_secret, walletId=wallet_id) if resp is None: # Cosigner server is not available. return current_app.encode_error(Errors.CosigningDisabled) if 'wallet' not in resp: logging.info(resp) err = ErrorCode(COSIGNER_ERR, resp['error']) return current_app.encode_error(err) # Store the cosigner wallet. cowallet = db.CosignerWallet(wallet_id=wallet_id, user_id=current_user.id, wallet=resp['wallet']) result = insert(cowallet) return result
def post(self): """ Register a new user. Parameters required from the client: * username [text] * check [text] - 6 hexadecimal digits * salt [text] - hexadecimal digits * iterations [integer] - must be at least 10000 """ resp = jws_preprocessor(request) if isinstance(resp, Response): # Some error occurred. return resp records = signup_request(**resp) if isinstance(records, ErrorCode): logging.error("Failed to validate signup request") err = records return current_app.encode_error(err) return signup_insert(records)