Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clarification about the meaning of an empty security array #3938

Closed
MathieuVeber opened this issue Jul 2, 2024 · 21 comments · Fixed by #4059 or #4064
Closed

Clarification about the meaning of an empty security array #3938

MathieuVeber opened this issue Jul 2, 2024 · 21 comments · Fixed by #4059 or #4064
Assignees
Labels
Milestone

Comments

@MathieuVeber
Copy link

MathieuVeber commented Jul 2, 2024

Hi, while I was trying to implement the specification, I ran into some existential questions about security requirements!

It's unclear to me if an empty security array ([]) is equivalent to a security array with an empty object ([{}])... The spec doesn't even mention if it would be valid to have an empty security array at the OpenApi Object level and what would that mean...

A declaration of which security mechanisms can be used across the API. The list of values includes alternative security requirement objects that can be used. Only one of the security requirement objects need to be satisfied to authorize a request. Individual operations can override this definition. To make security optional, an empty security requirement ({}) can be included in the array.

A declaration of which security mechanisms can be used for this operation. The list of values includes alternative security requirement objects that can be used. Only one of the security requirement objects need to be satisfied to authorize a request. To make security optional, an empty security requirement ({}) can be included in the array. This definition overrides any declared top-level security. To remove a top-level security declaration, an empty array can be used.

@handrews
Copy link
Member

handrews commented Aug 2, 2024

The empty object here is just a normal Security Requirement Object that doesn't have any entries. And therefore does not require any security requirements to be fulfilled. It does not have any special behavior - it is just the natural outcome of how the Security Requirement Object works that an empty object allows everything.

Since the OpenAPI Object and the Operation Object take arrays of Security Requirement Objects which are logically OR'd, an empty requirement list also allows everything.

You can stuff as many empty Security Requirement Objects into the array as you want, they still all get evaluated the same way, which means that [], [{}], and [{}, {}, {}, {}, {}] all behave the same. It's like adding multiple trues to an OR. true OR true OR true is still true.

@MathieuVeber
Copy link
Author

Thanks for your explanation 💡

We thought for a moment that an empty security array [ ] meant that nothing is allowed whereas an empty security requirement object [{}] allowed everything 😅

@rvedotrc here is your answer

@rvedotrc
Copy link

rvedotrc commented Aug 5, 2024

Since the OpenAPI Object and the Operation Object take arrays of Security Requirement Objects which are logically OR'd, an empty requirement list also allows everything.

So you're saying logical-OR is defined for zero operands, and the result of that is "true", right?

I'm just checking because it seems like a fairly commonly accepted thing that if one defines "AND" or "OR" for zero operands, then the result is the opposite of what you just said.

For example, in Ruby, where "all" / "any" are analagous to "AND" / "OR":

irb(main):001:0> [].all?
=> true
irb(main):002:0> [].any?
=> false

or indeed in Javascript:

 node
Welcome to Node.js v22.5.1.
Type ".help" for more information.
> [].every(Boolean)
true
> [].some(Boolean)
false
>

If one is going to define AND/OR for zero operands, then conventionally one defines AND-of-nothing to be true, and OR-of-nothing to be false.

So, I'm just checking, because at the moment, it sounds like [] is a special case, rather than following the logic one might expect. Thoughts?

@handrews
Copy link
Member

handrews commented Aug 6, 2024

There seems to be a fair amount of debate on this in #3995, so let's not consider this resolved just yet.

@rafalkrupinski
Copy link

It's not about what's allowed, it's about what's required
'[]' - no security requirement
'[{}]' - a single 'allow anyone' security requirement

@rvedotrc
Copy link

The list [ ... ] is about what's allowed.

The object { ... } is about what's required.

@rafalkrupinski
Copy link

rafalkrupinski commented Aug 11, 2024

Isn't English funny?

The list [ ... ] is about what's allowed.

This way [] would mean nothing is allowed, which is not the case. It means that nothing is required.
One could say, it's a list of gate keepers. You can go thru either one, and if there's not gate keeper, you're free to enter, while [{}] is a gatekeeper with no requirement and witch lets anyone.

So yes, [] would be an equivalent of [{}]

@ralfhandl
Copy link
Contributor

[] would be an equivalent of [{}]

Current consensus: no, these are not equivalent, see #3995 (comment)

The correct behaviour for an empty array is unclear. We think it is widely used to mean that there's no security applied ("allow all") - but logically it means that there are no security rules that can be fulfilled to permit access.

We do need to update wording to explain that the empty security array is ambiguous in the current spec versions.

Current proposal:

@rafalkrupinski
Copy link

I agree it could logically it means that there are no security rules that can be fulfilled to permit access. but I just gave equally logical explanation how it can mean open access.

It could also be settled that it means the same as null value - use API wide security

@ralfhandl
Copy link
Contributor

I agree it could logically it means that there are no security rules that can be fulfilled to permit access. but I just gave equally logical explanation how it can mean open access.

That neatly sums up our dilemma:

  • it wasn't clearly stated what it means
  • it can plausibly have two or three mutually exclusive meanings
  • the only way out now is to officially declare this as "undefined" or "implementation-specific"

Which is unsatisfying because it essentially means

  • Don't use this unless you have verified that all tools in your tool chain (and the tool chains of your partners, customers, ...) interpret it the same way

Which essentially means

  • Don't us this

@handrews
Copy link
Member

Don't use this unless you have verified that all tools in your tool chain (and the tool chains of your partners, customers, ...) interpret it the same way

This is exactly why we added "implementation defined" as a description. Which I think is where we ended up in the TDC call? (For 3.0.4 and 3.1.1 anyway).

@ralfhandl ralfhandl added this to the v3.0.4 milestone Aug 21, 2024
@mikekistler
Copy link
Contributor

I spent some time this weekend thinking this through and I now believe that the current spec as written is clear and requires no changes. The key to understanding the current wording is to view it in the context of the "Open World" philosophy we have adopted for OpenAPI descriptions.

"Open World" vs "Closed World" was discussed and debated extensively way back in the day in Issue #568. The conclusion was that OpenAPI descriptions are a contract that describe "what works and how", but do not preclude things not described from working. So for example, if a path is not listed in the set of paths, this does not mean it does not work. Likewise, if a parameter is not listed in the parameter list for an operation, this does not mean that the operation will not accept it / use it.

Extending this to security / security requirements (perhaps "requirement" is a bad name in this regard), this means that "if a security requirement is listed, it should work", but not "if a security requirement is not listed, then it does not work". In other words, there is no way to define when a security requirement will not work in OpenAPI, just as there is no way to define a path that does not work, or an operation parameter that does not work.

At both the OpenAPI level and operation level, the description of the security field says:

The list of values includes alternative security requirement objects that can be used. Only one of the security requirement objects need to be satisfied to authorize a request.

This is consistent with the "Open World" philosophy. It says "these are things that work". It does not and should not say "things not in this list do not work".

In this list, I think we can clearly answer the question that started all this unambiguously based on the current language in the spec:

An empty security array means "No security requirement is declared to work". It does not mean "none work".

The absence of a security field means the same thing: "No security requirement is declared to work", but operations may accept security requirements as OpenAPI definitions are "Open World" contracts.

@handrews
Copy link
Member

@mikekistler I have to say that I agree with your logic here, and I'm also trying to figure out why it feels a bit odd. I think that's because I think of security as an area where people are trying to close off possibilities.

We've previously discussed places where it makes sense to allow closing off a "world". For example, JSON Schema allows setting additionalProperties: false or (more flexibly) unevaluatedProperties: false, because the API provider owns the entire thing being described by the schema. This is also why we eventually agreed to consider closing off the set of URL query string parameters (#2511, #1502): the URL is owned by the resource, which is owned by the API provider. And while there are use cases for additional query string parameters (tracking info, for example), whether to allow such things or not is still under the control of the resource. And as we discussed with that example, it doesn't matter if it's a "good" or "bad" idea to close off that set, the point is that the API provider owns it.

So coming back to security, I definitely agree that if the field is not present, it's an open world "we're just not giving you any information here, and you can't assume that means anyhting one way or the other" situation.

Philosophically, I think the API provider controls the set of authentication mechanisms, and should be able to say "just these and no more" for the same reason as being able to close other fully-owned spaces. However, two points suggest that this is not allowed:

  1. The Operation Object states that security: [] removes the security field from the OpenAPI Object, which I interpret as restoring the "no security field at all" condition, which I agree is an open-world scenario
  2. We have a section on security filtering, which can result in an API Description with an empty Paths Object. This does not mean that there aren't any paths, just that there aren't any path that the reader is allowed to know about. Treating security: [] as a similar filtered list would be consistent.

So I think I end up agreeing with you in practical terms according to what the OAS is established to mean, while still philosophically agreeing with @rvedotrc that being able to close the set of authentication mechanisms would have made more sense.

I think it might be worth adding a section on open world / closed world implications that we can reference in fields where it is particularly significant. The section should note that in some cases OAS (or JSON Schema) allows "closing" a world.

I'll also note that we're not consistent with the open world: the set of HTTP methods is, according to the HTTP spec, open, yet we have closed it. Which is a point of significant ongoing frustration for our users (see #1747 for a consolidated issue of all of the requests over the years). So any section on this philosophy needs to account for the exceptions.

@rafalkrupinski
Copy link

I have to say that I agree with your logic here, and I'm also trying to figure out why it feels a bit odd.

@handrews Haha I have the same experience.
I think for me it's the fact you can make a story (open world concept, gate keepers) that makes anything logical.

@mikekistler Notice how you write (perhaps "requirement" is a bad name in this regard)... well perhaps that "requirement" name exempts it from the Open World concept?

How about "there are no keys to open these doors"? Sounds logical, doesn't it.

I agree with @ralfhandl, it should remain undefined.

@mikekistler
Copy link
Contributor

@handrews You say:

I'll also note that we're not consistent with the open world: the set of HTTP methods is, according to the HTTP spec, open, yet we have closed it.

I don't see it quite that way. OpenAPI says that you may describe only a certain set of HTTP methods. It does not say that your service must not accept any undescribed HTTP methods.

@handrews
Copy link
Member

@mikekistler Fair enough. It bothers me to have a part of our spec that is philosophically inconsistent with another spec (allowing only a fixed subset for an open set), but you are correct that it can be framed within an open world assumption.

@rvedotrc
Copy link

I now believe that the current spec as written is clear

I'm trying to square that statement with the amount of discussion that has happened under this issue / the PRs. If the current spec is clear, how is it that we have spent so long discussing what it means?

I don't think it's clear.

@ralfhandl
Copy link
Contributor

I agree with @mikekistler's reasoning and the conclusion that

the current spec as written [...] requires no changes

Adding some explanation probably would not hurt, given the length of this discussion.

@handrews
Copy link
Member

We could add something along the lines of:

Unless explicitly stated otherwise, OpenAPI Descriptions are not assumed to be complete. Additional endpoints, authentication or authorization mechanisms, parameters, headers, methods, data formats, etc. MAY be present within the API without being described. Use cases for this assumption include but are not limited to Security Filtering.

@handrews
Copy link
Member

I think the current wording around using {} in the array to make security "optional" is sufficient to indicate that [{}] is unambiguously "you don't need to passy any security, and we're not telling you anything about how you would do such a thing if you wanted to."

@ralfhandl
Copy link
Contributor

Clarified in 3.0.4 and 3.1.1, port to 3.2.0 in progress. Closing the issue.

jeremyfiel added a commit to jeremyfiel/OpenAPI-Specification that referenced this issue Sep 4, 2024
jeremyfiel added a commit to jeremyfiel/OpenAPI-Specification that referenced this issue Sep 4, 2024
jeremyfiel added a commit to jeremyfiel/OpenAPI-Specification that referenced this issue Sep 4, 2024
jeremyfiel added a commit to jeremyfiel/OpenAPI-Specification that referenced this issue Sep 4, 2024
jeremyfiel added a commit to jeremyfiel/OpenAPI-Specification that referenced this issue Sep 4, 2024
jeremyfiel added a commit to jeremyfiel/OpenAPI-Specification that referenced this issue Sep 4, 2024
jeremyfiel added a commit to jeremyfiel/OpenAPI-Specification that referenced this issue Sep 4, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment