diff --git a/UPGRADING.md b/UPGRADING.md index dd5b82ee..1a489d91 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -1034,6 +1034,13 @@ have dropped since, until the process restarted. wrapping `client.ErrTrustChainExpired`. A flow already begun completes as before. +From v0.69.0 every method that contacts or trusts the OP refuses once +the chain has expired, including those that complete a flow already +begun (`HandleAuthorizationResponse`, `ExchangeCode`, +`PollBackchannelAuthentication`), and the error is a `*client.Error` +with code `client.ErrorTrustChainExpired`, still wrapping +`client.ErrTrustChainExpired`. + **What to change:** resolve the chain again and build a new Client before it expires, for example on a timer from the new `Client.TrustChainExpiresAt`: diff --git a/internal/federation/claims_validation.go b/internal/federation/claims_validation.go index bdb80a95..4d225ea6 100644 --- a/internal/federation/claims_validation.go +++ b/internal/federation/claims_validation.go @@ -132,10 +132,11 @@ func parseSourceEndpoint(raw json.RawMessage) (string, error) { } // isHTTPSURL reports whether s is an absolute https URL with a host and -// no fragment. +// no fragment, not even an empty one: url.Parse drops a bare "#", so +// it's looked for in s itself. func isHTTPSURL(s string) bool { u, err := url.Parse(s) - return err == nil && u.Scheme == "https" && u.Host != "" && u.Fragment == "" && !strings.ContainsAny(s, " \t\r\n") + return err == nil && u.Scheme == "https" && u.Host != "" && !strings.ContainsAny(s, "# \t\r\n") } // maxJSONDepth bounds how deeply refuseDuplicateMembers descends; the diff --git a/internal/federation/claims_validation_test.go b/internal/federation/claims_validation_test.go index f74122f0..672c439c 100644 --- a/internal/federation/claims_validation_test.go +++ b/internal/federation/claims_validation_test.go @@ -32,6 +32,7 @@ func TestParseRefusesStatementsSection32Rejects(t *testing.T) { "§3.1.2 trust_anchor_hints in an EC": {ec, `"trust_anchor_hints":["https://ta.example.org"]`, false}, "§3.1.2 authority_hints empty": {ec, `"authority_hints":[]`, true}, "§3.1.2 authority_hints not an Entity ID": {ec, `"authority_hints":["not a url"]`, true}, + "§3.1.2 authority_hints empty fragment": {ec, `"authority_hints":["https://ia.example.org#"]`, true}, "§3.1.3 metadata_policy_crit empty": {ss, `"metadata_policy_crit":[]`, true}, "§3.1.3 metadata_policy_crit listed": {ss, `"metadata_policy_crit":["regexp"]`, false}, "§5 null entity-type metadata": {ec, `"metadata":{"openid_relying_party":null}`, true}, @@ -41,6 +42,7 @@ func TestParseRefusesStatementsSection32Rejects(t *testing.T) { "§5 empty entity-type metadata": {ec, `"metadata":{"openid_relying_party":{}}`, false}, "§3.2 source_endpoint not a URL": {ss, `"source_endpoint":"not a url"`, true}, "§3.2 source_endpoint not https": {ss, `"source_endpoint":"http://a.example.org/fetch"`, true}, + "§3.2 source_endpoint empty fragment": {ss, `"source_endpoint":"https://a.example.org/fetch#"`, true}, "§3.2 source_endpoint https": {ss, `"source_endpoint":"https://a.example.org/fetch"`, false}, "§6.1.2 duplicate metadata_policy parameter": {ss, `"metadata_policy":{"openid_relying_party":{"scope":{"value":"a"},"scope":{"value":"b"}}}`, true}, "§6.1.2 duplicate metadata_policy operator": {ss, `"metadata_policy":{"openid_relying_party":{"scope":{"value":"a","value":"b"}}}`, true},