def test_get_token_secret_is_deprecated(self): token = tokenlib.make_token({"hello": "world"}) with warnings.catch_warnings(record=True) as w: warnings.simplefilter("default") tokenlib.get_token_secret(token) self.assertEquals(len(w), 1) self.assertEquals(w[0].category, DeprecationWarning)
def test_convenience_functions(self): token = tokenlib.make_token({"hello": "world"}) self.assertEquals(tokenlib.parse_token(token)["hello"], "world") self.assertRaises(ValueError, tokenlib.parse_token, token, secret="X") self.assertEquals(tokenlib.get_token_secret(token), tokenlib.get_token_secret(token)) self.assertNotEquals(tokenlib.get_token_secret(token), tokenlib.get_token_secret(token, secret="X"))
def test_that_macauth_can_use_per_node_hostname_secrets(self): with tempfile.NamedTemporaryFile() as sf: # Write some secrets to a file. sf.write("host1,0001:secret11,0002:secret12\n") sf.write("host2,0001:secret21,0002:secret22\n") sf.flush() # Configure the plugin to load them. config2 = pyramid.testing.setUp() config2.add_settings(self.DEFAULT_SETTINGS) config2.add_settings({ "who.plugin.macauth.secrets_file": sf.name, }) config2.include("mozsvc.user.whoauth") config2.commit() # It should accept a request signed with the old secret on host1. req = self._make_request(config=config2, environ={ "HTTP_HOST": "host1", }) id = tokenlib.make_token({"userid": 42}, secret="secret11") key = tokenlib.get_token_secret(id, secret="secret11") macauthlib.sign_request(req, id, key) self.assertEquals(authenticated_userid(req), 42) # It should accept a request signed with the new secret on host1. req = self._make_request(config=config2, environ={ "HTTP_HOST": "host1", }) id = tokenlib.make_token({"userid": 42}, secret="secret12") key = tokenlib.get_token_secret(id, secret="secret12") macauthlib.sign_request(req, id, key) self.assertEquals(authenticated_userid(req), 42) # It should reject a request signed with secret from other host. req = self._make_request(config=config2, environ={ "HTTP_HOST": "host2", }) id = tokenlib.make_token({"userid": 42}, secret="secret12") key = tokenlib.get_token_secret(id, secret="secret12") macauthlib.sign_request(req, id, key) self.assertRaises(Exception, authenticated_userid, req) # It should accept a request signed with the new secret on host2. req = self._make_request(config=config2, environ={ "HTTP_HOST": "host2", }) id = tokenlib.make_token({"userid": 42}, secret="secret22") key = tokenlib.get_token_secret(id, secret="secret22") macauthlib.sign_request(req, id, key) self.assertEquals(authenticated_userid(req), 42) # It should reject unknown hostnames. req = self._make_request(config=config2, environ={ "HTTP_HOST": "host3", }) id = tokenlib.make_token({"userid": 42}, secret="secret12") key = tokenlib.get_token_secret(id, secret="secret12") macauthlib.sign_request(req, id, key) self.assertRaises(Exception, authenticated_userid, req)
def send_job(repo, server, cycles="", duration="", nodes="", redirect_url=""): mac_user = os.environ.get("MACAUTH_USER") mac_secret = os.environ.get("MACAUTH_SECRET") request = Request.blank(server.rstrip("/") + "/test") request.method = "POST" params = { "repo": repo, "cycles": cycles, "duration": duration, "nodes": nodes, "redirect_url": redirect_url, "api_call": 1, } request.body = urllib.urlencode(params) request.environ["CONTENT_TYPE"] = "application/x-www-form-urlencoded" if mac_user is not None: tokenid = tokenlib.make_token({"user": mac_user}, secret=mac_secret) key = tokenlib.get_token_secret(tokenid, secret=mac_secret) sign_request(request, tokenid, key) resp = request.get_response(proxy_exact_request) if resp.status_int == 401: raise ValueError("Authorization Failed!") job_id = resp.json["job_id"] return server.rstrip("/") + "/test/" + job_id
def decode_mac_id(self, request, tokenid): """Decode a MACAuth token id into its userid and MAC secret key. This method determines the appropriate secrets to use for the given request, then passes them on to tokenlib to handle the given MAC id token. If the id is invalid then ValueError will be raised. """ # There might be multiple secrets in use, if we're in the # process of transitioning from one to another. Try each # until we find one that works. secrets = self._get_token_secrets(request) for secret in secrets: try: data = tokenlib.parse_token(tokenid, secret=secret) userid = data["uid"] key = tokenlib.get_token_secret(tokenid, secret=secret) break except (ValueError, KeyError): pass else: log_cef("Authentication Failed: invalid MAC id", 5, request.environ, request.registry.settings, "", signature=AUTH_FAILURE) raise ValueError("invalid MAC id") return userid, key
def return_token(request): # at this stage, we are sure that the assertion, application and version # number were valid, so let's build the authentication token and return it. backend = request.registry.getUtility(INodeAssignment) email = request.validated['assertion']['email'] application = request.validated['application'] version = request.validated['version'] pattern = request.validated['pattern'] service = get_service_name(application, version) # get the node or allocate one if none is already set uid, node = backend.get_node(email, service) if node is None or uid is None: uid, node = backend.allocate_node(email, service) secrets = request.registry.settings['tokenserver.secrets_file'] node_secrets = secrets.get(node) if not node_secrets: raise Exception("The specified node does not have any shared secret") secret = node_secrets[-1] # the last one is the most recent one token = make_token({'uid': uid, 'service_entry': node}, secret=secret) # XXX needs to be renamed as 'get_derived_secret' because # it's not clear here it's a derived secret = get_token_secret(token, secret=secret) api_endpoint = pattern.format(uid=uid, service=service, node=node) # FIXME add the algo used to generate the token return {'id': token, 'key': secret, 'uid': uid, 'api_endpoint': api_endpoint}
def send_job(repo, server, cycles='', duration='', nodes='', redirect_url=''): mac_user = os.environ.get('MACAUTH_USER') mac_secret = os.environ.get('MACAUTH_SECRET') request = Request.blank(server.rstrip('/') + '/test') request.method = 'POST' params = { 'repo': repo, 'cycles': cycles, 'duration': duration, 'nodes': nodes, 'redirect_url': redirect_url, 'api_call': 1 } request.body = urllib.urlencode(params) request.environ['CONTENT_TYPE'] = 'application/x-www-form-urlencoded' if mac_user is not None: tokenid = tokenlib.make_token({"user": mac_user}, secret=mac_secret) key = tokenlib.get_token_secret(tokenid, secret=mac_secret) sign_request(request, tokenid, key) resp = request.get_response(proxy_exact_request) if resp.status_int == 401: raise ValueError("Authorization Failed!") job_id = resp.json['job_id'] return server.rstrip('/') + '/test/' + job_id
def decode_mac_id(self, request, id): """Decode MAC id into MAC key and data dict. This method decodes the given MAC id to give the corresponding MAC secret key and dict of user data. By default it uses the tokenlib library, but plugin instances may override this method with another callable from the config file. If the MAC id is invalid then ValueError will be raised. """ secret = tokenlib.get_token_secret(id) data = tokenlib.parse_token(id) return secret, data
def encode_mac_id(self, request, userid=None, **data): """Encode the given userid into a MACAuth token id and secret key. This method is essentially the reverse of decode_mac_id. Given a userid, it returns a MACAuth id and corresponding secret key. It is not needed for consuming authentication tokens, but is very useful when building them for testing purposes. """ if userid is not None: data["userid"] = userid tokenid = tokenlib.make_token(data, secret=self.master_secret) secret = tokenlib.get_token_secret(tokenid, secret=self.master_secret) return tokenid, secret
def encode_mac_id(self, request, userid): """Encode the given userid into a MAC id and secret key. This method is essentially the reverse of decode_mac_id. It is not needed for consuming authentication tokens, but is very useful when building them for testing purposes. """ # There might be multiple secrets in use, if we're in the # process of transitioning from one to another. Always use # the last one aka the "most recent" secret. secret = self._get_token_secrets(request)[-1] tokenid = tokenlib.make_token({"uid": userid}, secret=secret) key = tokenlib.get_token_secret(tokenid, secret=secret) return tokenid, key
def encode_hawk_id(self, request, data): """Encode data dict into Hawk id token and secret key. This method is essentially the reverse of decode_hawk_id. Given a dict of identity data, it encodes it into a unique Hawk id token and corresponding secret key. By default it uses the tokenlib library, but plugin instances may override this method with another callable the config file. This method is not needed when consuming auth tokens, but is very handy when building them for testing purposes. """ id = tokenlib.make_token(data, secret=self.master_secret) secret = tokenlib.get_token_secret(id, secret=self.master_secret) return id, secret
def decode_mac_id(self, request, tokenid): """Decode a MACAuth token id into its userid and MAC secret key. This method decodes the given MAC token id to give the corresponding userid and MAC secret key. It is a simple default implementation using the tokenlib library, and can be overridden by passing a callable into the MACAuthenticationPolicy constructor. If the MAC token id is invalid then ValueError will be raised. """ secret = tokenlib.get_token_secret(tokenid, secret=self.master_secret) data = tokenlib.parse_token(tokenid, secret=self.master_secret) userid = None for key in ("username", "userid", "uid", "email"): userid = data.get(key) if userid is not None: break else: msg = "MAC id contains no userid" raise self.challenge(request, msg) return userid, secret
def decode_mac_id(self, request, tokenid): """Decode a MACAuth token id into its userid and MAC secret key. This method decodes the given MAC token id to give the corresponding userid and MAC secret key. It is a simple default implementation using the tokenlib library, and can be overridden by passing a callable into the MACAuthenticationPolicy constructor. If the MAC token id is invalid then ValueError will be raised. """ secret = tokenlib.get_token_secret(tokenid) data = tokenlib.parse_token(tokenid) userid = None for key in ("username", "userid", "uid", "email"): userid = data.get(key) if userid is not None: break else: msg = "MAC id contains no userid" raise self.challenge(request, msg) return userid, secret
def send_job(repo, server, cycles='', duration='', nodes='', redirect_url=''): mac_user = os.environ.get('MACAUTH_USER') mac_secret = os.environ.get('MACAUTH_SECRET') request = Request.blank(server.rstrip('/') + '/test') request.method = 'POST' params = {'repo': repo, 'cycles': cycles, 'duration': duration, 'nodes': nodes, 'redirect_url': redirect_url, 'api_call': 1} request.body = urllib.urlencode(params) request.environ['CONTENT_TYPE'] = 'application/x-www-form-urlencoded' if mac_user is not None: tokenid = tokenlib.make_token({"user": mac_user}, secret=mac_secret) key = tokenlib.get_token_secret(tokenid, secret=mac_secret) sign_request(request, tokenid, key) resp = request.get_response(proxy_exact_request) if resp.status_int == 401: raise ValueError("Authorization Failed!") job_id = resp.json['job_id'] return server.rstrip('/') + '/test/' + job_id
def decode_mac_id(self, request, id): """Decode the MAC id into its secret key and dict of user data. This method determines the appropriate secrets to use for the given request, then passes them on to tokenlib to handle the given MAC id token. If the id is invalid then ValueError will be raised. """ # There might be multiple secrets in use, if we're in the # process of transitioning from one to another. Try each # until we find one that works. secrets = self._get_token_secrets(request) for secret in secrets: try: data = tokenlib.parse_token(id, secret=secret) key = tokenlib.get_token_secret(id, secret=secret) break except ValueError: pass else: raise ValueError("invalid MAC id") return key, data
def test_that_macauth_can_use_per_node_hostname_secrets(self): with tempfile.NamedTemporaryFile() as sf: # Write some secrets to a file. sf.write("http://host1.com,0001:secret11,0002:secret12\n") sf.write("https://host2.com,0001:secret21,0002:secret22\n") sf.write("https://host3.com:444,0001:secret31,0002:secret32\n") sf.flush() # Configure the plugin to load them. config2 = pyramid.testing.setUp() config2.add_settings(DEFAULT_SETTINGS) config2.add_settings({ "macauth.secrets_file": sf.name, }) config2.include("mozsvc.user") config2.commit() # It should accept a request signed with the old secret on host1. req = self.make_request(config=config2, environ={ "HTTP_HOST": "host1.com", }) id = tokenlib.make_token({"uid": 42}, secret="secret11") key = tokenlib.get_token_secret(id, secret="secret11") macauthlib.sign_request(req, id, key) self.assertEquals(authenticated_userid(req), 42) # It should accept a request signed with the new secret on host1. req = self.make_request(config=config2, environ={ "HTTP_HOST": "host1.com", }) id = tokenlib.make_token({"uid": 42}, secret="secret12") key = tokenlib.get_token_secret(id, secret="secret12") macauthlib.sign_request(req, id, key) self.assertEquals(authenticated_userid(req), 42) # It should reject a request signed with secret from other host. req = self.make_request(config=config2, environ={ "HTTP_HOST": "host2.com", }) id = tokenlib.make_token({"uid": 42}, secret="secret12") key = tokenlib.get_token_secret(id, secret="secret12") macauthlib.sign_request(req, id, key) self.assertRaises(HTTPUnauthorized, authenticated_userid, req) # It should reject a request over plain http when host2 is ssl. req = self.make_request(config=config2, environ={ "HTTP_HOST": "host2.com", }) id = tokenlib.make_token({"uid": 42}, secret="secret22") key = tokenlib.get_token_secret(id, secret="secret22") macauthlib.sign_request(req, id, key) self.assertRaises(HTTPUnauthorized, authenticated_userid, req) # It should accept a request signed with the new secret on host2. req = self.make_request(config=config2, environ={ "HTTP_HOST": "host2.com", "wsgi.url_scheme": "https", }) id = tokenlib.make_token({"uid": 42}, secret="secret22") key = tokenlib.get_token_secret(id, secret="secret22") macauthlib.sign_request(req, id, key) self.assertEquals(authenticated_userid(req), 42) # It should accept a request to host1 with an explicit port number. # Use some trickery to give host_url a value with default port. req = ExpandoRequest(self.make_request(config=config2, environ={ "HTTP_HOST": "host1.com:80", "wsgi.url_scheme": "http", })) req.host_url = "http://host1.com:80" id = tokenlib.make_token({"uid": 42}, secret="secret11") key = tokenlib.get_token_secret(id, secret="secret11") macauthlib.sign_request(req, id, key) self.assertEquals(authenticated_userid(req), 42) # It should accept a request to host2 with an explicit port number. # Use some trickery to give host_url a value with default port. req = ExpandoRequest(self.make_request(config=config2, environ={ "HTTP_HOST": "host2.com:443", "wsgi.url_scheme": "https", })) req.host_url = "https://host2.com:443" id = tokenlib.make_token({"uid": 42}, secret="secret22") key = tokenlib.get_token_secret(id, secret="secret22") macauthlib.sign_request(req, id, key) self.assertEquals(authenticated_userid(req), 42) # It should accept a request to host3 on a custom port. req = self.make_request(config=config2, environ={ "HTTP_HOST": "host3.com:444", "wsgi.url_scheme": "https", }) id = tokenlib.make_token({"uid": 42}, secret="secret32") key = tokenlib.get_token_secret(id, secret="secret32") macauthlib.sign_request(req, id, key) self.assertEquals(authenticated_userid(req), 42) # It should reject unknown hostnames. req = self.make_request(config=config2, environ={ "HTTP_HOST": "host4.com", }) id = tokenlib.make_token({"uid": 42}, secret="secret12") key = tokenlib.get_token_secret(id, secret="secret12") macauthlib.sign_request(req, id, key) self.assertRaises(HTTPUnauthorized, authenticated_userid, req)
def test_that_macauth_can_use_per_node_hostname_secrets(self): with tempfile.NamedTemporaryFile() as sf: # Write some secrets to a file. sf.write("http://host1.com,0001:secret11,0002:secret12\n") sf.write("https://host2.com,0001:secret21,0002:secret22\n") sf.write("https://host3.com:444,0001:secret31,0002:secret32\n") sf.flush() # Configure the plugin to load them. config2 = pyramid.testing.setUp() config2.add_settings(DEFAULT_SETTINGS) config2.add_settings({ "macauth.secrets_file": sf.name, }) config2.include("mozsvc.user") config2.commit() # It should accept a request signed with the old secret on host1. req = self.make_request(config=config2, environ={ "HTTP_HOST": "host1.com", }) id = tokenlib.make_token({"uid": 42}, secret="secret11") key = tokenlib.get_token_secret(id, secret="secret11") macauthlib.sign_request(req, id, key) self.assertEquals(authenticated_userid(req), 42) # It should accept a request signed with the new secret on host1. req = self.make_request(config=config2, environ={ "HTTP_HOST": "host1.com", }) id = tokenlib.make_token({"uid": 42}, secret="secret12") key = tokenlib.get_token_secret(id, secret="secret12") macauthlib.sign_request(req, id, key) self.assertEquals(authenticated_userid(req), 42) # It should reject a request signed with secret from other host. req = self.make_request(config=config2, environ={ "HTTP_HOST": "host2.com", }) id = tokenlib.make_token({"uid": 42}, secret="secret12") key = tokenlib.get_token_secret(id, secret="secret12") macauthlib.sign_request(req, id, key) self.assertRaises(HTTPUnauthorized, authenticated_userid, req) # It should reject a request over plain http when host2 is ssl. req = self.make_request(config=config2, environ={ "HTTP_HOST": "host2.com", }) id = tokenlib.make_token({"uid": 42}, secret="secret22") key = tokenlib.get_token_secret(id, secret="secret22") macauthlib.sign_request(req, id, key) self.assertRaises(HTTPUnauthorized, authenticated_userid, req) # It should accept a request signed with the new secret on host2. req = self.make_request(config=config2, environ={ "HTTP_HOST": "host2.com", "wsgi.url_scheme": "https", }) id = tokenlib.make_token({"uid": 42}, secret="secret22") key = tokenlib.get_token_secret(id, secret="secret22") macauthlib.sign_request(req, id, key) self.assertEquals(authenticated_userid(req), 42) # It should accept a request to host1 with an explicit port number. # Use some trickery to give host_url a value with default port. req = ExpandoRequest( self.make_request(config=config2, environ={ "HTTP_HOST": "host1.com:80", "wsgi.url_scheme": "http", })) req.host_url = "http://host1.com:80" id = tokenlib.make_token({"uid": 42}, secret="secret11") key = tokenlib.get_token_secret(id, secret="secret11") macauthlib.sign_request(req, id, key) self.assertEquals(authenticated_userid(req), 42) # It should accept a request to host2 with an explicit port number. # Use some trickery to give host_url a value with default port. req = ExpandoRequest( self.make_request(config=config2, environ={ "HTTP_HOST": "host2.com:443", "wsgi.url_scheme": "https", })) req.host_url = "https://host2.com:443" id = tokenlib.make_token({"uid": 42}, secret="secret22") key = tokenlib.get_token_secret(id, secret="secret22") macauthlib.sign_request(req, id, key) self.assertEquals(authenticated_userid(req), 42) # It should accept a request to host3 on a custom port. req = self.make_request(config=config2, environ={ "HTTP_HOST": "host3.com:444", "wsgi.url_scheme": "https", }) id = tokenlib.make_token({"uid": 42}, secret="secret32") key = tokenlib.get_token_secret(id, secret="secret32") macauthlib.sign_request(req, id, key) self.assertEquals(authenticated_userid(req), 42) # It should reject unknown hostnames. req = self.make_request(config=config2, environ={ "HTTP_HOST": "host4.com", }) id = tokenlib.make_token({"uid": 42}, secret="secret12") key = tokenlib.get_token_secret(id, secret="secret12") macauthlib.sign_request(req, id, key) self.assertRaises(HTTPUnauthorized, authenticated_userid, req)
def _get_credentials(self, **data): id = tokenlib.make_token(data) key = tokenlib.get_token_secret(id) return {"id": id, "key": key}
queue = request.registry['queue'] # extracting the user try: decoded_token = b64decode(tokenid) except TypeError, e: raise ValueError(str(e)) payload = decoded_token[:-hashlib.sha1().digest_size] data = json.loads(payload) user = data['user'] # getting the associated secret secret = queue.get_key(user) # now we can parse the token and make sure we're good tsecret = tokenlib.get_token_secret(tokenid, secret=secret) data = tokenlib.parse_token(tokenid, secret=secret) return user, tsecret class ImportStringError(ImportError): """Provides information about a failed :func:`import_string` attempt.""" #: String in dotted notation that failed to be imported. import_name = None #: Wrapped exception. exception = None def __init__(self, import_name, exception): self.import_name = import_name self.exception = exception
def return_token(request): """This service does the following process: - validates the Browser-ID assertion provided on the Authorization header - allocates when necessary a node to the user for the required service - deals with the X-Conditions-Accepted header - returns a JSON mapping containing the following values: - **id** -- a signed authorization token, containing the user's id for hthe application and the node. - **secret** -- a secret derived from the shared secret - **uid** -- the user id for this servic - **api_endpoint** -- the root URL for the user for the service. """ # at this stage, we are sure that the assertion, application and version # number were valid, so let's build the authentication token and return it. backend = request.registry.getUtility(INodeAssignment) email = request.validated['assertion']['email'] application = request.validated['application'] version = request.validated['version'] pattern = request.validated['pattern'] service = get_service_name(application, version) accepted = request.validated['x-conditions-accepted'] # get the node or allocate one if none is already set uid, node, to_accept = backend.get_node(email, service) if to_accept is not None: # the backend sent a tos url, meaning the user needs to # sign it, we want to compare both tos and raise a 403 # if they are not equal if not accepted: to_accept = dict([(name, value) for name, value, __ in to_accept]) raise json_error(403, location='header', description='Need to accept conditions', name='X-Conditions-Accepted', condition_urls=to_accept) # at this point, either the tos were signed or the service does not # have any ToS if node is None or uid is None: metlog = request.registry['metlog'] start = time.time() try: uid, node = backend.allocate_node(email, service) finally: duration = time.time() - start metlog.timer_send("token.sql.allocate_node", duration) secrets = request.registry.settings['tokenserver.secrets_file'] node_secrets = secrets.get(node) if not node_secrets: raise Exception("The specified node does not have any shared secret") secret = node_secrets[-1] # the last one is the most recent one token_duration = request.registry.settings.get( 'tokenserver.token_duration', DEFAULT_TOKEN_DURATION) token = make_token({'uid': uid, 'service_entry': node}, timeout=token_duration, secret=secret) # XXX needs to be renamed as 'get_derived_secret' because # it's not clear here it's a derived secret = get_token_secret(token, secret=secret) api_endpoint = pattern.format(uid=uid, service=service, node=node) # FIXME add the algo used to generate the token return {'id': token, 'key': secret, 'uid': uid, 'api_endpoint': api_endpoint, 'duration': token_duration}