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

feat: allow passing over security_shemes to the specification and add… #73

Merged
merged 5 commits into from
Aug 30, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions examples/config.ru
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,18 @@ use Apia::OpenApi::Rack,
external_docs: {
description: "Find out more",
url: "https://example.com"
},
security_schemes: {
OAuth2: {
type: "oauth2",
"x-scope-prefix": "example.com/core/v1",
flows: {
authorizationCode: {
authorizationUrl: "https://example.com/oauth/authorize",
tokenUrl: "https://example.com/oauth/token"
}
}
}
}
use Apia::Rack, CoreAPI::Base, "/core/v1", development: true

Expand Down
26 changes: 25 additions & 1 deletion lib/apia/open_api/objects/path.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,14 @@ def initialize(spec:, path_ids:, route:, name:, api_authenticator:)
operationId: convert_route_to_id,
summary: @route.endpoint.definition.name,
description: @route.endpoint.definition.description,
tags: route.group ? get_group_tags(route.group) : [name]
tags: route.group ? get_group_tags(route.group) : [name],
security: []
ikadix marked this conversation as resolved.
Show resolved Hide resolved
}
end

def add_to_spec
add_scopes_description
add_scopes_security
path = @route.path

if @route.request_method == :get
Expand Down Expand Up @@ -106,6 +108,28 @@ def add_scopes_description
DESCRIPTION
end

# Adds scopes security to the OpenAPI path specification.
#
# This method checks if the route's endpoint definition has any scopes defined.
# If scopes are present, it iterates over the security schemes in the OpenAPI
# specification and adds the corresponding scopes to the route's security section.
#
# @return [void]
def add_scopes_security
return unless @route.endpoint.definition.scopes.any?

@spec[:security].each do |auth|
auth.each_key do |key|
scopes = @route.endpoint.definition.scopes
if scope_prefix = @spec[:components][:securitySchemes][key][:"x-scope-prefix"]
scopes = scopes.map { |v| "#{scope_prefix}/#{v}" }
end

@route_spec[:security] << { key => scopes }
end
end
end

# It's worth creating a 'nice' operationId for each route, as this is used as the
# basis for the method name when calling the endpoint using a generated client.
def convert_route_to_id
Expand Down
11 changes: 10 additions & 1 deletion lib/apia/open_api/rack.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ def base_url
@options[:base_url] || "https://api.example.com/api/v1"
end

def security_schemes
@options[:security_schemes] || {}
end

def external_docs
@options[:external_docs] || {}
end
Expand All @@ -46,7 +50,12 @@ def call(env)
return @app.call(env)
end

specification = Specification.new(api_class, base_url, @options[:name], info, external_docs)
specification = Specification.new(api_class, base_url, @options[:name],
{
info: info,
external_docs: external_docs,
security_schemes: security_schemes
})
body = specification.json

[200, { "content-type" => "application/json", "content-length" => body.bytesize.to_s }, [body]]
Expand Down
21 changes: 17 additions & 4 deletions lib/apia/open_api/specification.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,17 @@ class Specification

OPEN_API_VERSION = "3.0.0" # The Ruby client generator currently only supports v3.0.0 https://openapi-generator.tech/

def initialize(api, base_url, name, info = {}, external_docs = {})
def initialize(api, base_url, name, additions = {})
default_additions = { info: {}, external_docs: {}, security_schemes: {} }
additions = default_additions.merge(additions)

@api = api
@base_url = base_url
@name = name || "Core" # will be suffixed with 'Api' and used in the client generator
@spec = {
openapi: OPEN_API_VERSION,
info: info,
externalDocs: external_docs,
info: additions[:info],
externalDocs: additions[:external_docs],
servers: [],
paths: {},
components: {
Expand All @@ -36,6 +39,8 @@ def initialize(api, base_url, name, info = {}, external_docs = {})
@spec.delete(:externalDocs)
end

add_additional_security_schemes(additions[:security_schemes])

# path_ids is used to keep track of all the IDs of all the paths we've generated, to avoid duplicates
# refer to the Path object for more info
@path_ids = []
Expand All @@ -57,8 +62,8 @@ def sort_hash_by_nested_tag(hash)
def build_spec
add_info
add_servers
add_paths
add_security
add_paths
add_tag_groups

@spec[:paths] = sort_hash_by_nested_tag(@spec[:paths])
Expand Down Expand Up @@ -160,6 +165,14 @@ def add_tag_groups
@spec[:"x-tagGroups"].each { |group| group[:tags].sort! }
end

def add_additional_security_schemes(security_schemes)
security_schemes.each do |key, value|
@spec[:components][:securitySchemes] ||= {}
@spec[:components][:securitySchemes][key] = value
@spec[:security] << { key => [] }
end
end

end
end
end
24 changes: 20 additions & 4 deletions spec/apia/open_api/rack_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,11 @@ class MockApi < Apia::API; end
)

expect(Apia::OpenApi::Specification).to have_received(:new).with(
MockApi, default_base_url, name_option, {}, {}
MockApi, default_base_url, name_option, {
info: {},
external_docs: {},
security_schemes: {}
}
)
end

Expand All @@ -80,7 +84,11 @@ class MockApi < Apia::API; end
middleware_response

expect(Apia::OpenApi::Specification).to have_received(:new).with(
MockApi, base_url_option, name_option, {}, {}
MockApi, base_url_option, name_option, {
info: {},
external_docs: {},
security_schemes: {}
}
)
end
end
Expand Down Expand Up @@ -119,7 +127,11 @@ class MockApi < Apia::API; end
)

expect(Apia::OpenApi::Specification).to have_received(:new).with(
MockApi, default_base_url, name_option, {}, {}
MockApi, default_base_url, name_option, {
info: {},
external_docs: {},
security_schemes: {}
}
)
end
end
Expand Down Expand Up @@ -151,7 +163,11 @@ class MockApi < Apia::API; end
)

expect(Apia::OpenApi::Specification).to have_received(:new).with(
api_class, default_base_url, name_option, {}, {}
api_class, default_base_url, name_option, {
info: {},
external_docs: {},
security_schemes: {}
}
)
end
end
Expand Down
18 changes: 16 additions & 2 deletions spec/apia/open_api/specification_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

spec = described_class.new(example_api, base_url, "Core",
{
info: {
version: "2.1.3",
contact: {
name: "API Support",
Expand All @@ -27,10 +28,23 @@
termsOfService: "https://example.com/terms",
"x-added-info": "This is an example of adding custom information to the OpenAPI spec"
},
{
external_docs: {
description: "Find out more",
url: "https://example.com"
})
},
security_schemes: {
OAuth2: {
type: "oauth2",
"x-scope-prefix": "example.com/core/v1",
flows: {
authorizationCode: {
authorizationUrl: "https://example.com/oauth/authorize",
tokenUrl: "https://example.com/oauth/token"
}
}
}
}
})

# uncomment the following line for debugging :)
# puts spec.json
Expand Down
Loading
Loading