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

initial work on security schema (only jwt bearer) #143

Open
wants to merge 16 commits into
base: master
Choose a base branch
from

Conversation

muhammadn
Copy link
Contributor

No description provided.

Copy link
Owner

@ota42y ota42y left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding the feature!
Certainly supporting only JWT to begin with is a very good start.

lib/openapi_parser/schemas/security_schemes.rb Outdated Show resolved Hide resolved
lib/openapi_parser/schemas/components.rb Outdated Show resolved Hide resolved
@muhammadn
Copy link
Contributor Author

muhammadn commented Feb 21, 2023

@ota42y You can clone my branch. It is almost complete but problem with the MyBearerToken which can be any name in comment above.

Update: This is done! 🎉

@muhammadn
Copy link
Contributor Author

muhammadn commented Feb 21, 2023

@ota42y Mostly done. I will figure out how to get the headers to check for a JWT token, or if it is a valid token.

I've done some others, i'm very appreciative of your time you took to answer me, 🙇‍♂️, so i contributed some PRs for other stuff that is missing (servers is done, info is not that important but it's simple so i decided to do it)

Copy link
Contributor Author

@muhammadn muhammadn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've finished it including tests.

Copy link
Owner

@ota42y ota42y left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!!! I think it is a very good code.
Some of the validation was not up to OpenAPI Specification, so that needs to be fixed, but I think we're almost there!

def validate_security_schemes(securityScheme, headers)
if self.type == "http" && self.scheme == "bearer" && self.bearer_format == "JWT"
raise "need authorization" unless headers["AUTHORIZATION"]
raise "not bearer" unless headers["AUTHORIZATION"].split[0] == "Bearer"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

end

def validate_security(security_schemes, headers)
operation_object.validate_security_schemes(security_schemes, headers)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The OpenAPI Specification define that it is OK if either one of OpenAPI Object or Operation Object's security requirement is satisfied, so need to check both security fields.
https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#securityRequirementObject

When a list of Security Requirement Objects is defined on the OpenAPI Object or Operation Object, only one of the Security Requirement Objects in the list needs to be satisfied to authorize the request.

Now RequestOperation Object doesn't have OpenAPI's security field so we need pass it too.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is good to create validator which require Security Scheme Object and Security Requriement Object and validate seurity schemes specified by requirement objects for validating OpenAPI Object and Operation Object.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ota42y I don't understand this part:

I think it is good to create validator which require Security Scheme Object and Security Requriement Object

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you are adding the validation method to the Security Requriement Object, that's fine. I was thinking of using Security Scheme Object for validation and thought that would be a problem. Ignore it.

@@ -30,5 +30,21 @@ def validate_request_body(content_type, params, options)
def validate_response(response_body, response_validate_options)
responses&.validate(response_body, response_validate_options)
end

def validate_security_schemes(securitySchemes, headers)
securitySchemes&.each do |securityScheme|
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We must checks for a Security Scheme Object with a corresponding name if there is a SecurityRequriment Object so we don't need check all securitySchemes.

def validate_security_schemes(securitySchemes, headers)
  validate_results = security.map do |s| # s is security requirement object
    # check all security
    s.validate_security_schemes(securitySchemes, headers)
  end
  if validate_results.count(true) == 1
    return true # accept 
  end
  return ValidateSecurityError()
end

I have determined that the validate passes if only one security scheme is valid, and if two or more are valid, the validate does not pass.
( We need check validate_results.count(true) == 1, not validate_results.count(true) >= 1 )
Is this OK? My English is not very good, so I doubt if I am reading it correctly.

Only one of the security requirement objects need to be satisfied to authorize a request. 

Copy link
Contributor Author

@muhammadn muhammadn Feb 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ota42y
your english is ok...! I have to understand, security requirement object does not have schema information about type, scheme and bearerFormat is only in SecuritySchema object.

So this code below cannot be used in security.rb which is security requirements object (the latest code that i just pushed)

   # this code cannot be used in security.rb (security requirement object)
    def validate_security_schemes(securityScheme, headers)
      if self.type == "http" && self.scheme == "bearer" && self.bearer_format == "JWT"
      end
    end

Copy link
Contributor Author

@muhammadn muhammadn Feb 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ota42y Hi. any updates? I'm thinking of doing my own fork because i cannot find the solution with:

def validate_security_schemes(securitySchemes, headers)
  validate_results = security.map do |s| # s is security requirement object
    # check all security
    s.validate_security_schemes(securitySchemes, headers) <-- this needs to be object, cannot use "self" with `securitySchemes
  end
  if validate_results.count(true) == 1
    return true # accept 
  end
  return ValidateSecurityError()
end

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security requirement object does not have schema information about type, scheme and bearerFormat is only in SecuritySchema object.

You are right. I confused security scheme object and secuirty requirement object 🙇

@@ -65,6 +70,7 @@ def validate_response_body(response_body, response_validate_options = nil)
# @param [OpenAPIParser::SchemaValidator::Options] options request validator options
def validate_request_parameter(params, headers, options = nil)
options ||= config.request_validator_options
validate_security(security_schemes, headers)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now users are using code under the assumption that this validation will not take place, and it may suddenly break at update time.
Therefore, it would be better to control this optionally or make it a separate method and have it called separately.

# check if the JWT token is being sent and try to decode.
# if JWT token does not exist or token cannot decode, then deny access
token = headers["AUTHORIZATION"].split[1]
value = JWT.decode token, nil, false
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How much detail to verify here is a difficult question.
When verifying a signed JWT, the user expects the signature to be correct.
But we must passed key and various options to the library.
The options are numerous and require the same amount of effort as creating a wrapper for the jwt library.
Also, the OpenAPI definition does not expect JWT to have detailed information about signature verification.

One solution would be to have the user pass in a callback what validation is to be performed, and this library would only call the callback.
This would allow users to, for example, check for certain values in the JWT or force disabling of the JWT for some users only.

In this case, instead of limiting the bearerFormat=JWT, I think it would be possible to pass a Security Scheme Object to the callback so that users can validate non-JWT formats as well, thus allowing for a wider range of applications.

@ota42y
Copy link
Owner

ota42y commented Mar 2, 2023

Late reply, but I got back to you, thanks for pointing it out right!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants