lib/rodauth/features/oauth.rb in rodauth-oauth-0.0.6 vs lib/rodauth/features/oauth.rb in rodauth-oauth-0.1.0
- old
+ new
@@ -41,11 +41,10 @@
SCOPES = %w[profile.read].freeze
before "authorize"
after "authorize"
- after "authorize_failure"
before "token"
before "revoke"
after "revoke"
@@ -53,19 +52,17 @@
before "introspect"
before "create_oauth_application"
after "create_oauth_application"
- error_flash "OAuth Authorization invalid parameters", "oauth_grant_valid_parameters"
-
error_flash "Please authorize to continue", "require_authorization"
error_flash "There was an error registering your oauth application", "create_oauth_application"
notice_flash "Your oauth application has been registered", "create_oauth_application"
notice_flash "The oauth token has been revoked", "revoke_oauth_token"
- view "oauth_authorize", "Authorize", "authorize"
+ view "authorize", "Authorize", "authorize"
view "oauth_applications", "Oauth Applications", "oauth_applications"
view "oauth_application", "Oauth Application", "oauth_application"
view "new_oauth_application", "New Oauth Application", "new_oauth_application"
view "oauth_tokens", "Oauth Tokens", "oauth_tokens"
@@ -78,11 +75,11 @@
auth_value_method :use_oauth_access_type?, true
auth_value_method :oauth_require_pkce, false
auth_value_method :oauth_pkce_challenge_method, "S256"
- auth_value_method :oauth_valid_uri_schemes, %w[http https]
+ auth_value_method :oauth_valid_uri_schemes, %w[https]
auth_value_method :oauth_scope_separator, " "
# Application
APPLICATION_REQUIRED_PARAMS = %w[name description scopes homepage_url redirect_uri client_secret].freeze
@@ -146,13 +143,11 @@
auth_value_method :oauth_application_default_scope, SCOPES.first
auth_value_method :oauth_application_scopes, SCOPES
auth_value_method :oauth_token_type, "bearer"
- auth_value_method :invalid_request, "Request is missing a required parameter"
- auth_value_method :invalid_client, "Invalid client"
- auth_value_method :unauthorized_client, "Unauthorized client"
+ auth_value_method :invalid_client_message, "Invalid client"
auth_value_method :invalid_grant_type_message, "Invalid grant type"
auth_value_method :invalid_grant_message, "Invalid grant"
auth_value_method :invalid_scope_message, "Invalid scope"
auth_value_method :invalid_url_message, "Invalid URL"
@@ -193,15 +188,15 @@
SERVER_METADATA = OAuth::TtlStore.new
def check_csrf?
case request.path
- when oauth_token_path, oauth_introspect_path
+ when token_path, introspect_path
false
- when oauth_revoke_path
+ when revoke_path
!json_request?
- when oauth_authorize_path, %r{/#{oauth_applications_path}}
+ when authorize_path, %r{/#{oauth_applications_path}}
only_json? ? false : super
else
super
end
end
@@ -231,11 +226,19 @@
def initialize(scope)
@scope = scope
end
def scopes
- (param_or_nil("scope") || oauth_application_default_scope).split(" ")
+ scope = request.params["scope"]
+ case scope
+ when Array
+ scope
+ when String
+ scope.split(" ")
+ when nil
+ [oauth_application_default_scope]
+ end
end
def redirect_uri
param_or_nil("redirect_uri") || begin
return unless oauth_application
@@ -264,10 +267,12 @@
scheme, token = value.split(" ", 2)
return unless scheme.downcase == oauth_token_type
+ return if token.empty?
+
token
end
def authorization_token
return @authorization_token if defined?(@authorization_token)
@@ -407,11 +412,11 @@
def introspection_request(token_type_hint, token)
auth_url = URI(authorization_server_url)
http = Net::HTTP.new(auth_url.host, auth_url.port)
http.use_ssl = auth_url.scheme == "https"
- request = Net::HTTP::Post.new(oauth_introspect_path)
+ request = Net::HTTP::Post.new(introspect_path)
request["content-type"] = json_response_content_type
request["accept"] = json_response_content_type
request.body = JSON.dump({ "token_type_hint" => token_type_hint, "token" => token })
before_introspection_request(request)
@@ -692,18 +697,18 @@
# if there's a previous oauth grant for the params combo, it means that this user has approved before.
request.env["REQUEST_METHOD"] = "POST"
end
- def create_oauth_grant
- create_params = {
+ def create_oauth_grant(create_params = {})
+ create_params.merge!(
oauth_grants_account_id_column => account_id,
oauth_grants_oauth_application_id_column => oauth_application[oauth_applications_id_column],
oauth_grants_redirect_uri_column => redirect_uri,
oauth_grants_expires_in_column => Time.now + oauth_grant_expires_in,
oauth_grants_scopes_column => scopes.join(oauth_scope_separator)
- }
+ )
# Access Type flow
if use_oauth_access_type?
if (access_type = param_or_nil("access_type"))
create_params[oauth_grants_access_type_column] = access_type
@@ -733,10 +738,49 @@
rescue Sequel::UniqueConstraintViolation
retry
end
end
+ def do_authorize(redirect_url, query_params = [], fragment_params = [])
+ case param("response_type")
+ when "token"
+ redirect_response_error("invalid_request") unless use_oauth_implicit_grant_type?
+
+ fragment_params.replace(_do_authorize_token.map { |k, v| "#{k}=#{v}" })
+ when "code", "", nil
+ query_params.replace(_do_authorize_code.map { |k, v| "#{k}=#{v}" })
+ end
+
+ if param_or_nil("state")
+ if !fragment_params.empty?
+ fragment_params << "state=#{param('state')}"
+ else
+ query_params << "state=#{param('state')}"
+ end
+ end
+
+ query_params << redirect_url.query if redirect_url.query
+
+ redirect_url.query = query_params.join("&") unless query_params.empty?
+ redirect_url.fragment = fragment_params.join("&") unless fragment_params.empty?
+ end
+
+ def _do_authorize_code
+ { "code" => create_oauth_grant }
+ end
+
+ def _do_authorize_token
+ create_params = {
+ oauth_tokens_account_id_column => account_id,
+ oauth_tokens_oauth_application_id_column => oauth_application[oauth_applications_id_column],
+ oauth_tokens_scopes_column => scopes
+ }
+ oauth_token = generate_oauth_token(create_params, false)
+
+ json_access_token_payload(oauth_token)
+ end
+
# Access Tokens
def before_token
require_oauth_application
end
@@ -758,31 +802,46 @@
end
def create_oauth_token
case param("grant_type")
when "authorization_code"
- create_oauth_token_from_authorization_code(oauth_application)
+ # fetch oauth grant
+ oauth_grant = db[oauth_grants_table].where(
+ oauth_grants_code_column => param("code"),
+ oauth_grants_redirect_uri_column => param("redirect_uri"),
+ oauth_grants_oauth_application_id_column => oauth_application[oauth_applications_id_column],
+ oauth_grants_revoked_at_column => nil
+ ).where(Sequel[oauth_grants_expires_in_column] >= Sequel::CURRENT_TIMESTAMP)
+ .for_update
+ .first
+
+ redirect_response_error("invalid_grant") unless oauth_grant
+
+ create_params = {
+ oauth_tokens_account_id_column => oauth_grant[oauth_grants_account_id_column],
+ oauth_tokens_oauth_application_id_column => oauth_grant[oauth_grants_oauth_application_id_column],
+ oauth_tokens_oauth_grant_id_column => oauth_grant[oauth_grants_id_column],
+ oauth_tokens_scopes_column => oauth_grant[oauth_grants_scopes_column]
+ }
+ create_oauth_token_from_authorization_code(oauth_grant, create_params)
when "refresh_token"
- create_oauth_token_from_token(oauth_application)
+ # fetch oauth token
+ oauth_token = oauth_token_by_refresh_token(param("refresh_token"))
+
+ redirect_response_error("invalid_grant") unless oauth_token
+
+ update_params = {
+ oauth_tokens_oauth_application_id_column => oauth_token[oauth_grants_oauth_application_id_column],
+ oauth_tokens_expires_in_column => Time.now + oauth_token_expires_in
+ }
+ create_oauth_token_from_token(oauth_token, update_params)
else
redirect_response_error("invalid_grant")
end
end
- def create_oauth_token_from_authorization_code(oauth_application)
- # fetch oauth grant
- oauth_grant = db[oauth_grants_table].where(
- oauth_grants_code_column => param("code"),
- oauth_grants_redirect_uri_column => param("redirect_uri"),
- oauth_grants_oauth_application_id_column => oauth_application[oauth_applications_id_column],
- oauth_grants_revoked_at_column => nil
- ).where(Sequel[oauth_grants_expires_in_column] >= Sequel::CURRENT_TIMESTAMP)
- .for_update
- .first
-
- redirect_response_error("invalid_grant") unless oauth_grant
-
+ def create_oauth_token_from_authorization_code(oauth_grant, create_params)
# PKCE
if use_oauth_pkce?
if oauth_grant[oauth_grants_code_challenge_column]
code_verifier = param_or_nil("code_verifier")
@@ -790,40 +849,25 @@
elsif oauth_require_pkce
redirect_response_error("code_challenge_required")
end
end
- create_params = {
- oauth_tokens_account_id_column => oauth_grant[oauth_grants_account_id_column],
- oauth_tokens_oauth_application_id_column => oauth_grant[oauth_grants_oauth_application_id_column],
- oauth_tokens_oauth_grant_id_column => oauth_grant[oauth_grants_id_column],
- oauth_tokens_scopes_column => oauth_grant[oauth_grants_scopes_column]
- }
-
# revoke oauth grant
db[oauth_grants_table].where(oauth_grants_id_column => oauth_grant[oauth_grants_id_column])
.update(oauth_grants_revoked_at_column => Sequel::CURRENT_TIMESTAMP)
should_generate_refresh_token = !use_oauth_access_type? ||
oauth_grant[oauth_grants_access_type_column] == "offline"
generate_oauth_token(create_params, should_generate_refresh_token)
end
- def create_oauth_token_from_token(oauth_application)
- # fetch oauth token
- oauth_token = oauth_token_by_refresh_token(param("refresh_token"))
+ def create_oauth_token_from_token(oauth_token, update_params)
+ redirect_response_error("invalid_grant") unless token_from_application?(oauth_token, oauth_application)
- redirect_response_error("invalid_grant") unless oauth_token && token_from_application?(oauth_token, oauth_application)
-
token = oauth_unique_id_generator
- update_params = {
- oauth_tokens_oauth_application_id_column => oauth_token[oauth_grants_oauth_application_id_column],
- oauth_tokens_expires_in_column => Time.now + oauth_token_expires_in
- }
-
if oauth_tokens_token_hash_column
update_params[oauth_tokens_token_hash_column] = generate_token_hash(token)
else
update_params[oauth_tokens_token_column] = token
end
@@ -993,11 +1037,11 @@
def authorization_required
if accepts_json?
throw_json_response_error(authorization_required_error_status, "invalid_client")
else
set_redirect_error_flash(require_authorization_error_flash)
- redirect(oauth_authorize_path)
+ redirect(authorize_path)
end
end
def check_valid_uri?(uri)
URI::DEFAULT_PARSER.make_regexp(oauth_valid_uri_schemes).match?(uri)
@@ -1073,11 +1117,11 @@
# Server metadata
def oauth_server_metadata_body(path)
issuer = base_url
- issuer += "/#{path}" if issuer
+ issuer += "/#{path}" if path
responses_supported = %w[code]
response_modes_supported = %w[query]
grant_types_supported = %w[authorization_code]
@@ -1086,32 +1130,34 @@
response_modes_supported << "fragment"
grant_types_supported << "implicit"
end
{
issuer: issuer,
- authorization_endpoint: oauth_authorize_url,
- token_endpoint: oauth_token_url,
+ authorization_endpoint: authorize_url,
+ token_endpoint: token_url,
registration_endpoint: "#{base_url}/#{oauth_applications_path}",
scopes_supported: oauth_application_scopes,
response_types_supported: responses_supported,
response_modes_supported: response_modes_supported,
grant_types_supported: grant_types_supported,
token_endpoint_auth_methods_supported: %w[client_secret_basic client_secret_post],
service_documentation: oauth_metadata_service_documentation,
ui_locales_supported: oauth_metadata_ui_locales_supported,
op_policy_uri: oauth_metadata_op_policy_uri,
op_tos_uri: oauth_metadata_op_tos_uri,
- revocation_endpoint: oauth_revoke_url,
+ revocation_endpoint: revoke_url,
revocation_endpoint_auth_methods_supported: nil, # because it's client_secret_basic
- introspection_endpoint: oauth_introspect_url,
+ introspection_endpoint: introspect_url,
introspection_endpoint_auth_methods_supported: %w[client_secret_basic],
code_challenge_methods_supported: (use_oauth_pkce? ? oauth_pkce_challenge_method : nil)
}
end
- # /oauth-token
- route(:oauth_token) do |r|
+ # /token
+ route(:token) do |r|
+ next unless is_authorization_server?
+
before_token
r.post do
catch_error do
validate_oauth_token_params
@@ -1126,12 +1172,14 @@
throw_json_response_error(invalid_oauth_response_status, "invalid_request")
end
end
- # /oauth-introspect
- route(:oauth_introspect) do |r|
+ # /introspect
+ route(:introspect) do |r|
+ next unless is_authorization_server?
+
before_introspect
r.post do
catch_error do
validate_oauth_introspect_params
@@ -1157,12 +1205,14 @@
throw_json_response_error(invalid_oauth_response_status, "invalid_request")
end
end
- # /oauth-revoke
- route(:oauth_revoke) do |r|
+ # /revoke
+ route(:revoke) do |r|
+ next unless is_authorization_server?
+
before_revoke
r.post do
catch_error do
validate_oauth_revoke_params
@@ -1186,12 +1236,14 @@
redirect_response_error("invalid_request", request.referer || "/")
end
end
- # /oauth-authorize
- route(:oauth_authorize) do |r|
+ # /authorize
+ route(:authorize) do |r|
+ next unless is_authorization_server?
+
require_account
validate_oauth_grant_params
try_approval_prompt if use_oauth_access_type? && request.get?
before_authorize
@@ -1199,42 +1251,14 @@
r.get do
authorize_view
end
r.post do
- code = nil
- query_params = []
- fragment_params = []
+ redirect_url = URI.parse(redirect_uri)
transaction do
- case param("response_type")
- when "token"
- redirect_response_error("invalid_request") unless use_oauth_implicit_grant_type?
-
- create_params = {
- oauth_tokens_account_id_column => account_id,
- oauth_tokens_oauth_application_id_column => oauth_application[oauth_applications_id_column],
- oauth_tokens_scopes_column => scopes
- }
- oauth_token = generate_oauth_token(create_params, false)
-
- token_payload = json_access_token_payload(oauth_token)
- fragment_params.replace(token_payload.map { |k, v| "#{k}=#{v}" })
- when "code", "", nil
- code = create_oauth_grant
- query_params << "code=#{code}"
- else
- redirect_response_error("invalid_request")
- end
- after_authorize
+ do_authorize(redirect_url)
end
-
- redirect_url = URI.parse(redirect_uri)
- query_params << "state=#{param('state')}" if param_or_nil("state")
- query_params << redirect_url.query if redirect_url.query
- redirect_url.query = query_params.join("&") unless query_params.empty?
- redirect_url.fragment = fragment_params.join("&") unless fragment_params.empty?
-
redirect(redirect_url.to_s)
end
end
end
end