def _get(self, resource_url, params={}, data = {}): """ Функція підписує заголовки, указані в SIGNATURE_HEADERS, і відправляє запит до вказаного API resource_url, передаючи серверу параметри із params Повертає словник в форматі json """ auth = HTTPSignatureAuth(key_id = self.API_KEY, secret = self.API_SECRET, algorithm = 'hmac-sha256', headers = SIGNATURE_HEADERS) # Відсилаємо запит до api, параметри кодуємо функцією urlencode. # Особливість urlencode - кодує значення somevar = None в строку "somevar=None", тому замінюємо всі None на пусті значення try: response = requests.get('%s/%s/?%s'%(self.API_URL, resource_url, urlencode(params).replace('None', '')), auth = auth, headers = self.HEADERS, data=json.dumps(data)) except RequestException as error: raise APIGetError("Error, while loading data. %s"%error) # Якщо сервер повертає помилку, виводимо її # Формат відповіді сервера {'detail':'error message'} if not response.status_code in [requests.codes.OK, requests.codes.CREATED]: try: error = response.json().get('detail', '') raise APIGetError('Error, while loading data. %s'%error) # Якщо сервер не повернув помилку, як об’єкт json except ValueError: raise APIGetError('%s %s'%(response.status_code, response.reason)) # Інакше повертаємо результат if response.text: return response.json() return {}
def __init__(self, api_endpoint, api_key, api_secret, debug=False): """ :param str api_endpoint: the base url to use for all API requests :param str api_key: the API_KEY to use for authentication :param str api_secret: the API_SECRET to use for authentication :param bool debug: print debug information when requests fail """ self.api_endpoint = api_endpoint self.debug = debug # create a default User-Agent self.default_headers = { 'User-Agent': "%s/%s" % (blocktrail.SDK_USER_AGENT, blocktrail.SDK_VERSION) } # api_key is always in the query string self.default_params = {'api_key': api_key} # prepare HTTP-Signature Auth signer self.auth = HTTPSignatureAuth( key_id=api_key, secret=api_secret, algorithm='hmac-sha256', headers=['(request-target)', 'Date', 'Content-MD5'])
def logon(cls, username=None, password=None): config = ConfigDict("cloudmesh.yaml") cometConf = config["cloudmesh.comet"] cls.set_endpoint(cometConf["active"]) cls.set_base_uri(cometConf["endpoints"][cls.endpoint]["nucleus_base_url"]) cls.set_api_version(cometConf["endpoints"][cls.endpoint]["api_version"]) cls.set_auth_provider() # print (cls.endpoint) # print (cls.base_uri) # print (cls.api_version) # print (cls.auth_provider) ret = False if "USERPASS" == cls.auth_provider: # for unit testing only. if username is None: username = cometConf["endpoints"][cls.endpoint]["userpass"]["username"] if username == '' or username == 'TBD': username = cometConf["username"] if password is None: password = cometConf["endpoints"][cls.endpoint]["userpass"]["password"] if password.lower() == "readline": password = getpass.getpass() elif password.lower() == "env": password = os.environ.get("COMET_PASSWORD", getpass.getpass()) if cls.token is None: if cls.auth_uri: if cls.tunnelled: authuri = "%s/login/" % cls.local_auth_uri else: authuri = "%s/login/" % cls.auth_uri data = {"username": username, "password": password} r = requests.post(authuri, data=json.dumps(data), headers=cls.HEADER, verify=cls.verify) try: cls.token = r.json()["key"] cls.AUTH_HEADER['Authorization'] = "Token {:}".format( cls.token) except: ret = False ret = cls.token else: ret = cls.token elif "APIKEY" == cls.auth_provider: # print ("API KEY based auth goes here") cls.api_key = cometConf["endpoints"][cls.endpoint]["apikey"]["api_key"] cls.api_secret = cometConf["endpoints"][cls.endpoint]["apikey"]["api_secret"] cls.api_auth = HTTPSignatureAuth(secret=cls.api_secret, headers=["nonce", "timestamp"]) # # api key based auth does not maintain a session # once values specified, considered as AuthNed. if cls.api_key and cls.api_secret and cls.api_auth: ret = True else: print("The specified AUTH Provider Not Currently Supported") pass return ret
def _options(self, resource_url): auth = HTTPSignatureAuth(key_id = self.API_KEY, secret = self.API_SECRET, algorithm = 'hmac-sha256', headers = SIGNATURE_HEADERS) try: response = requests.options('%s/%s/'%(self.API_URL, resource_url), auth = auth, headers = self.HEADERS) except RequestException as error: raise APIUploadError("Error, while loading data. %s"%error) return response.json()
def __init__(self, api_key, api_secret, app_id, version='v1.0', timeout=6.0, base_url=None, location=None): ''' Initialize the client with the given api key and secret :param api_key: the api key :param api_secret: the api secret :param app_id: the app id **Example usage**:: import stream # initialize the client client = stream.connect('key', 'secret') # get a feed object feed = client.feed('aggregated:1') # write data to the feed activity_data = {'actor': 1, 'verb': 'tweet', 'object': 1} activity_id = feed.add_activity(activity_data)['id'] activities = feed.get() feed.follow('flat:3') activities = feed.get() feed.unfollow('flat:3') feed.remove_activity(activity_id) ''' self.api_key = api_key self.api_secret = api_secret self.app_id = app_id self.version = version self.timeout = timeout self.location = location if os.environ.get('LOCAL'): self.base_url = 'http://localhost:8000/api/' self.timeout = 20 elif base_url is not None: self.base_url = base_url elif location is not None: self.base_url = 'https://%s-api.getstream.io/api/' % location self.base_analytics_url = 'https://analytics.getstream.io/analytics/' self.session = requests.Session() # TODO: turn this back on after we verify it doesnt retry on slower requests self.session.mount(self.base_url, HTTPAdapter(max_retries=0)) self.auth = HTTPSignatureAuth(api_key, secret=api_secret)
def test_signed_request(self): signature_headers = ['(request-target)', 'accept', 'date', 'host'] headers = { 'Host': 'localhost:8000', 'Accept': 'application/json', 'Date': "Mon, 6 Jul 2020 10:11:00 GMT" } auth = HTTPSignatureAuth(key_id=KEY_ID, secret=SECRET, algorithm='rsa-sha256', headers=signature_headers) data = {'auth': auth, 'headers': headers} response = self.client.get( 'http://localhost:8000/api/premiumguapshit/', data, format='json') print(response.json()) self.assertEqual(response.json()['status'], 'VIP')
def _post(self, resource_url, params={}, data = {}, chunk = False): """ Функція підписує заголовки, указані в SIGNATURE_HEADERS, і відправляє запит до вказаного API resource_url, передаючи серверу параметри із params Повертає словник в форматі json """ signature_headers = ['accept', 'date', 'host', '(request-target)'] headers = self.HEADERS headers['content-type'] = "application/json" auth = HTTPSignatureAuth(key_id = self.API_KEY, secret = self.API_SECRET, algorithm = 'hmac-sha256', headers = signature_headers) # Відсилаємо запит до api, параметри кодуємо функцією urlencode. try: response = requests.post('%s/%s/'%(self.API_URL, resource_url), data = json.dumps(data), auth = auth, headers = headers) except RequestException as error: raise APIUploadError("Error, while loading data. %s"%error) # Якщо сервер повертає помилку, виводимо її # Формат відповіді сервера {'detail':'error message'} if not response.status_code in [requests.codes.OK, requests.codes.CREATED]: try: error = response.json() print(error) # print error # Якщо data - це чанк, виду [obj, obj, ...] if chunk and isinstance(error, list): # Вичисляємо список індексів елементів чанку, які викликали помилку failed_elements = [error.index(x) for x in error if x!={}] # Формуємо чанк, який не буде викликати помилку на сервері data = [x for x in data if data.index(x) not in failed_elements] # Відправляємо сформований чанк на сервер requests.post('%s/%s/'%(API_URL, resource_url), data = json.dumps(data), auth = auth, headers = headers) # Повертаємо індекси невірних елементів, для подальшої обробки, або виводу користувачу return failed_elements raise APIUploadError('Error, while loading data. %s'%str(error.get('detail', ''))) # Якщо сервер не повернув помилку, як об’єкт json except ValueError: raise APIUploadError('%s %s'%(response.status_code, response.reason)) # Інакше повертаємо результат if response.text and not chunk: return response.json() return {}
def sign(self, headers: dict, body=None, more_sign_headers=None): now = gmt_time() # Add a request ID, if not any if 'X-Request-Id' not in headers: request_id = str(uuid.uuid4()) headers['X-Request-Id'] = request_id # Add headers needed by HTTPSig headers['Date'] = now headers['Original-Date'] = now headers['Accept-Signature'] = self.algorithm # Which headers will be signed sign_headers = ['(request-target)', 'Host', 'Date', 'Original-Date', 'Digest', 'X-Request-Id'] # Add other headers to sign the user might want to add if more_sign_headers: for h in more_sign_headers: if h not in sign_headers: sign_headers.append(h) # Not sure if this is required, I think it is not on the draft, but # the lib we use from EWP requires a digest even if no body is passed if not body: body = "".encode('utf-8') # Calculate body digest, even if no body is passed if "Digest" not in sign_headers: sign_headers.append("Digest") digest = self._base64_digest(body) logging.debug("Digest: " + digest) headers["Digest"] = "SHA-256=" + digest logging.debug("Headers: " + str(headers)) logging.debug("Sign-Headers: " + str(sign_headers)) # Create the signature handler auth = HTTPSignatureAuth(key_id=self.key_id, #key=self.privkey, secret=self.privkey, algorithm=self.algorithm, headers=sign_headers) return {'auth': auth, 'headers': headers}
def send(activity, inbox_url, user_domain): """Sends an ActivityPub request to an inbox. Args: activity: dict, AS2 activity inbox_url: string user_domain: string, domain of the bridgy fed user sending the request Returns: requests.Response """ logging.info( 'Sending AP request from {user_domain}: {json_dumps(activity, indent=2)}' ) # prepare HTTP Signature (required by Mastodon) # https://w3c.github.io/activitypub/#authorization # https://tools.ietf.org/html/draft-cavage-http-signatures-07 # https://github.com/tootsuite/mastodon/issues/4906#issuecomment-328844846 acct = 'acct:%s@%s' % (user_domain, user_domain) key = MagicKey.get_or_create(user_domain) auth = HTTPSignatureAuth(secret=key.private_pem(), key_id=acct, algorithm='rsa-sha256', sign_header='signature', headers=('Date', 'Digest', 'Host')) # deliver to inbox body = json_dumps(activity).encode() headers = { 'Content-Type': common.CONTENT_TYPE_AS2, # required for HTTP Signature # https://tools.ietf.org/html/draft-cavage-http-signatures-07#section-2.1.3 'Date': datetime.datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT'), # required by Mastodon # https://github.com/tootsuite/mastodon/pull/14556#issuecomment-674077648 'Digest': 'SHA-256=' + b64encode(sha256(body).digest()).decode(), 'Host': util.domain_from_link(inbox_url), } return common.requests_post(inbox_url, data=body, auth=auth, headers=headers)
def _put(self, resource_url, params={}, data={}): """ Функція підписує заголовки, указані в SIGNATURE_HEADERS, і відправляє запит до вказаного API resource_url, передаючи серверу параметри із params Повертає словник в форматі json """ auth = HTTPSignatureAuth(key_id=self.API_KEY, secret=self.API_SECRET, algorithm='hmac-sha256', headers=SIGNATURE_HEADERS) # Відсилаємо запит до api, параметри кодуємо функцією urlencode. try: response = requests.put('%s/%s/' % (API_URL, resource_url), params=params, data=json.dumps(data), auth=auth, headers=HEADERS) except RequestException, error: raise APIUploadError("Error, while loading data. %s" % error)
def _get(self, resource_url, params={}, data={}): """ Функція підписує заголовки, указані в SIGNATURE_HEADERS, і відправляє запит до вказаного API resource_url, передаючи серверу параметри із params Повертає словник в форматі json """ auth = HTTPSignatureAuth(key_id=self.API_KEY, secret=self.API_SECRET, algorithm='hmac-sha256', headers=SIGNATURE_HEADERS) # Відсилаємо запит до api, параметри кодуємо функцією urlencode. # Особливість urlencode - кодує значення somevar = None в строку "somevar=None", тому замінюємо всі None на пусті значення try: response = requests.get( '%s/%s/?%s' % (API_URL, resource_url, urllib.urlencode(params).replace('None', '')), auth=auth, headers=HEADERS, data=json.dumps(data)) except RequestException, error: raise APIGetError("Error, while loading data. %s" % error)
def send(activity, inbox_url, user_domain): """Sends an ActivityPub request to an inbox. Args: activity: dict, AS2 activity inbox_url: string user_domain: string, domain of the bridgy fed user sending the request Returns: requests.Response """ logging.info('Sending AP request from %s: %s', user_domain, json.dumps(activity, indent=2)) # prepare HTTP Signature (required by Mastodon) # https://w3c.github.io/activitypub/#authorization-lds # https://tools.ietf.org/html/draft-cavage-http-signatures-07 # https://github.com/tootsuite/mastodon/issues/4906#issuecomment-328844846 acct = 'acct:%s@%s' % (user_domain, user_domain) key = MagicKey.get_or_create(user_domain) auth = HTTPSignatureAuth(secret=key.private_pem(), key_id=acct, algorithm='rsa-sha256') # deliver to inbox headers = { 'Content-Type': common.CONTENT_TYPE_AS2, # required for HTTP Signature # https://tools.ietf.org/html/draft-cavage-http-signatures-07#section-2.1.3 'Date': datetime.datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT'), } return common.requests_post(inbox_url, json=activity, auth=auth, headers=headers)
def try_activitypub(self): source = util.get_required_param(self, 'source') # fetch source page, convert to ActivityStreams source_resp = common.requests_get(source) source_url = source_resp.url or source source_mf2 = mf2py.parse(source_resp.text, url=source_url) # logging.debug('Parsed mf2 for %s: %s', source_resp.url, json.dumps(source_mf2, indent=2)) entry = mf2util.find_first_entry(source_mf2, ['h-entry']) logging.info('First entry: %s', json.dumps(entry, indent=2)) # make sure it has url, since we use that for AS2 id, which is required # for ActivityPub. props = entry.setdefault('properties', {}) if not props.get('url'): props['url'] = [source_url] source_obj = microformats2.json_to_object(entry, fetch_mf2=True) logging.info('Converted to AS: %s', json.dumps(source_obj, indent=2)) # fetch target page as AS object. target is first in-reply-to, like-of, # or repost-of, *not* target query param.) target = util.get_url(util.get_first(source_obj, 'inReplyTo') or util.get_first(source_obj, 'object')) if not target: common.error(self, 'No u-in-reply-to, u-like-of, or u-repost-of ' 'found in %s' % source_url) try: target_resp = common.get_as2(target) except (requests.HTTPError, exc.HTTPBadGateway) as e: if (e.response.status_code // 100 == 2 and common.content_type(e.response).startswith('text/html')): self.resp = Response.get_or_create( source=source_url, target=e.response.url or target, direction='out', source_mf2=json.dumps(source_mf2)) return self.send_salmon(source_obj, target_resp=e.response) raise target_url = target_resp.url or target self.resp = Response.get_or_create( source=source_url, target=target_url, direction='out', protocol='activitypub', source_mf2=json.dumps(source_mf2)) # find actor's inbox target_obj = target_resp.json() inbox_url = target_obj.get('inbox') if not inbox_url: # TODO: test actor/attributedTo and not, with/without inbox actor = target_obj.get('actor') or target_obj.get('attributedTo') if isinstance(actor, dict): inbox_url = actor.get('inbox') actor = actor.get('url') if not inbox_url and not actor: common.error(self, 'Target object has no actor or attributedTo URL') if not inbox_url: # fetch actor as AS object actor = common.get_as2(actor).json() inbox_url = actor.get('inbox') if not inbox_url: # TODO: probably need a way to save errors like this so that we can # return them if ostatus fails too. # common.error(self, 'Target actor has no inbox') return self.send_salmon(source_obj, target_resp=target_resp) # convert to AS2 source_domain = urlparse.urlparse(source_url).netloc key = MagicKey.get_or_create(source_domain) source_activity = common.postprocess_as2( as2.from_as1(source_obj), target=target_obj, key=key) if self.resp.status == 'complete': source_activity['type'] = 'Update' # prepare HTTP Signature (required by Mastodon) # https://w3c.github.io/activitypub/#authorization-lds # https://tools.ietf.org/html/draft-cavage-http-signatures-07 # https://github.com/tootsuite/mastodon/issues/4906#issuecomment-328844846 acct = 'acct:%s@%s' % (source_domain, source_domain) auth = HTTPSignatureAuth(secret=key.private_pem(), key_id=acct, algorithm='rsa-sha256') # deliver source object to target actor's inbox. headers = { 'Content-Type': common.CONTENT_TYPE_AS2, # required for HTTP Signature # https://tools.ietf.org/html/draft-cavage-http-signatures-07#section-2.1.3 'Date': datetime.datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT'), } inbox_url = urlparse.urljoin(target_url, inbox_url) resp = common.requests_post(inbox_url, json=source_activity, auth=auth, headers=headers) self.response.status_int = resp.status_code if resp.status_code == 202: self.response.write('202 response! If this is Mastodon 1.x, their ' 'signature verification probably failed. :(\n') self.response.write(resp.text)
""" import json import logging from datetime import datetime from time import mktime from wsgiref.handlers import format_date_time import requests from django.conf import settings from httpsig.requests_auth import HTTPSignatureAuth logger = logging.getLogger(__name__) _httpsig_auth = HTTPSignatureAuth(key_id=settings.ORCHESTRA_PROJECT_API_KEY, secret=settings.ORCHESTRA_PROJECT_API_SECRET, algorithm='hmac-sha256') _api_root_url = '{}/orchestra/api/project/'.format(settings.ORCHESTRA_URL) class OrchestraError(Exception): pass def _make_api_request(method, endpoint, *args, **kwargs): func = getattr(requests, method) # Adding 'date' header as per # https://github.com/zzsnzmn/py-http-signature/blob/e2e2c753db7da45fab4b215d84e8d490bd708833/http_signature/sign.py#L155 # noqa headers = { 'date': format_date_time(mktime(datetime.now().timetuple())), 'X-Api-Version': '~6.5',
def auth(): auth = HTTPSignatureAuth(key_id=ApiAuthentication.access_key_id(), secret=ApiAuthentication.secret_access_key(), algorithm='hmac-sha256', headers=ApiAuthentication.signature_headers()) return auth