diff --git a/README.md b/README.md index 4a096db..33cc7b0 100644 --- a/README.md +++ b/README.md @@ -144,7 +144,7 @@ See more [examples](/examples). - [x] Basic - [x] Bearer (include JWT) - [x] APIKey - - [ ] OAuth2 + - [x] OAuth2 - [ ] ExternalDocs Object ## Donate diff --git a/examples/authorization.cr b/examples/authorization.cr index c9d8822..82ca623 100644 --- a/examples/authorization.cr +++ b/examples/authorization.cr @@ -14,6 +14,11 @@ builder = Swagger::Builder.new( Swagger::Authorization.new("bearer", "Private Token Auth"), Swagger::Authorization.jwt(description: "JWT Auth"), Swagger::Authorization.api_key(name: "api_key", location: "query", description: "API Key Auth"), + Swagger::Authorization.cookie(name: "JSESSIONID", description: "Cookie Auth"), + Swagger::Authorization.oauth2(grant_type: "implicit", authorization_url: "/oauth/authorize", scopes: { + "read_users" => "Read users in your account", + "write_users" => "modify users in your account" + }, description: "OAuth 2 Auth") ] ) diff --git a/src/swagger/authorization.cr b/src/swagger/authorization.cr index 6155bba..c0dd884 100644 --- a/src/swagger/authorization.cr +++ b/src/swagger/authorization.cr @@ -11,7 +11,7 @@ module Swagger Basic Bearer APIKey - # OAuth2 + OAuth2 end # Access without any authorization. @@ -44,9 +44,19 @@ module Swagger new(Type::APIKey, description, api_key_name: name, parameter_location: location) end - # def self.oauth2(description : String? = nil) - # new(OAuth2, description) - # end + def self.oauth2(*, grant_type name : String, authorization_url : String? = nil, token_url : String? = nil, + refresh_url : String? = nil, scopes : Hash(String, String)? = nil, description : String? = nil) + oauth2(flows: [OAuth2Flow.new(name, + authorization_url: authorization_url, + token_url: token_url, + refresh_url: refresh_url, + scopes: scopes + )]) + end + + def self.oauth2(*, flows : Array(OAuth2Flow)? = nil, description : String? = nil) + new(Type::OAuth2, description, oauth2_flows: flows) + end def self.new(name : String, description : String? = nil, api_key_name : String? = nil, bearer_format : String? = nil, parameter_location : String? = nil) @@ -58,9 +68,11 @@ module Swagger property api_key_name property bearer_format property parameter_location + property oauth2_flows def initialize(@name : Type, @description : String? = nil, @api_key_name : String? = nil, - @bearer_format : String? = nil, @parameter_location : String? = nil) + @bearer_format : String? = nil, @parameter_location : String? = nil, + @oauth2_flows : Array(OAuth2Flow)? = nil) end def type : Type @@ -84,12 +96,12 @@ module Swagger # ``` def key String.build do |io| - if @type == Authorization::Type::Bearer && (format = @bearer_format) + if type == Authorization::Type::Bearer && (format = @bearer_format) io << format.downcase - elsif @type == Authorization::Type::APIKey && @parameter_location == "cookie" + elsif type == Authorization::Type::APIKey && @parameter_location == "cookie" io << "cookie" else - io << @name + io << name end io << "_auth" diff --git a/src/swagger/error.cr b/src/swagger/error.cr index f9862f9..d21bf08 100644 --- a/src/swagger/error.cr +++ b/src/swagger/error.cr @@ -2,4 +2,5 @@ module Swagger class Error < Exception; end class UndefinedMethod < Error; end class UndefinedParameterLocation < Error; end + class UndefinedOAuth2GrantType < Error; end end diff --git a/src/swagger/http/views/oauth2-redirect.html b/src/swagger/http/views/oauth2-redirect.html index fb68399..96c1cb9 100644 --- a/src/swagger/http/views/oauth2-redirect.html +++ b/src/swagger/http/views/oauth2-redirect.html @@ -41,6 +41,7 @@ } if (qp.code) { + delete oauth2.state; oauth2.auth.code = qp.code; oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl}); diff --git a/src/swagger/oauth2_flow.cr b/src/swagger/oauth2_flow.cr new file mode 100644 index 0000000..e790984 --- /dev/null +++ b/src/swagger/oauth2_flow.cr @@ -0,0 +1,18 @@ +module Swagger + # OAuth2 Flow Object + struct OAuth2Flow + property name + property authorization_url + property token_url + property refresh_url + property scopes + + def initialize(@name : String, @authorization_url : String? = nil, @token_url : String? = nil, + @refresh_url : String? = nil, @scopes : Hash(String, String)? = nil) + + unless Objects::OAuth2Flow::GRANT_TYPES.includes?(@name) + raise UndefinedOAuth2GrantType.new("Undefined grant type `#{@name}`, avaiabled in #{Objects::OAuth2Flow::GRANT_TYPES}") + end + end + end +end diff --git a/src/swagger/objects/oauth2_flow.cr b/src/swagger/objects/oauth2_flow.cr new file mode 100644 index 0000000..f4da65f --- /dev/null +++ b/src/swagger/objects/oauth2_flow.cr @@ -0,0 +1,22 @@ +module Swagger::Objects + # OAuth2 Flow Object + struct OAuth2Flow + include JSON::Serializable + + GRANT_TYPES = %w(authorizationCode implicit password clientCredentials) + + @[JSON::Field(key: "authorizationUrl")] + getter authorization_url : String? = nil + + @[JSON::Field(key: "tokenUrl")] + getter token_url : String? = nil + + @[JSON::Field(key: "refreshUrl")] + getter refresh_url : String? = nil + getter scopes : Hash(String, String)? = nil + + def initialize(@authorization_url : String? = nil, @token_url : String? = nil, + @refresh_url : String? = nil, @scopes : Hash(String, String)? = nil) + end + end +end diff --git a/src/swagger/objects/security_scheme.cr b/src/swagger/objects/security_scheme.cr index f1a6625..f59d092 100644 --- a/src/swagger/objects/security_scheme.cr +++ b/src/swagger/objects/security_scheme.cr @@ -13,6 +13,8 @@ module Swagger::Objects bearer(auth.description, auth.bearer_format) when Authorization::Type::APIKey api_key(auth.api_key_name.not_nil!, auth.parameter_location.not_nil!, auth.description) + when Authorization::Type::OAuth2 + oauth2(auth.oauth2_flows.not_nil!, auth.description) end end @@ -28,6 +30,14 @@ module Swagger::Objects new("apiKey", description, name: name, parameter_location: location) end + def self.oauth2(flows : Array(Swagger::OAuth2Flow), description : String? = nil) + object_flows = flows.each_with_object(Hash(String, OAuth2Flow).new) do |flow, obj| + obj[flow.name] = OAuth2Flow.new(flow.authorization_url, flow.token_url, flow.refresh_url) + end + + new("oauth2", description, flows: object_flows) + end + getter type : String? = nil getter description : String? = nil getter name : String? = nil @@ -40,7 +50,7 @@ module Swagger::Objects @[JSON::Field(key: "bearerFormat")] getter bearer_format : String? = nil - # getter flows : OpenAPIOAuthFlows? = nil + getter flows : Hash(String, OAuth2Flow)? = nil @[JSON::Field(key: "openIdConnectUrl")] getter open_id_connect_url : String? = nil @@ -50,8 +60,8 @@ module Swagger::Objects def initialize(@type : String? = nil, @description : String? = nil, @name : String? = nil, @parameter_location : String? = nil, @scheme : String? = nil, - @bearer_format : String? = nil, @open_id_connect_url : String? = nil, - @ref : String? = nil) + @bearer_format : String? = nil, @flows : Hash(String, OAuth2Flow)? = nil, + @open_id_connect_url : String? = nil, @ref : String? = nil) end end end