def buildSignedUrl(self, methodName, *args, **kwargs): """ Build a signed URL. This URL contains the credentials needed to access a resource.""" if 'expiration' in kwargs: expiration = kwargs['expiration'] del kwargs['expiration'] else: expiration = self.options['signedUrlExpiration'] expiration = int(time.time() + expiration) # Mainly so that we throw if it's not a number requestUrl = self.buildUrl(methodName, *args, **kwargs) if not self._hasCredentials(): raise exceptions.TaskclusterAuthFailure('Invalid Hawk Credentials') clientId = utils.toStr(self.options['credentials']['clientId']) accessToken = utils.toStr(self.options['credentials']['accessToken']) def genBewit(): # We need to fix the output of get_bewit. It returns a url-safe base64 # encoded string, which contains a list of tokens separated by '\'. # The first one is the clientId, the second is an int, the third is # url-safe base64 encoded MAC, the fourth is the ext param. # The problem is that the nested url-safe base64 encoded MAC must be # base64 (i.e. not url safe) or server-side will complain. # id + '\\' + exp + '\\' + mac + '\\' + options.ext; resource = mohawk.base.Resource( credentials={ 'id': clientId, 'key': accessToken, 'algorithm': 'sha256', }, method='GET', ext=utils.toStr(self.makeHawkExt()), url=requestUrl, timestamp=expiration, nonce='', # content='', # content_type='', ) bewit = mohawk.bewit.get_bewit(resource) return bewit.rstrip('=') bewit = genBewit() if not bewit: raise exceptions.TaskclusterFailure('Did not receive a bewit') u = urllib.parse.urlparse(requestUrl) return urllib.parse.urlunparse(( u.scheme, u.netloc, u.path, u.params, u.query + 'bewit=%s' % bewit, u.fragment, ))
def genBewit(): # We need to fix the output of get_bewit. It returns a url-safe base64 # encoded string, which contains a list of tokens separated by '\'. # The first one is the clientId, the second is an int, the third is # url-safe base64 encoded MAC, the fourth is the ext param. # The problem is that the nested url-safe base64 encoded MAC must be # base64 (i.e. not url safe) or server-side will complain. # id + '\\' + exp + '\\' + mac + '\\' + options.ext; resource = mohawk.base.Resource( credentials={ 'id': clientId, 'key': accessToken, 'algorithm': 'sha256', }, method='GET', ext=utils.toStr(self.makeHawkExt()), url=requestUrl, timestamp=expiration, nonce='', # content='', # content_type='', ) bewit = mohawk.bewit.get_bewit(resource) return bewit.rstrip('=')
def _makeTopicExchange(self, exchangeUrl, routingKey, routingKeyPattern): if routingKeyPattern is None: routingKeyPattern = {} data = { 'exchange': exchangeUrl, } # If we are passed in a string, we can short-circuit this function if isinstance(routingKeyPattern, six.string_types): log.debug('Passing through string for topic exchange key') data['routingKeyPattern'] = routingKeyPattern return data if not isinstance(routingKeyPattern, dict): errStr = 'routingKeyPattern must eventually be a dict' raise exceptions.TaskclusterTopicExchangeFailure(errStr) # There is no canonical meaning for the maxSize and required # reference entry in the JS client, so we don't try to define # them here, even though they sound pretty obvious routingKeyParts = [] for key in routingKey: if 'constant' in key: value = key['constant'] elif key['name'] in routingKeyPattern: log.debug('Found %s in routing key params', key['name']) value = str(routingKeyPattern[key['name']]) if not key.get('multipleWords') and '.' in value: raise exceptions.TaskclusterTopicExchangeFailure( 'Cannot have periods in single word keys') else: value = '#' if key.get('multipleWords') else '*' log.debug('Did not find %s in input params, using %s', key['name'], value) routingKeyParts.append(value) data['routingKeyPattern'] = '.'.join([utils.toStr(x) for x in routingKeyParts]) return data
def createTemporaryCredentials(clientId, accessToken, start, expiry, scopes, name=None): """ Create a set of temporary credentials Callers should not apply any clock skew; clock drift is accounted for by auth service. clientId: the issuing clientId accessToken: the issuer's accessToken start: start time of credentials (datetime.datetime) expiry: expiration time of credentials, (datetime.datetime) scopes: list of scopes granted name: credential name (optional) Returns a dictionary in the form: { 'clientId': str, 'accessToken: str, 'certificate': str} """ for scope in scopes: if not isinstance(scope, six.string_types): raise exceptions.TaskclusterFailure('Scope must be string') # Credentials can only be valid for 31 days. I hope that # this is validated on the server somehow... if expiry - start > datetime.timedelta(days=31): raise exceptions.TaskclusterFailure('Only 31 days allowed') # We multiply times by 1000 because the auth service is JS and as a result # uses milliseconds instead of seconds cert = dict( version=1, scopes=scopes, start=calendar.timegm(start.utctimetuple()) * 1000, expiry=calendar.timegm(expiry.utctimetuple()) * 1000, seed=utils.slugId() + utils.slugId(), ) # if this is a named temporary credential, include the issuer in the certificate if name: cert['issuer'] = utils.toStr(clientId) sig = ['version:' + utils.toStr(cert['version'])] if name: sig.extend([ 'clientId:' + utils.toStr(name), 'issuer:' + utils.toStr(clientId), ]) sig.extend([ 'seed:' + utils.toStr(cert['seed']), 'start:' + utils.toStr(cert['start']), 'expiry:' + utils.toStr(cert['expiry']), 'scopes:' ] + scopes) sigStr = '\n'.join(sig).encode() if isinstance(accessToken, six.text_type): accessToken = accessToken.encode() sig = hmac.new(accessToken, sigStr, hashlib.sha256).digest() cert['signature'] = utils.encodeStringForB64Header(sig) newToken = hmac.new(accessToken, cert['seed'], hashlib.sha256).digest() newToken = utils.makeB64UrlSafe( utils.encodeStringForB64Header(newToken)).replace(b'=', b'') return { 'clientId': name or clientId, 'accessToken': newToken, 'certificate': utils.dumpJson(cert), }
def createApiClient(name, api): api = api['reference'] attributes = dict( name=name, __doc__=api.get('description'), classOptions={}, funcinfo={}, ) # apply a default for apiVersion; this can be removed when all services # have apiVersion if 'apiVersion' not in api: api['apiVersion'] = 'v1' copiedOptions = ('exchangePrefix', ) for opt in copiedOptions: if opt in api: attributes['classOptions'][opt] = api[opt] copiedProperties = ('serviceName', 'apiVersion') for opt in copiedProperties: if opt in api: attributes[opt] = api[opt] for entry in api['entries']: if entry['type'] == 'function': def addApiCall(e): def apiCall(self, *args, **kwargs): return self._makeApiCall(e, *args, **kwargs) return apiCall f = addApiCall(entry) docStr = "Call the %s api's %s method. " % (name, entry['name']) if entry['args'] and len(entry['args']) > 0: docStr += "This method takes:\n\n" docStr += '\n'.join(['- ``%s``' % x for x in entry['args']]) docStr += '\n\n' else: docStr += "This method takes no arguments. " if 'input' in entry: docStr += "This method takes input ``%s``. " % entry['input'] if 'output' in entry: docStr += "This method gives output ``%s``" % entry['output'] docStr += '\n\nThis method does a ``%s`` to ``%s``.' % ( entry['method'].upper(), entry['route']) f.__doc__ = docStr attributes['funcinfo'][entry['name']] = entry elif entry['type'] == 'topic-exchange': def addTopicExchange(e): def topicExchange(self, *args, **kwargs): return self._makeTopicExchange(e, *args, **kwargs) return topicExchange f = addTopicExchange(entry) docStr = 'Generate a routing key pattern for the %s exchange. ' % entry[ 'exchange'] docStr += 'This method takes a given routing key as a string or a ' docStr += 'dictionary. For each given dictionary key, the corresponding ' docStr += 'routing key token takes its value. For routing key tokens ' docStr += 'which are not specified by the dictionary, the * or # character ' docStr += 'is used depending on whether or not the key allows multiple words.\n\n' docStr += 'This exchange takes the following keys:\n\n' docStr += '\n'.join( ['- ``%s``' % x['name'] for x in entry['routingKey']]) f.__doc__ = docStr # Add whichever function we created f.__name__ = str(entry['name']) attributes[entry['name']] = f return type(utils.toStr(name), (BaseClient, ), attributes)
def buildSignedUrl(self, methodName, *args, **kwargs): """ Build a signed URL. This URL contains the credentials needed to access a resource.""" if 'expiration' in kwargs: expiration = kwargs['expiration'] del kwargs['expiration'] else: expiration = self.options['signedUrlExpiration'] expiration = int( time.time() + expiration) # Mainly so that we throw if it's not a number requestUrl = self.buildUrl(methodName, *args, **kwargs) if not self._hasCredentials(): raise exceptions.TaskclusterAuthFailure('Invalid Hawk Credentials') clientId = utils.toStr(self.options['credentials']['clientId']) accessToken = utils.toStr(self.options['credentials']['accessToken']) def genBewit(): # We need to fix the output of get_bewit. It returns a url-safe base64 # encoded string, which contains a list of tokens separated by '\'. # The first one is the clientId, the second is an int, the third is # url-safe base64 encoded MAC, the fourth is the ext param. # The problem is that the nested url-safe base64 encoded MAC must be # base64 (i.e. not url safe) or server-side will complain. # id + '\\' + exp + '\\' + mac + '\\' + options.ext; resource = mohawk.base.Resource( credentials={ 'id': clientId, 'key': accessToken, 'algorithm': 'sha256', }, method='GET', ext=utils.toStr(self.makeHawkExt()), url=requestUrl, timestamp=expiration, nonce='', # content='', # content_type='', ) bewit = mohawk.bewit.get_bewit(resource) return bewit.rstrip('=') bewit = genBewit() if not bewit: raise exceptions.TaskclusterFailure('Did not receive a bewit') u = urllib.parse.urlparse(requestUrl) qs = u.query if qs: qs += '&' qs += 'bewit=%s' % bewit return urllib.parse.urlunparse(( u.scheme, u.netloc, u.path, u.params, qs, u.fragment, ))
def createTemporaryCredentials(clientId, accessToken, start, expiry, scopes, name=None): """ Create a set of temporary credentials Callers should not apply any clock skew; clock drift is accounted for by auth service. clientId: the issuing clientId accessToken: the issuer's accessToken start: start time of credentials (datetime.datetime) expiry: expiration time of credentials, (datetime.datetime) scopes: list of scopes granted name: credential name (optional) Returns a dictionary in the form: { 'clientId': str, 'accessToken: str, 'certificate': str} """ for scope in scopes: if not isinstance(scope, six.string_types): raise exceptions.TaskclusterFailure('Scope must be string') # Credentials can only be valid for 31 days. I hope that # this is validated on the server somehow... if expiry - start > datetime.timedelta(days=31): raise exceptions.TaskclusterFailure('Only 31 days allowed') # We multiply times by 1000 because the auth service is JS and as a result # uses milliseconds instead of seconds cert = dict( version=1, scopes=scopes, start=calendar.timegm(start.utctimetuple()) * 1000, expiry=calendar.timegm(expiry.utctimetuple()) * 1000, seed=utils.slugId().encode('ascii') + utils.slugId().encode('ascii'), ) # if this is a named temporary credential, include the issuer in the certificate if name: cert['issuer'] = utils.toStr(clientId) sig = ['version:' + utils.toStr(cert['version'])] if name: sig.extend([ 'clientId:' + utils.toStr(name), 'issuer:' + utils.toStr(clientId), ]) sig.extend([ 'seed:' + utils.toStr(cert['seed']), 'start:' + utils.toStr(cert['start']), 'expiry:' + utils.toStr(cert['expiry']), 'scopes:' ] + scopes) sigStr = '\n'.join(sig).encode() if isinstance(accessToken, six.text_type): accessToken = accessToken.encode() sig = hmac.new(accessToken, sigStr, hashlib.sha256).digest() cert['signature'] = utils.encodeStringForB64Header(sig) newToken = hmac.new(accessToken, cert['seed'], hashlib.sha256).digest() newToken = utils.makeB64UrlSafe(utils.encodeStringForB64Header(newToken)).replace(b'=', b'') return { 'clientId': name or clientId, 'accessToken': newToken, 'certificate': utils.dumpJson(cert), }
def createApiClient(name, api): api = api['reference'] attributes = dict( name=name, __doc__=api.get('description'), classOptions={}, funcinfo={}, ) # apply a default for apiVersion; this can be removed when all services # have apiVersion if 'apiVersion' not in api: api['apiVersion'] = 'v1' copiedOptions = ('exchangePrefix',) for opt in copiedOptions: if opt in api: attributes['classOptions'][opt] = api[opt] copiedProperties = ('serviceName', 'apiVersion') for opt in copiedProperties: if opt in api: attributes[opt] = api[opt] for entry in api['entries']: if entry['type'] == 'function': def addApiCall(e): def apiCall(self, *args, **kwargs): return self._makeApiCall(e, *args, **kwargs) return apiCall f = addApiCall(entry) docStr = "Call the %s api's %s method. " % (name, entry['name']) if entry['args'] and len(entry['args']) > 0: docStr += "This method takes:\n\n" docStr += '\n'.join(['- ``%s``' % x for x in entry['args']]) docStr += '\n\n' else: docStr += "This method takes no arguments. " if 'input' in entry: docStr += "This method takes input ``%s``. " % entry['input'] if 'output' in entry: docStr += "This method gives output ``%s``" % entry['output'] docStr += '\n\nThis method does a ``%s`` to ``%s``.' % ( entry['method'].upper(), entry['route']) f.__doc__ = docStr attributes['funcinfo'][entry['name']] = entry elif entry['type'] == 'topic-exchange': def addTopicExchange(e): def topicExchange(self, *args, **kwargs): return self._makeTopicExchange(e, *args, **kwargs) return topicExchange f = addTopicExchange(entry) docStr = 'Generate a routing key pattern for the %s exchange. ' % entry['exchange'] docStr += 'This method takes a given routing key as a string or a ' docStr += 'dictionary. For each given dictionary key, the corresponding ' docStr += 'routing key token takes its value. For routing key tokens ' docStr += 'which are not specified by the dictionary, the * or # character ' docStr += 'is used depending on whether or not the key allows multiple words.\n\n' docStr += 'This exchange takes the following keys:\n\n' docStr += '\n'.join(['- ``%s``' % x['name'] for x in entry['routingKey']]) f.__doc__ = docStr # Add whichever function we created f.__name__ = str(entry['name']) attributes[entry['name']] = f return type(utils.toStr(name), (BaseClient,), attributes)