Skip to content

Commit

Permalink
Merge pull request #464 from m11o/feature/add-type-for-authentication…
Browse files Browse the repository at this point in the history
…-token

Add Types for ActionController::HttpAuthentication::Token
  • Loading branch information
pocke authored Jan 25, 2024
2 parents 6fa47a8 + 7593f57 commit 4da7ecb
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 136 deletions.
8 changes: 8 additions & 0 deletions gems/actionpack/6.0/_test/test.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
class ApplicationController < ActionController::Base
before_action :set_locale
before_action -> (controller) { self.controller_instance_method; controller.controller_instance_method }
before_action :authenticate

around_action -> (controller, block) { block.call; controller.controller_instance_method }

Expand All @@ -12,4 +13,11 @@ def self.after(_) end

def controller_instance_method
end

def authenticate
authenticate_or_request_with_http_token('realm') do |token, options|
puts options[:nonce]
ActiveSupport::SecurityUtils.secure_compare(token, 'secret')
end
end
end
1 change: 1 addition & 0 deletions gems/actionpack/6.0/_test/test.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ class ApplicationController < ActionController::Base
end

def controller_instance_method: () -> untyped
def authenticate: () -> untyped
end
34 changes: 34 additions & 0 deletions gems/actionpack/6.0/actioncontroller.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,40 @@ module ActionController
include ParamsWrapper
extend ParamsWrapper::ClassMethods
end

module HttpAuthentication
module Token
TOKEN_KEY: ::String
TOKEN_REGEX: ::Regexp
AUTHN_PAIR_DELIMITERS: ::Regexp

extend ::ActionController::HttpAuthentication::Token

module ControllerMethods
def authenticate_or_request_with_http_token: (?String realm, ?String? message) { (String token, ActiveSupport::HashWithIndifferentAccess[untyped, untyped] options) -> boolish } -> untyped

def authenticate_with_http_token: () { (String token, ActiveSupport::HashWithIndifferentAccess[untyped, untyped] options) -> boolish } -> boolish

def request_http_token_authentication: (?String realm, ?String? message) -> void
end

def authenticate: (instance controller) { (String token, ActiveSupport::HashWithIndifferentAccess[untyped, untyped] options) -> boolish } -> boolish

def token_and_options: (ActionDispatch::Request request) -> [String, ActiveSupport::HashWithIndifferentAccess[untyped, untyped]]?

def token_params_from: (String auth) -> Array[String]

def raw_params: (String auth) -> Array[String]

def params_array_from: (Array[String] raw_params) -> Array[String]

def rewrite_param_values: (Array[String] array_params) -> Array[String]

def encode_credentials: (String token, ?::Hash[untyped, untyped] options) -> ::String

def authentication_request: (instance controller, String realm, ?String? message) -> void
end
end
end

module AbstractController::Callbacks::ClassMethods
Expand Down
136 changes: 0 additions & 136 deletions gems/actionpack/6.0/actionpack-generated.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -1890,142 +1890,6 @@ module ActionController
# Opaque based on digest of secret key
def opaque: (untyped secret_key) -> untyped
end

# Makes it dead easy to do HTTP Token authentication.
#
# Simple Token example:
#
# class PostsController < ApplicationController
# TOKEN = "secret"
#
# before_action :authenticate, except: [ :index ]
#
# def index
# render plain: "Everyone can see me!"
# end
#
# def edit
# render plain: "I'm only accessible if you know the password"
# end
#
# private
# def authenticate
# authenticate_or_request_with_http_token do |token, options|
# # Compare the tokens in a time-constant manner, to mitigate
# # timing attacks.
# ActiveSupport::SecurityUtils.secure_compare(token, TOKEN)
# end
# end
# end
#
#
# Here is a more advanced Token example where only Atom feeds and the XML API is protected by HTTP token authentication,
# the regular HTML interface is protected by a session approach:
#
# class ApplicationController < ActionController::Base
# before_action :set_account, :authenticate
#
# private
# def set_account
# @account = Account.find_by(url_name: request.subdomains.first)
# end
#
# def authenticate
# case request.format
# when Mime[:xml], Mime[:atom]
# if user = authenticate_with_http_token { |t, o| @account.users.authenticate(t, o) }
# @current_user = user
# else
# request_http_token_authentication
# end
# else
# if session_authenticated?
# @current_user = @account.users.find(session[:authenticated][:user_id])
# else
# redirect_to(login_url) and return false
# end
# end
# end
# end
#
#
# In your integration tests, you can do something like this:
#
# def test_access_granted_from_xml
# authorization = ActionController::HttpAuthentication::Token.encode_credentials(users(:dhh).token)
#
# get "/notes/1.xml", headers: { 'HTTP_AUTHORIZATION' => authorization }
#
# assert_equal 200, status
# end
#
#
# On shared hosts, Apache sometimes doesn't pass authentication headers to
# FCGI instances. If your environment matches this description and you cannot
# authenticate, try this rule in your Apache setup:
#
# RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L]
module Token
TOKEN_KEY: ::String

TOKEN_REGEX: untyped

AUTHN_PAIR_DELIMITERS: untyped

extend ::ActionController::HttpAuthentication::Token

module ControllerMethods
def authenticate_or_request_with_http_token: (?::String realm, ?untyped? message) { () -> untyped } -> untyped

def authenticate_with_http_token: () { () -> untyped } -> untyped

def request_http_token_authentication: (?::String realm, ?untyped? message) -> untyped
end

def authenticate: (untyped controller) { () -> untyped } -> untyped

# Parses the token and options out of the token Authorization header.
# The value for the Authorization header is expected to have the prefix
# <tt>"Token"</tt> or <tt>"Bearer"</tt>. If the header looks like this:
# Authorization: Token token="abc", nonce="def"
# Then the returned token is <tt>"abc"</tt>, and the options are
# <tt>{nonce: "def"}</tt>
#
# request - ActionDispatch::Request instance with the current headers.
#
# Returns an +Array+ of <tt>[String, Hash]</tt> if a token is present.
# Returns +nil+ if no token is found.
def token_and_options: (untyped request) -> untyped

def token_params_from: (untyped auth) -> untyped

# Takes raw_params and turns it into an array of parameters
def params_array_from: (untyped raw_params) -> untyped

# This removes the <tt>"</tt> characters wrapping the value.
def rewrite_param_values: (untyped array_params) -> untyped

# This method takes an authorization body and splits up the key-value
# pairs by the standardized <tt>:</tt>, <tt>;</tt>, or <tt>\t</tt>
# delimiters defined in +AUTHN_PAIR_DELIMITERS+.
def raw_params: (untyped auth) -> untyped

# Encodes the given token and options into an Authorization header value.
#
# token - String token.
# options - optional Hash of the options.
#
# Returns String.
def encode_credentials: (untyped token, ?::Hash[untyped, untyped] options) -> ::String

# Sets a WWW-Authenticate header to let the client know a token is desired.
#
# controller - ActionController::Base instance for the outgoing response.
# realm - String realm to use in the header.
#
# Returns nothing.
def authentication_request: (untyped controller, untyped realm, ?untyped? message) -> untyped
end
end
end

Expand Down

0 comments on commit 4da7ecb

Please sign in to comment.