)]}'
{"keystonemiddleware/auth_token/__init__.py":[{"author":{"_account_id":2903,"name":"Morgan Fainberg","email":"morgan.fainberg@gmail.com","username":"mdrnstm"},"change_message_id":"cc4370b9084ef4a238144db978362dde92b01051","unresolved":false,"context_lines":[{"line_number":522,"context_line":"                self._invalid_user_token()"},{"line_number":523,"context_line":""},{"line_number":524,"context_line":"    def _path_matches(self, request_path, path_pattern):"},{"line_number":525,"context_line":"        # The fnmatch module doesn\u0027t provide the ability to match * versus **,"},{"line_number":526,"context_line":"        # so convert to regex."},{"line_number":527,"context_line":"        # replace {tags} with *"},{"line_number":528,"context_line":"        pattern \u003d r\u0027{[^}]*}\u0027"}],"source_content_type":"text/x-python","patch_set":16,"id":"7faddb67_824ca742","line":525,"range":{"start_line":525,"start_character":10,"end_line":525,"end_character":77},"updated":"2019-07-12 16:34:28.000000000","message":"That is annoying. This would be a lot easier to read/maintain without the need to convert to regex.","commit_id":"da57af63f5855688f08015b0dc76e114a99bf266"},{"author":{"_account_id":2903,"name":"Morgan Fainberg","email":"morgan.fainberg@gmail.com","username":"mdrnstm"},"change_message_id":"cc4370b9084ef4a238144db978362dde92b01051","unresolved":false,"context_lines":[{"line_number":525,"context_line":"        # The fnmatch module doesn\u0027t provide the ability to match * versus **,"},{"line_number":526,"context_line":"        # so convert to regex."},{"line_number":527,"context_line":"        # replace {tags} with *"},{"line_number":528,"context_line":"        pattern \u003d r\u0027{[^}]*}\u0027"},{"line_number":529,"context_line":"        replace \u003d r\u0027*\u0027"},{"line_number":530,"context_line":"        path_regex \u003d re.sub(pattern, replace, path_pattern)"},{"line_number":531,"context_line":"        # temporarily sub out **"},{"line_number":532,"context_line":"        pattern \u003d r\u0027([^\\*]*)\\*\\*([^\\*]*)\u0027"},{"line_number":533,"context_line":"        replace \u003d r\u0027\\1%TMP%\\2\u0027"}],"source_content_type":"text/x-python","patch_set":16,"id":"7faddb67_021677a9","line":530,"range":{"start_line":528,"start_character":7,"end_line":530,"end_character":59},"updated":"2019-07-12 16:34:28.000000000","message":"Looking at a contrived example \u0027/xxxx/{project_id}/yyyy\u0027\n\nDo we really want to go to a *? This seems to be a broken substitution, wouldn\u0027t this become /xxxx/*/yyyy, which would mean:\n\n/xxxx matches the characters /xxxx literally (case sensitive)\n\n/* matches the character / literally (case sensitive)\n* Quantifier — Matches between zero and unlimited times, as many times as possible, giving back as needed (greedy)\n\n/yyyy matches the characters /yyyy literally (case sensitive)\n\n\nNow in the case of \u0027/v1/servers/{server_id}\u0027: \n\n/v1/servers matches the characters /v1/servers literally (case sensitive)\n\n/* matches the character / literally (case sensitive)\n* Quantifier — Matches between zero and unlimited times, as many times as possible, giving back as needed (greedy)\n\n\nThis means we would match:\n/v1/servers\n/v1/servers/\n/v1/servers//////////\n/v1/servers/xxxxxxx\n/v1/servers///////xxxxxx\n\nIs this intended to allow for the first 3?","commit_id":"da57af63f5855688f08015b0dc76e114a99bf266"},{"author":{"_account_id":8482,"name":"Colleen Murphy","email":"colleen@gazlene.net","username":"krinkle"},"change_message_id":"f5133e1c12a6d58b9dc372d97cfe9cc1a61c48e1","unresolved":false,"context_lines":[{"line_number":525,"context_line":"        # The fnmatch module doesn\u0027t provide the ability to match * versus **,"},{"line_number":526,"context_line":"        # so convert to regex."},{"line_number":527,"context_line":"        # replace {tags} with *"},{"line_number":528,"context_line":"        pattern \u003d r\u0027{[^}]*}\u0027"},{"line_number":529,"context_line":"        replace \u003d r\u0027*\u0027"},{"line_number":530,"context_line":"        path_regex \u003d re.sub(pattern, replace, path_pattern)"},{"line_number":531,"context_line":"        # temporarily sub out **"},{"line_number":532,"context_line":"        pattern \u003d r\u0027([^\\*]*)\\*\\*([^\\*]*)\u0027"},{"line_number":533,"context_line":"        replace \u003d r\u0027\\1%TMP%\\2\u0027"}],"source_content_type":"text/x-python","patch_set":16,"id":"7faddb67_8d4a97a9","line":530,"range":{"start_line":528,"start_character":7,"end_line":530,"end_character":59},"in_reply_to":"7faddb67_021677a9","updated":"2019-07-12 21:51:43.000000000","message":"This stage is just meant to convert the {tag} syntax, which is a convenience for readability, to *, which has equivalent meaning from the user\u0027s point of view. So at the end of this stage, path_pattern will be a string (not a regex) that would behave as if the user had actually used a glob-style * rather than the tag-style {tag}. It\u0027s just normalizing the string for the next stage, where * and ** are actually converted to a regex format.\n\nIn your example, if we have /v1/servers/{server_id}, at the end of line 530 we have path_regex (now I see it is badly named) containing /v1/servers/*, and is just a string, ready to be further processed in the next few stages which will convert the string \u0027*\u0027 to the regex /.*/.","commit_id":"da57af63f5855688f08015b0dc76e114a99bf266"},{"author":{"_account_id":8482,"name":"Colleen Murphy","email":"colleen@gazlene.net","username":"krinkle"},"change_message_id":"0b0c3538bae33448279c4988bc7c1aad3caa6d74","unresolved":false,"context_lines":[{"line_number":525,"context_line":"        # The fnmatch module doesn\u0027t provide the ability to match * versus **,"},{"line_number":526,"context_line":"        # so convert to regex."},{"line_number":527,"context_line":"        # replace {tags} with *"},{"line_number":528,"context_line":"        pattern \u003d r\u0027{[^}]*}\u0027"},{"line_number":529,"context_line":"        replace \u003d r\u0027*\u0027"},{"line_number":530,"context_line":"        path_regex \u003d re.sub(pattern, replace, path_pattern)"},{"line_number":531,"context_line":"        # temporarily sub out **"},{"line_number":532,"context_line":"        pattern \u003d r\u0027([^\\*]*)\\*\\*([^\\*]*)\u0027"},{"line_number":533,"context_line":"        replace \u003d r\u0027\\1%TMP%\\2\u0027"}],"source_content_type":"text/x-python","patch_set":16,"id":"7faddb67_7a525a3d","line":530,"range":{"start_line":528,"start_character":7,"end_line":530,"end_character":59},"in_reply_to":"7faddb67_1391e847","updated":"2019-07-15 23:06:31.000000000","message":"I see what you mean now, I think it should not be allowed to make a request /v1//xxxx that omits the component named by the tag, and have incorporated this into the next patchset.","commit_id":"da57af63f5855688f08015b0dc76e114a99bf266"},{"author":{"_account_id":2903,"name":"Morgan Fainberg","email":"morgan.fainberg@gmail.com","username":"mdrnstm"},"change_message_id":"eb204ee83f037ee92caacfe4d98d147450a6d68a","unresolved":false,"context_lines":[{"line_number":525,"context_line":"        # The fnmatch module doesn\u0027t provide the ability to match * versus **,"},{"line_number":526,"context_line":"        # so convert to regex."},{"line_number":527,"context_line":"        # replace {tags} with *"},{"line_number":528,"context_line":"        pattern \u003d r\u0027{[^}]*}\u0027"},{"line_number":529,"context_line":"        replace \u003d r\u0027*\u0027"},{"line_number":530,"context_line":"        path_regex \u003d re.sub(pattern, replace, path_pattern)"},{"line_number":531,"context_line":"        # temporarily sub out **"},{"line_number":532,"context_line":"        pattern \u003d r\u0027([^\\*]*)\\*\\*([^\\*]*)\u0027"},{"line_number":533,"context_line":"        replace \u003d r\u0027\\1%TMP%\\2\u0027"}],"source_content_type":"text/x-python","patch_set":16,"id":"7faddb67_1391e847","line":530,"range":{"start_line":528,"start_character":7,"end_line":530,"end_character":59},"in_reply_to":"7faddb67_586bff83","updated":"2019-07-13 02:21:00.000000000","message":".* vs .+ on a tag replacement.\n\nThe question of 0 or more matching vs 1 or more matching:\n\nuse of 0 or more (*) means you can omit the tag, so making the replacement of * with [^\\/]* means that /v1/{project_id}/XXXX allows for /v1//xxxxx and /v1/1234/xxxx\n\nin the case of /v1/servers/{server_id}, * would allow for \n/v1/servers// which is semantically the same in HTTP land as /v1/servers/ whereas [^\\/]+ would require some value for ID in the tag.\n\nTokenizing this would ultimately solve much of the same thing as long as that issue is still addressed if we care about it.","commit_id":"da57af63f5855688f08015b0dc76e114a99bf266"},{"author":{"_account_id":8482,"name":"Colleen Murphy","email":"colleen@gazlene.net","username":"krinkle"},"change_message_id":"c9297a2e5851812c1f4ab651c7e622d5b69b4164","unresolved":false,"context_lines":[{"line_number":525,"context_line":"        # The fnmatch module doesn\u0027t provide the ability to match * versus **,"},{"line_number":526,"context_line":"        # so convert to regex."},{"line_number":527,"context_line":"        # replace {tags} with *"},{"line_number":528,"context_line":"        pattern \u003d r\u0027{[^}]*}\u0027"},{"line_number":529,"context_line":"        replace \u003d r\u0027*\u0027"},{"line_number":530,"context_line":"        path_regex \u003d re.sub(pattern, replace, path_pattern)"},{"line_number":531,"context_line":"        # temporarily sub out **"},{"line_number":532,"context_line":"        pattern \u003d r\u0027([^\\*]*)\\*\\*([^\\*]*)\u0027"},{"line_number":533,"context_line":"        replace \u003d r\u0027\\1%TMP%\\2\u0027"}],"source_content_type":"text/x-python","patch_set":16,"id":"7faddb67_586bff83","line":530,"range":{"start_line":528,"start_character":7,"end_line":530,"end_character":59},"in_reply_to":"7faddb67_6d05db13","updated":"2019-07-12 22:59:22.000000000","message":"I\u0027m not sure I understand the question. This is just processing the access rule in the app cred, it is not matching to a request yet. Let me try to convert this to a tokenizer and see if your concern is addressed.","commit_id":"da57af63f5855688f08015b0dc76e114a99bf266"},{"author":{"_account_id":2903,"name":"Morgan Fainberg","email":"morgan.fainberg@gmail.com","username":"mdrnstm"},"change_message_id":"500f739b729c45e4007bb5719b07b93245657eb2","unresolved":false,"context_lines":[{"line_number":525,"context_line":"        # The fnmatch module doesn\u0027t provide the ability to match * versus **,"},{"line_number":526,"context_line":"        # so convert to regex."},{"line_number":527,"context_line":"        # replace {tags} with *"},{"line_number":528,"context_line":"        pattern \u003d r\u0027{[^}]*}\u0027"},{"line_number":529,"context_line":"        replace \u003d r\u0027*\u0027"},{"line_number":530,"context_line":"        path_regex \u003d re.sub(pattern, replace, path_pattern)"},{"line_number":531,"context_line":"        # temporarily sub out **"},{"line_number":532,"context_line":"        pattern \u003d r\u0027([^\\*]*)\\*\\*([^\\*]*)\u0027"},{"line_number":533,"context_line":"        replace \u003d r\u0027\\1%TMP%\\2\u0027"}],"source_content_type":"text/x-python","patch_set":16,"id":"7faddb67_6d05db13","line":530,"range":{"start_line":528,"start_character":7,"end_line":530,"end_character":59},"in_reply_to":"7faddb67_8d4a97a9","updated":"2019-07-12 22:23:13.000000000","message":"Thanks for addressing the first concern, I see it now..., the question remains should we allow:\n\n/v1/servers/\n/v1/servers/////\n\nShould we make it a \"+\" vs \"*\" (regex, 1 or more, vs 0 or more) for {tag}?","commit_id":"da57af63f5855688f08015b0dc76e114a99bf266"},{"author":{"_account_id":2903,"name":"Morgan Fainberg","email":"morgan.fainberg@gmail.com","username":"mdrnstm"},"change_message_id":"cc4370b9084ef4a238144db978362dde92b01051","unresolved":false,"context_lines":[{"line_number":525,"context_line":"        # The fnmatch module doesn\u0027t provide the ability to match * versus **,"},{"line_number":526,"context_line":"        # so convert to regex."},{"line_number":527,"context_line":"        # replace {tags} with *"},{"line_number":528,"context_line":"        pattern \u003d r\u0027{[^}]*}\u0027"},{"line_number":529,"context_line":"        replace \u003d r\u0027*\u0027"},{"line_number":530,"context_line":"        path_regex \u003d re.sub(pattern, replace, path_pattern)"},{"line_number":531,"context_line":"        # temporarily sub out **"},{"line_number":532,"context_line":"        pattern \u003d r\u0027([^\\*]*)\\*\\*([^\\*]*)\u0027"},{"line_number":533,"context_line":"        replace \u003d r\u0027\\1%TMP%\\2\u0027"},{"line_number":534,"context_line":"        path_regex \u003d re.sub(pattern, replace, path_regex)"},{"line_number":535,"context_line":"        # replace * with [^\\/]* (all except /)"},{"line_number":536,"context_line":"        pattern \u003d r\u0027((?:[^\\*])?)\\*($|[^\\*])\u0027"},{"line_number":537,"context_line":"        replace \u003d r\u0027\\1[^\\/]*\\2\u0027"},{"line_number":538,"context_line":"        path_regex \u003d re.sub(pattern, replace, path_regex)"},{"line_number":539,"context_line":"        # replace ** with .* (includes /)"},{"line_number":540,"context_line":"        pattern \u003d r\u0027%TMP%\u0027"},{"line_number":541,"context_line":"        replace \u003d \u0027.*\u0027"},{"line_number":542,"context_line":"        path_regex \u003d re.sub(pattern, replace, path_regex)"},{"line_number":543,"context_line":"        path_regex \u003d r\u0027^%s$\u0027 % path_regex"},{"line_number":544,"context_line":"        regex \u003d re.compile(path_regex)"},{"line_number":545,"context_line":"        return regex.match(request_path)"},{"line_number":546,"context_line":""},{"line_number":547,"context_line":"    def validate_allowed_request(self, request, token):"},{"line_number":548,"context_line":"        self.log.debug(\"Validating token access rules against request\")"}],"source_content_type":"text/x-python","patch_set":16,"id":"7faddb67_42c78f65","line":545,"range":{"start_line":528,"start_character":7,"end_line":545,"end_character":40},"updated":"2019-07-12 16:34:28.000000000","message":"A couple comments here:\n\n1) If we\u0027re doing this work, we can probably pre-compile the regex and then perform the sub, these are static regexes and since this happens a lot, it will be sped up. The compile should be done outside of the method (module level) and covers the regexes until line 542.\n\n2) I\u0027d like some explicit tests for this method. It\u0027s pretty hairy.\n\n3) It might be easier to tokenize the glob, capturing the {tag} and ** as groups, then iterating through and replacing {tag} with [^\\/]+ and ** with .* then reconstruct into a r\u0027XXX\u0027 and run regex.match (see item 4 below, no need to compile)\n\n4) The final regex doesn\u0027t need to be compiled explicitly, it could be run just with re.match(\u003cpattern\u003e, \u003cstring\u003e).","commit_id":"da57af63f5855688f08015b0dc76e114a99bf266"},{"author":{"_account_id":8482,"name":"Colleen Murphy","email":"colleen@gazlene.net","username":"krinkle"},"change_message_id":"0b0c3538bae33448279c4988bc7c1aad3caa6d74","unresolved":false,"context_lines":[{"line_number":525,"context_line":"        # The fnmatch module doesn\u0027t provide the ability to match * versus **,"},{"line_number":526,"context_line":"        # so convert to regex."},{"line_number":527,"context_line":"        # replace {tags} with *"},{"line_number":528,"context_line":"        pattern \u003d r\u0027{[^}]*}\u0027"},{"line_number":529,"context_line":"        replace \u003d r\u0027*\u0027"},{"line_number":530,"context_line":"        path_regex \u003d re.sub(pattern, replace, path_pattern)"},{"line_number":531,"context_line":"        # temporarily sub out **"},{"line_number":532,"context_line":"        pattern \u003d r\u0027([^\\*]*)\\*\\*([^\\*]*)\u0027"},{"line_number":533,"context_line":"        replace \u003d r\u0027\\1%TMP%\\2\u0027"},{"line_number":534,"context_line":"        path_regex \u003d re.sub(pattern, replace, path_regex)"},{"line_number":535,"context_line":"        # replace * with [^\\/]* (all except /)"},{"line_number":536,"context_line":"        pattern \u003d r\u0027((?:[^\\*])?)\\*($|[^\\*])\u0027"},{"line_number":537,"context_line":"        replace \u003d r\u0027\\1[^\\/]*\\2\u0027"},{"line_number":538,"context_line":"        path_regex \u003d re.sub(pattern, replace, path_regex)"},{"line_number":539,"context_line":"        # replace ** with .* (includes /)"},{"line_number":540,"context_line":"        pattern \u003d r\u0027%TMP%\u0027"},{"line_number":541,"context_line":"        replace \u003d \u0027.*\u0027"},{"line_number":542,"context_line":"        path_regex \u003d re.sub(pattern, replace, path_regex)"},{"line_number":543,"context_line":"        path_regex \u003d r\u0027^%s$\u0027 % path_regex"},{"line_number":544,"context_line":"        regex \u003d re.compile(path_regex)"},{"line_number":545,"context_line":"        return regex.match(request_path)"},{"line_number":546,"context_line":""},{"line_number":547,"context_line":"    def validate_allowed_request(self, request, token):"},{"line_number":548,"context_line":"        self.log.debug(\"Validating token access rules against request\")"}],"source_content_type":"text/x-python","patch_set":16,"id":"7faddb67_7a37ba00","line":545,"range":{"start_line":528,"start_character":7,"end_line":545,"end_character":40},"in_reply_to":"7faddb67_42c78f65","updated":"2019-07-15 23:06:31.000000000","message":"Done","commit_id":"da57af63f5855688f08015b0dc76e114a99bf266"},{"author":{"_account_id":2903,"name":"Morgan Fainberg","email":"morgan.fainberg@gmail.com","username":"mdrnstm"},"change_message_id":"a5d292dbba54804835f558cc057e75dd6230baf7","unresolved":false,"context_lines":[{"line_number":571,"context_line":"                             \u0027 [keystone_authtoken] is not a valid service\u0027"},{"line_number":572,"context_line":"                             \u0027 type in the catalog.\u0027)"},{"line_number":573,"context_line":"            raise ksm_exceptions.InvalidToken(_(\u0027Token authorization failed\u0027))"},{"line_number":574,"context_line":"        # token can always be validated regardless of access rules"},{"line_number":575,"context_line":"        if (my_service_type \u003d\u003d \u0027identity\u0027 and"},{"line_number":576,"context_line":"                request.method \u003d\u003d \u0027GET\u0027 and"},{"line_number":577,"context_line":"                request.path.endswith(\u0027/v3/auth/tokens\u0027)):"},{"line_number":578,"context_line":"            return"},{"line_number":579,"context_line":"        if request.service_token:"},{"line_number":580,"context_line":"            # The request may not match an allowed request, but the presence"},{"line_number":581,"context_line":"            # of the service token indicates this is a chain of requests and"}],"source_content_type":"text/x-python","patch_set":16,"id":"7faddb67_c4adcce9","line":578,"range":{"start_line":574,"start_character":0,"end_line":578,"end_character":18},"updated":"2019-07-12 16:53:05.000000000","message":"This should probably be above the catalog checking. We don\u0027t care about the catalog in this case, we know we are identity and we know the path. Less work for high-traffic paths.","commit_id":"da57af63f5855688f08015b0dc76e114a99bf266"},{"author":{"_account_id":8482,"name":"Colleen Murphy","email":"colleen@gazlene.net","username":"krinkle"},"change_message_id":"0b0c3538bae33448279c4988bc7c1aad3caa6d74","unresolved":false,"context_lines":[{"line_number":571,"context_line":"                             \u0027 [keystone_authtoken] is not a valid service\u0027"},{"line_number":572,"context_line":"                             \u0027 type in the catalog.\u0027)"},{"line_number":573,"context_line":"            raise ksm_exceptions.InvalidToken(_(\u0027Token authorization failed\u0027))"},{"line_number":574,"context_line":"        # token can always be validated regardless of access rules"},{"line_number":575,"context_line":"        if (my_service_type \u003d\u003d \u0027identity\u0027 and"},{"line_number":576,"context_line":"                request.method \u003d\u003d \u0027GET\u0027 and"},{"line_number":577,"context_line":"                request.path.endswith(\u0027/v3/auth/tokens\u0027)):"},{"line_number":578,"context_line":"            return"},{"line_number":579,"context_line":"        if request.service_token:"},{"line_number":580,"context_line":"            # The request may not match an allowed request, but the presence"},{"line_number":581,"context_line":"            # of the service token indicates this is a chain of requests and"}],"source_content_type":"text/x-python","patch_set":16,"id":"7faddb67_5a32fe11","line":578,"range":{"start_line":574,"start_character":0,"end_line":578,"end_character":18},"in_reply_to":"7faddb67_c4adcce9","updated":"2019-07-15 23:06:31.000000000","message":"Done","commit_id":"da57af63f5855688f08015b0dc76e114a99bf266"},{"author":{"_account_id":2903,"name":"Morgan Fainberg","email":"morgan.fainberg@gmail.com","username":"mdrnstm"},"change_message_id":"f653238b2e5ee320032bf7bd459d26d65fa607f2","unresolved":false,"context_lines":[{"line_number":294,"context_line":"            path_regex +\u003d \u0027.*\u0027"},{"line_number":295,"context_line":"        if token[\u0027literal\u0027]:"},{"line_number":296,"context_line":"            path_regex +\u003d token[\u0027literal\u0027]"},{"line_number":297,"context_line":"    path_regex \u003d r\u0027^%s$\u0027 % path_regex"},{"line_number":298,"context_line":"    return re.match(path_regex, request_path)"},{"line_number":299,"context_line":""},{"line_number":300,"context_line":""}],"source_content_type":"text/x-python","patch_set":17,"id":"7faddb67_4b86ae15","line":297,"range":{"start_line":297,"start_character":17,"end_line":297,"end_character":24},"updated":"2019-07-15 22:49:48.000000000","message":".match() used on line298 implies use of \"^\", this is in contrast to .search(). However, I am glad you\u0027re being explicit here (please do not take this as a comment that a change should be made).\n\n    \u003e\u003e\u003e x \u003d \u0027ASdf\u0027\n    \u003e\u003e\u003e re.match(\u0027df\u0027, x)\n    \u003e\u003e\u003e re.match(\u0027ASdf\u0027, x)\n    \u003c_sre.SRE_Match object; span\u003d(0, 4), match\u003d\u0027ASdf\u0027\u003e\n    \u003e\u003e\u003e re.match(\u0027ASdf\u0027, x)","commit_id":"2b2dd1e2d5f4d2561482f323784d3cdd3dda8e0e"},{"author":{"_account_id":2903,"name":"Morgan Fainberg","email":"morgan.fainberg@gmail.com","username":"mdrnstm"},"change_message_id":"f653238b2e5ee320032bf7bd459d26d65fa607f2","unresolved":false,"context_lines":[{"line_number":278,"context_line":"    return [(g, copy.deepcopy(o)) for g, o in AUTH_TOKEN_OPTS]"},{"line_number":279,"context_line":""},{"line_number":280,"context_line":""},{"line_number":281,"context_line":"def _path_matches(request_path, path_pattern):"},{"line_number":282,"context_line":"    # The fnmatch module doesn\u0027t provide the ability to match * versus **,"},{"line_number":283,"context_line":"    # so convert to regex."},{"line_number":284,"context_line":"    token_regex \u003d (r\u0027(?P\u003ctag\u003e{[^}]*})|\u0027  # {tag} # nosec"},{"line_number":285,"context_line":"                   \u0027(?P\u003cwild\u003e\\*(?\u003d$|[^\\*]))|\u0027  # *"},{"line_number":286,"context_line":"                   \u0027(?P\u003crec_wild\u003e\\*\\*)|\u0027  # **"},{"line_number":287,"context_line":"                   \u0027(?P\u003cliteral\u003e[^{}\\*])\u0027)  # anything else"},{"line_number":288,"context_line":"    path_regex \u003d \u0027\u0027"},{"line_number":289,"context_line":"    for match in re.finditer(token_regex, path_pattern):"},{"line_number":290,"context_line":"        token \u003d match.groupdict()"},{"line_number":291,"context_line":"        if token[\u0027tag\u0027] or token[\u0027wild\u0027]:"},{"line_number":292,"context_line":"            path_regex +\u003d \u0027[^\\/]+\u0027"},{"line_number":293,"context_line":"        if token[\u0027rec_wild\u0027]:"},{"line_number":294,"context_line":"            path_regex +\u003d \u0027.*\u0027"},{"line_number":295,"context_line":"        if token[\u0027literal\u0027]:"},{"line_number":296,"context_line":"            path_regex +\u003d token[\u0027literal\u0027]"},{"line_number":297,"context_line":"    path_regex \u003d r\u0027^%s$\u0027 % path_regex"},{"line_number":298,"context_line":"    return re.match(path_regex, request_path)"},{"line_number":299,"context_line":""},{"line_number":300,"context_line":""},{"line_number":301,"context_line":"class _BIND_MODE(object):"}],"source_content_type":"text/x-python","patch_set":17,"id":"7faddb67_8b34a694","line":298,"range":{"start_line":281,"start_character":0,"end_line":298,"end_character":45},"updated":"2019-07-15 22:49:48.000000000","message":"So very much easier to follow. Thanks!","commit_id":"2b2dd1e2d5f4d2561482f323784d3cdd3dda8e0e"}],"keystonemiddleware/auth_token/_identity.py":[{"author":{"_account_id":5046,"name":"Lance Bragstad","email":"lbragstad@redhat.com","username":"ldbragst"},"change_message_id":"6d0fe5ea73292f921498f47283311e4932f629b5","unresolved":false,"context_lines":[{"line_number":24,"context_line":"from keystonemiddleware.auth_token import _exceptions as ksm_exceptions"},{"line_number":25,"context_line":"from keystonemiddleware.i18n import _"},{"line_number":26,"context_line":""},{"line_number":27,"context_line":"ACCESS_RULES_SUPPORT \u003d \u00271.0\u0027"},{"line_number":28,"context_line":""},{"line_number":29,"context_line":""},{"line_number":30,"context_line":"def _convert_fetch_cert_exception(fetch_cert):"}],"source_content_type":"text/x-python","patch_set":10,"id":"9fdfeff1_40cbdc1c","line":27,"updated":"2019-02-27 18:05:16.000000000","message":"I think I was originally expecting this to be a client version or something. That way we could query the version installed and use that if \u003e than version X, where X is the version we implemented access rule support.","commit_id":"9b69a99e8b77e6cb13dd2eef308e5904620bf726"},{"author":{"_account_id":8482,"name":"Colleen Murphy","email":"colleen@gazlene.net","username":"krinkle"},"change_message_id":"90576e776c713fbc2f9b1425c5e351fbe7860303","unresolved":false,"context_lines":[{"line_number":24,"context_line":"from keystonemiddleware.auth_token import _exceptions as ksm_exceptions"},{"line_number":25,"context_line":"from keystonemiddleware.i18n import _"},{"line_number":26,"context_line":""},{"line_number":27,"context_line":"ACCESS_RULES_SUPPORT \u003d \u00271.0\u0027"},{"line_number":28,"context_line":""},{"line_number":29,"context_line":""},{"line_number":30,"context_line":"def _convert_fetch_cert_exception(fetch_cert):"}],"source_content_type":"text/x-python","patch_set":10,"id":"9fdfeff1_6b56012d","line":27,"in_reply_to":"9fdfeff1_40cbdc1c","updated":"2019-02-27 19:05:17.000000000","message":"Hmm, like the released version of keystonemiddleware? We could do that, but that assumes that the only client to ever use this will be keystonemiddleware.","commit_id":"9b69a99e8b77e6cb13dd2eef308e5904620bf726"},{"author":{"_account_id":5046,"name":"Lance Bragstad","email":"lbragstad@redhat.com","username":"ldbragst"},"change_message_id":"583685429479147eb5d366cac338100016bc5f2c","unresolved":false,"context_lines":[{"line_number":24,"context_line":"from keystonemiddleware.auth_token import _exceptions as ksm_exceptions"},{"line_number":25,"context_line":"from keystonemiddleware.i18n import _"},{"line_number":26,"context_line":""},{"line_number":27,"context_line":"ACCESS_RULES_SUPPORT \u003d \u00271.0\u0027"},{"line_number":28,"context_line":""},{"line_number":29,"context_line":""},{"line_number":30,"context_line":"def _convert_fetch_cert_exception(fetch_cert):"}],"source_content_type":"text/x-python","patch_set":10,"id":"9fdfeff1_06e72ce5","line":27,"in_reply_to":"9fdfeff1_6b56012d","updated":"2019-02-27 19:27:21.000000000","message":"Mmm - I suppose an entirely separate version would allow other clients to queue off of it, instead of forcing a keystonemiddleware version number. Not sure how likely that is, but...","commit_id":"9b69a99e8b77e6cb13dd2eef308e5904620bf726"},{"author":{"_account_id":2903,"name":"Morgan Fainberg","email":"morgan.fainberg@gmail.com","username":"mdrnstm"},"change_message_id":"f653238b2e5ee320032bf7bd459d26d65fa607f2","unresolved":false,"context_lines":[{"line_number":21,"context_line":"from keystonemiddleware.auth_token import _exceptions as ksm_exceptions"},{"line_number":22,"context_line":"from keystonemiddleware.i18n import _"},{"line_number":23,"context_line":""},{"line_number":24,"context_line":"ACCESS_RULES_SUPPORT \u003d \u00271.0\u0027"},{"line_number":25,"context_line":""},{"line_number":26,"context_line":""},{"line_number":27,"context_line":"class _RequestStrategy(object):"}],"source_content_type":"text/x-python","patch_set":17,"id":"7faddb67_4b9b0e65","line":24,"range":{"start_line":24,"start_character":23,"end_line":24,"end_character":28},"updated":"2019-07-15 22:49:48.000000000","message":"Are we intending Access Rules being SEMVER? I\u0027d almost just go monotonic increase of versioning, no need to change this, more just a thought.","commit_id":"2b2dd1e2d5f4d2561482f323784d3cdd3dda8e0e"},{"author":{"_account_id":8482,"name":"Colleen Murphy","email":"colleen@gazlene.net","username":"krinkle"},"change_message_id":"0b0c3538bae33448279c4988bc7c1aad3caa6d74","unresolved":false,"context_lines":[{"line_number":21,"context_line":"from keystonemiddleware.auth_token import _exceptions as ksm_exceptions"},{"line_number":22,"context_line":"from keystonemiddleware.i18n import _"},{"line_number":23,"context_line":""},{"line_number":24,"context_line":"ACCESS_RULES_SUPPORT \u003d \u00271.0\u0027"},{"line_number":25,"context_line":""},{"line_number":26,"context_line":""},{"line_number":27,"context_line":"class _RequestStrategy(object):"}],"source_content_type":"text/x-python","patch_set":17,"id":"7faddb67_6b2cca01","line":24,"range":{"start_line":24,"start_character":23,"end_line":24,"end_character":28},"in_reply_to":"7faddb67_4b9b0e65","updated":"2019-07-15 23:06:31.000000000","message":"I can\u0027t really think of a reason this would need to be semver, this was sort of an arbitrary marker. I think it may be best to change this to monotonic just to avoid implying it was semver. Most likely this will never change at all and it\u0027s more of a boolean than anything else.","commit_id":"2b2dd1e2d5f4d2561482f323784d3cdd3dda8e0e"}],"keystonemiddleware/tests/unit/client_fixtures.py":[{"author":{"_account_id":2903,"name":"Morgan Fainberg","email":"morgan.fainberg@gmail.com","username":"mdrnstm"},"change_message_id":"a5d292dbba54804835f558cc057e75dd6230baf7","unresolved":false,"context_lines":[{"line_number":372,"context_line":"                \u0027service\u0027: \u0027compute\u0027"},{"line_number":373,"context_line":"            },"},{"line_number":374,"context_line":"            {"},{"line_number":375,"context_line":"                \u0027path\u0027: \u0027/v2/images/*\u0027,"},{"line_number":376,"context_line":"                \u0027method\u0027: \u0027GET\u0027,"},{"line_number":377,"context_line":"                \u0027service\u0027: \u0027image\u0027"},{"line_number":378,"context_line":"            },"}],"source_content_type":"text/x-python","patch_set":16,"id":"7faddb67_64e4183d","line":375,"range":{"start_line":375,"start_character":16,"end_line":375,"end_character":39},"updated":"2019-07-12 16:53:05.000000000","message":"See previous comments on regex globbing. /* in regex is very different from the glob style.","commit_id":"da57af63f5855688f08015b0dc76e114a99bf266"}],"keystonemiddleware/tests/unit/test_access_rules.py":[{"author":{"_account_id":2903,"name":"Morgan Fainberg","email":"morgan.fainberg@gmail.com","username":"mdrnstm"},"change_message_id":"f653238b2e5ee320032bf7bd459d26d65fa607f2","unresolved":false,"context_lines":[{"line_number":39,"context_line":"        for (request, pattern) in good_matches:"},{"line_number":40,"context_line":"            self.assertIsNotNone(_path_matches(request, pattern))"},{"line_number":41,"context_line":"        bad_matches \u003d ["},{"line_number":42,"context_line":"            (\u0027/v2/servers/someuuid\u0027, \u0027/v2/servers\u0027),"},{"line_number":43,"context_line":"            (\u0027/v2/servers//\u0027, \u0027/v2/servers/{server_id}\u0027),"},{"line_number":44,"context_line":"            (\u0027/v2/servers/123/\u0027, \u0027/v2/servers/{server_id}\u0027),"},{"line_number":45,"context_line":"            (\u0027/v2/servers/123/456\u0027, \u0027/v2/servers/{server_id}\u0027),"},{"line_number":46,"context_line":"            (\u0027/v2/servers/123/456\u0027, \u0027/v2/servers/*\u0027),"},{"line_number":47,"context_line":"            (\u0027/v2/servers\u0027, \u0027v2/servers\u0027),"},{"line_number":48,"context_line":"            (\u0027/v2/servers/123/456/789\u0027, \u0027/v2/*/*/*\u0027),"},{"line_number":49,"context_line":"            (\u0027/v2/servers/123/\u0027, \u0027/v2/*/*/*\u0027),"},{"line_number":50,"context_line":"        ]"},{"line_number":51,"context_line":"        for (request, pattern) in bad_matches:"},{"line_number":52,"context_line":"            self.assertIsNone(_path_matches(request, pattern))"}],"source_content_type":"text/x-python","patch_set":17,"id":"7faddb67_4bb46ed1","line":49,"range":{"start_line":42,"start_character":0,"end_line":49,"end_character":46},"updated":"2019-07-15 22:49:48.000000000","message":"Can be added in a followup, but should also include:\n\n    (\u0027/v2/servers/\u0027, \u0027/v2/servers/{server_id}\u0027),\n    (\u0027/v2/servers\u0027, \u0027/v2/servers/{server_id}\u0027)","commit_id":"2b2dd1e2d5f4d2561482f323784d3cdd3dda8e0e"},{"author":{"_account_id":8482,"name":"Colleen Murphy","email":"colleen@gazlene.net","username":"krinkle"},"change_message_id":"0b0c3538bae33448279c4988bc7c1aad3caa6d74","unresolved":false,"context_lines":[{"line_number":39,"context_line":"        for (request, pattern) in good_matches:"},{"line_number":40,"context_line":"            self.assertIsNotNone(_path_matches(request, pattern))"},{"line_number":41,"context_line":"        bad_matches \u003d ["},{"line_number":42,"context_line":"            (\u0027/v2/servers/someuuid\u0027, \u0027/v2/servers\u0027),"},{"line_number":43,"context_line":"            (\u0027/v2/servers//\u0027, \u0027/v2/servers/{server_id}\u0027),"},{"line_number":44,"context_line":"            (\u0027/v2/servers/123/\u0027, \u0027/v2/servers/{server_id}\u0027),"},{"line_number":45,"context_line":"            (\u0027/v2/servers/123/456\u0027, \u0027/v2/servers/{server_id}\u0027),"},{"line_number":46,"context_line":"            (\u0027/v2/servers/123/456\u0027, \u0027/v2/servers/*\u0027),"},{"line_number":47,"context_line":"            (\u0027/v2/servers\u0027, \u0027v2/servers\u0027),"},{"line_number":48,"context_line":"            (\u0027/v2/servers/123/456/789\u0027, \u0027/v2/*/*/*\u0027),"},{"line_number":49,"context_line":"            (\u0027/v2/servers/123/\u0027, \u0027/v2/*/*/*\u0027),"},{"line_number":50,"context_line":"        ]"},{"line_number":51,"context_line":"        for (request, pattern) in bad_matches:"},{"line_number":52,"context_line":"            self.assertIsNone(_path_matches(request, pattern))"}],"source_content_type":"text/x-python","patch_set":17,"id":"7faddb67_2b0b327d","line":49,"range":{"start_line":42,"start_character":0,"end_line":49,"end_character":46},"in_reply_to":"7faddb67_4bb46ed1","updated":"2019-07-15 23:06:31.000000000","message":"Done","commit_id":"2b2dd1e2d5f4d2561482f323784d3cdd3dda8e0e"}]}
