According to official documentation https://bybit-exchange.github.io/docs/derivativesV3/unified_margin/#t-constructingtherequest I try to get account balance endpoint: GET /unified/v3/private/account/wallet/balance. But anyway I get this response: {"retCode":10004,"retMsg":"error sign!","result":null,"retExtInfo":null,"time":1665751778451}
from urllib.parse import urlencode
import requests
import hashlib
import time
import hmac
import uuid
api_key = 'xxxxxxxxxxxx'
secret_key = 'xxxxxxxxxxxxxxxxxxxx'
httpClient = requests.Session()
recv_window = "5000"
url = "https://api.bybit.com"
def HTTP_Request(endPoint, method, payload):
time_stamp = requests.get(url + "/v3/public/time").json()
time_stamp = str(time_stamp["time"])
signature = genSignature(params, time_stamp)
headers = {
'X-BAPI-SIGN-TYPE': '2',
'X-BAPI-SIGN': signature,
'X-BAPI-API-KEY': api_key,
'X-BAPI-TIMESTAMP': time_stamp,
'X-BAPI-RECV-WINDOW': recv_window
}
response = httpClient.request(method, url+endpoint+"?"+payload, headers=headers)
print(response.text)
def genSignature(payload, time_stamp):
# encoding list obj to url format str
peyload_url_enc = urlencode(
sorted(payload.items(), key=lambda tup: tup[0])
)
param_str = str(time_stamp) + api_key + recv_window + peyload_url_enc
hash = hmac.new(
key=bytes(secret_key, "utf-8"),
msg=param_str.encode("utf-8"),
digestmod="sha256"
)
signature = hash.hexdigest()
return signature
endpoint = "/unified/v3/private/account/wallet/balance"
method = "GET"
params = {"coin": "USDT"}
HTTP_Request(endpoint, method, str(params))
The problem seems to be in genSignature, but no matter how I change the function, the error remains the same
Thanks to this repository I found another method of connecting to the Bybit. So if you faced with similar problem, open the link, there are also some examples of creating different post/get queries
My working variant:
def bybit_spot_balance(AccessKey: str, SecretKey: str) -> dict:
params = {
"api_key": AccessKey,
"timestamp": round(time.time() * 1000),
"recv_window": 10000
}
# Create the param str
param_str = urlencode(
sorted(params.items(), key=lambda tup: tup[0])
)
# Generate the signature
hash = hmac.new(
bytes(SecretKey, "utf-8"),
param_str.encode("utf-8"),
hashlib.sha256
)
signature = hash.hexdigest()
sign_real = {
"sign": signature
}
param_str = quote_plus(param_str, safe="=&")
full_param_str = f"{param_str}&sign={sign_real['sign']}"
# Request information
url = "https://api.bybit.com/spot/v3/private/account"
headers = {"Content-Type": "application/json"}
body = dict(params, **sign_real)
urllib3.disable_warnings()
response = requests.get(f"{url}?{full_param_str}", headers=headers, verify=False).json()
return response
I have a class called Org, and I'm trying to access its method from multiple functions (that are defined outside of the class). I'm calling main() first, followed by discover_buildings(). The main() executes without error, however, I get AttributeError: 'Org' has no attribute 'headers' error after I call discover_buildings(). What is it that I'm doing wrong? (I was expecting the headers attribute to be shared across the different methods)
class Org(object):
def __init__(self, client_id, client_secret, grant_type='client_credentials'):
self.grant_type = grant_type
self.client_id = client_id
self.client_secret = client_secret
self.url = CI_A_URL
def auth(self):
""" authenticate with bos """
params = {
'client_id': self.client_id,
'client_secret': self.client_secret,
'grant_type': self.grant_type
}
r = requests.post(self.url + 'o/token/', data=params)
if r.status_code == 200:
self.access_token = r.json()['access_token']
self.headers = {
'Authorization': 'Bearer %s' %self.access_token,
'Content-Type': 'application/json',
}
else:
logging.error(r.content)
r.raise_for_status()
def get_buildings(self, perPage=1000):
params = {
'perPage': perPage
}
r = requests.get(self.url + 'buildings/', params=params, headers=self.headers)
result = r.json().get('data')
if r.status_code == 200:
buildings_dict = {i['name']: i['id'] for i in result}
sheet_buildings['A1'].value = buildings_dict
else:
logging.error(r.content)
r.raise_for_status()
client_id = 'xxx'
client_secret = 'yyy'
gateway_id = 123
o = Org(client_id, client_secret)
def discover_buildings():
return o.get_buildings()
def main():
return o.auth()
Thanks, in advance, for your help!
Try using a property to calculate headers whenever you need it and then cache it.
def auth(self):
""" authenticate with bos """
# 👇you might want to isolate `token` into a nested #property token
params = {
'client_id': self.client_id,
'client_secret': self.client_secret,
'grant_type': self.grant_type
}
# note assignment to `_headers`, not `headers`
r = requests.post(self.url + 'o/token/', data=params)
if r.status_code == 200:
self._access_token = r.json()['access_token']
# 👆
self._headers = { # 👈
'Authorization': 'Bearer %s' %self._access_token,
'Content-Type': 'application/json',
}
else:
logging.error(r.content)
r.raise_for_status()
#cache after the first time.
_headers = None
#property
def headers(self):
""" call auth when needed
you might want to isolate `token`
into its own property, allowing different
headers to use the same token lookup
"""
if self._headers is None:
self.auth()
return self._headers
the problem is the way you define "discover_buildings"
you define it first with "o" just initialised not after the authentication.
to handle this:
rewrite discover to take 'o' as a parameter
or
check first to see 'o' has 'headers' if not authenticate 'o' and do the rest
def discover_buildings():
if not getattr(o, 'headers'):
o.auth()
return o.get_buildings()
You didn't define self.headers. You need to run o.auth() (or define self.headers) before you run o.get_buildings().
I am using the Requests-Oauthlib1 workflow to authenticate on a site with oauth 1 from django.
I think I am not using the one obvious way to do it.
The way I have implemented it:
/oauth - Gets the request token and returns the oauth authorization url
def oauth(request):
oauth_service = Oauth_service()
key, secret = oauth_service.obtain_request_token()
message = oauth_service.get_authorization_url()
return HttpResponse(message)
/oauthcomplete - oauth success redirects here, but I am now creating a new instance of the Oath_Service which won't have the request token and secret
def oauth_complete(request):
oauth_service = Oauth_service()
verifier = oauth_service.get_verifier(request.build_absolute_uri())
return HttpResponse("Verified with {}".format(verifier))
oauth_service.py
class Oauth_service:
'''
Class used to commuicate with an Oauth magento
'''
BASE_URL = 'https://xxx'
RETRIEVE_REQUEST_TOKEN_PATH = '/oauth/initiate'
ADMIN_AUTHORIZATION_PATH = '/admin/oauth_authorize'
RETRIEVE_ACCESS_TOKEN_PATH = '/oauth/token'
OAUTH_CONSUMER_KEY = 'xxx'
OAUTH_SECRET_KEY = 'xxx'
OAUTH_SIGNATURE_METHOD = 'HMAC-SHA1'
CALLBACK_URL = 'http://127.0.0.1:8000/models/oauth_complete'
oauth = OAuth1Session(
OAUTH_CONSUMER_KEY,
client_secret=OAUTH_SECRET_KEY,
callback_uri=CALLBACK_URL,
)
resource_owner_key = ''
resource_owner_secret = ''
verifier = ''
def obtain_request_token(self):
'''
step 1 in oauth process
'''
request_token_url = self.BASE_URL + self.RETRIEVE_REQUEST_TOKEN_PATH
headers = {
'Accept': 'application/json'
}
fetch_response = self.oauth.fetch_request_token(request_token_url, verify=False, headers=headers)
self.resource_owner_key = fetch_response.get('oauth_token')
self.resource_owner_secret = fetch_response.get('oauth_token_secret')
return self.resource_owner_key, self.resource_owner_secret
def get_authorization_url(self):
base_authorization_url = self.BASE_URL + self.ADMIN_AUTHORIZATION_PATH
authorization_url = self.oauth.authorization_url(base_authorization_url)
return 'Please go here and authorize, {}'.format(authorization_url)
def get_verifier(self, url):
oauth_response = self.oauth.parse_authorization_response(url)
verifier = oauth_response.get('oauth_verifier')
return verifier
It feels like I am doing this in a strange way with use of class variables that are updated but do not persist. As those class variables won't be their when a new instance of the service is created.
I am trying to maintain the OauthSession1 instance during the full oauth workflow.
Context
I am writing a python/django application which accesses Fitbit data.
Problem
To access a users data I must get their token for which can be used repeatably to access fitness data. The following goes through the current steps.
1. Firstly I present the user with a link:
def register_2(request):
if request.user.is_authenticated():
oauth = OAuth2Session(
auth_2_client_id,
redirect_uri = redirect_uri,
scope = fitbit_scope)
authorization_url, state = oauth.authorization_url(fitbit_url_authorise_2)
return render_to_response('register.html',{
"authorization_url" : authorization_url
},context_instance=RequestContext(request))
return HttpResponseRedirect("/")
2. User goes to Fitbit, logs into their account and authorises access.
3. The user is then returned to my site with a code that should allow me to get the token.
def callback_2(request):
if request.user.is_authenticated():
code = request.GET.get('code')
state = request.GET.get('state')
oauth = OAuth2Session(
client_id = auth_2_client_id,
redirect_uri = redirect_uri
)
token = oauth.fetch_token(
code = code,
token_url = fitbit_url_access_2,
authorization_response = request.build_absolute_uri()
)
Once callback_2 is called I get the error:
(missing_token) Missing access token parameter.
Resources:
Fitbit OAUTH2 API
OAuth2Session Docs
Found a way around this. Pretty simple after all the effort of research. The following is a custom method using requests.post() and base64.b64encode().
def callback_2(request):
if request.user.is_authenticated():
code = request.GET.get('code')
state = request.GET.get('state')
url = fitbit_url_access_2
data = "client_id=" + auth_2_client_id + "&" +\
"grant_type=" + "authorization_code" + "&" +\
"redirect_uri=" + redirect_uri_special + "&" +\
"code=" + code
headers = {
'Authorization': 'Basic ' + base64.b64encode(auth_2_client_id + ':' + consumer_secret),
'Content-Type': 'application/x-www-form-urlencoded'}
r = requests.post(url, data=data, headers=headers).json()
I'm having an awfully hard time with Yahoo's authentication/authorization. I've enabled BOSS in my account, set up a payment method, and now I'm trying to run a search using some python code:
import urllib2
import oauth2 as oauth
import time
OAUTH_CONSUMER_KEY = "blahblahblah"
OAUTH_CONSUMER_SECRET = "blah"
def oauth_request(url, params, method="GET"):
params['oauth_version'] = "1.0",
params['oauth_nonce'] = oauth.generate_nonce(),
params['oauth_timestamp'] = int(time.time())
consumer = oauth.Consumer(key=OAUTH_CONSUMER_KEY,
secret=OAUTH_CONSUMER_SECRET)
params['oauth_consumer_key'] = consumer.key
req = oauth.Request(method=method, url=url, parameters=params)
req.sign_request(oauth.SignatureMethod_HMAC_SHA1(), consumer, None)
return req
if __name__ == "__main__":
url = "http://yboss.yahooapis.com/ysearch/web"
req = oauth_request(url, params={"q": "cats dogs"})
req_url = req.to_url()
print req_url
result = urllib2.urlopen(req_url)
I keep getting a urllib2.HTTPError: HTTP Error 401: Unauthorized exception. I can't figure out whether there's something wrong with my key, or the method of signing, or if I'm somehow tampering with my data after signing, or what the deal is. Anyone have suggestions?
I made some small changes to make your example work. See code for comments.
import urllib2
import oauth2 as oauth
import time
OAUTH_CONSUMER_KEY = "blahblahblah"
OAUTH_CONSUMER_SECRET = "blah"
def oauth_request(url, params, method="GET"):
# Removed trailing commas here - they make a difference.
params['oauth_version'] = "1.0" #,
params['oauth_nonce'] = oauth.generate_nonce() #,
params['oauth_timestamp'] = int(time.time())
consumer = oauth.Consumer(key=OAUTH_CONSUMER_KEY,
secret=OAUTH_CONSUMER_SECRET)
params['oauth_consumer_key'] = consumer.key
req = oauth.Request(method=method, url=url, parameters=params)
req.sign_request(oauth.SignatureMethod_HMAC_SHA1(), consumer, None)
return req
if __name__ == "__main__":
url = "http://yboss.yahooapis.com/ysearch/web"
req = oauth_request(url, params={"q": "cats dogs"})
# This one is a bit nasty. Apparently the BOSS API does not like
# "+" in its URLs so you have to replace "%20" manually.
# Not sure if the API should be expected to accept either.
# Not sure why to_url does not just return %20 instead...
# Also, oauth2.Request seems to store parameters as unicode and forget
# to encode to utf8 prior to percentage encoding them in its to_url
# method. However, it's handled correctly for generating signatures.
# to_url fails when query parameters contain non-ASCII characters. To
# work around, manually utf8 encode the request parameters.
req['q'] = req['q'].encode('utf8')
req_url = req.to_url().replace('+', '%20')
print req_url
result = urllib2.urlopen(req_url)
Here is a Python code snippet that works for me against Yahoo! BOSS:
import httplib2
import oauth2
import time
OAUTH_CONSUMER_KEY = "Blah"
OAUTH_CONSUMER_SECRET = "Blah"
if __name__ == "__main__":
url = "http://yboss.yahooapis.com/ysearch/web?q=cats%20dogs"
consumer = oauth2.Consumer(key=OAUTH_CONSUMER_KEY,secret=OAUTH_CONSUMER_SECRET)
params = {
'oauth_version': '1.0',
'oauth_nonce': oauth2.generate_nonce(),
'oauth_timestamp': int(time.time()),
}
oauth_request = oauth2.Request(method='GET', url=url, parameters=params)
oauth_request.sign_request(oauth2.SignatureMethod_HMAC_SHA1(), consumer, None)
oauth_header=oauth_request.to_header(realm='yahooapis.com')
# Get search results
http = httplib2.Http()
resp, content = http.request(url, 'GET', headers=oauth_header)
print resp
print content
Im using an Authenticate Header to submit the OAuth signature.
So I decided to ditch Python and try Perl, and it Just Worked. Here's a minimal code sample:
use strict;
use Net::OAuth;
use LWP::UserAgent;
my $CC_KEY = "blahblahblah";
my $CC_SECRET = "blah";
my $url = 'http://yboss.yahooapis.com/ysearch/web';
print make_request($url, {q => "cat dog", format => "xml", count => 5});
sub make_request {
my ($url, $args) = #_;
my $request = Net::OAuth->request("request token")
->new(
consumer_key => $CC_KEY,
consumer_secret => $CC_SECRET,
request_url => $url,
request_method => 'GET',
signature_method => 'HMAC-SHA1',
timestamp => time,
nonce => int(rand 10**6),
callback => 'oob',
extra_params => $args,
protocol_version => Net::OAuth::PROTOCOL_VERSION_1_0A,
);
$request->sign;
my $res = LWP::UserAgent->new(env_proxy=>1)->get($request->to_url);
return $res->content if $res->is_success;
die $res->status_line;
}
Here's another solution, this time back in python-land. This was put together by Tom De Smedt, author of the Pattern web-mining kit.
I'll communicate with the author of python-oauth2 to see if it can be fixed.
OAUTH_CONSUMER_KEY = "blahblahblah"
OAUTH_CONSUMER_SECRET = "blah"
import urllib
import hmac
import time
import random
import base64
try:
from hashlib import sha1
from hashlib import md5
except:
import sha as sha1
import md5; md5=md5.new
def hmac_sha1(key, text):
return hmac.new(key, text, sha1).digest()
def oauth_nonce(length=40):
h = "".join([str(random.randint(0, 9)) for i in range(length)])
h = md5(str(time.time()) + h).hexdigest()
return h
def oauth_timestamp():
return str(int(time.time()))
def oauth_encode(s):
return urllib.quote(s, "~")
def oauth_signature(url, data={}, method="get", secret="", token=""):
# Signature base string: http://tools.ietf.org/html/rfc5849#section-3.4.1
base = oauth_encode(method.upper()) + "&"
base += oauth_encode(url.rstrip("?")) + "&"
base += oauth_encode("&".join(["%s=%s" % (k, v) for k, v in sorted(data.items())]))
# HMAC-SHA1 signature algorithm: http://tools.ietf.org/html/rfc5849#section-3.4.2
signature = hmac_sha1(oauth_encode(secret) + "&" + token, base)
signature = base64.b64encode(signature)
return signature
q = "cat"
url = "http://yboss.yahooapis.com/ysearch/" + "web" # web | images | news
data = {
"q": q,
"start": 0,
"count": 50, # 35 for images
"format": "xml",
"oauth_version": "1.0",
"oauth_nonce" : oauth_nonce(),
"oauth_timestamp" : oauth_timestamp(),
"oauth_consumer_key" : OAUTH_CONSUMER_KEY,
"oauth_signature_method" : "HMAC-SHA1",
}
data["oauth_signature"] = oauth_signature(url, data, secret=OAUTH_CONSUMER_SECRET)
complete_url = url + "?" + urllib.urlencode(data)
response = urllib.urlopen(complete_url)
print response.read()
Here is sample code to access Yahoo! BOSS API v2 using with python-oauth as oauth liberary.
OAUTH_CONSUMER_KEY = "<oauth consumer key>"
OAUTH_CONSUMER_SECRET = "<oauth consumer secret>"
URL = "http://yboss.yahooapis.com/ysearch/web"
import urllib
import oauth.oauth as oauth
data = {
"q": "yahoo boss search",
"start":0,
"count":2,
"format":"json"
}
consumer = oauth.OAuthConsumer(OAUTH_CONSUMER_KEY, OAUTH_CONSUMER_SECRET)
signature_method_plaintext = oauth.OAuthSignatureMethod_PLAINTEXT()
signature_method_hmac_sha1 = oauth.OAuthSignatureMethod_HMAC_SHA1()
oauth_request = oauth.OAuthRequest.from_consumer_and_token(consumer, token=None, http_method='GET', http_url=URL, parameters=data)
oauth_request.sign_request(signature_method_hmac_sha1, consumer, "")
complete_url = oauth_request.to_url()
response = urllib.urlopen(complete_url)
print "REQUEST URL => %s" % complete_url
print ""
print "RESPONSE =>"
print response.read()
I stepped into the urllib2.open code using the debugger, and found that the response has this header:
WWW-Authenticate: OAuth oauth_problem="version_rejected", realm="yahooapis.com"
So I guess I'm having some kind of version mismatch of OAuth.