padlock_auth 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PadlockAuth
4
+ module Rails
5
+ ##
6
+ # Responsible for extracting access tokens from requests,
7
+ # and delgating the creation of access tokens to the configured strategy.
8
+ class TokenFactory
9
+ class << self
10
+ # Retreives the access token from the request using the configured methods.
11
+ def from_request(request, *methods)
12
+ methods.inject(nil) do |_, method|
13
+ method = self.method(method) if method.is_a?(Symbol)
14
+ credentials = method.call(request)
15
+ break credentials if credentials.present?
16
+ end
17
+ end
18
+
19
+ # Retreives the access token from the request, and builds an access token object.
20
+ def authenticate(request)
21
+ if (token = from_request(request, *PadlockAuth.config.access_token_methods))
22
+ if token.is_a?(Array)
23
+ PadlockAuth.build_access_token_from_credentials(*token)
24
+ else
25
+ PadlockAuth.build_access_token(token)
26
+ end
27
+ end
28
+ end
29
+
30
+ # Extracts the access token from the `access_token` parameter.
31
+ #
32
+ # @param request [ActionDispatch::Request] request
33
+ #
34
+ # @return [String, nil] Access token
35
+ #
36
+ def from_access_token_param(request)
37
+ request.parameters[:access_token]
38
+ end
39
+
40
+ # Extracts the access token from the `bearer_token` parameter.
41
+ #
42
+ # @param request [ActionDispatch::Request] request
43
+ #
44
+ # @return [String, nil] Access token
45
+ #
46
+ def from_bearer_param(request)
47
+ request.parameters[:bearer_token]
48
+ end
49
+
50
+ # Extracts a Bearer access token from the `Authorization` header.
51
+ #
52
+ # @param request [ActionDispatch::Request] request
53
+ #
54
+ # @return [String, nil] Access token
55
+ #
56
+ def from_bearer_authorization(request)
57
+ pattern = /^Bearer /i
58
+ header = request.authorization
59
+ token_from_header(header, pattern) if match?(header, pattern)
60
+ end
61
+
62
+ # Extracts Basic Auth credentials from the `Authorization` header.
63
+ #
64
+ # @param request [ActionDispatch::Request] request
65
+ #
66
+ # @return [Array, nil] Username and password
67
+ #
68
+ def from_basic_authorization(request)
69
+ pattern = /^Basic /i
70
+ header = request.authorization
71
+ token_from_basic_header(header, pattern) if match?(header, pattern)
72
+ end
73
+
74
+ private
75
+
76
+ def token_from_basic_header(header, pattern)
77
+ encoded_header = token_from_header(header, pattern)
78
+ decode_basic_credentials_token(encoded_header)
79
+ end
80
+
81
+ def decode_basic_credentials_token(encoded_header)
82
+ Base64.decode64(encoded_header).split(":", 2)
83
+ end
84
+
85
+ def token_from_header(header, pattern)
86
+ header.gsub(pattern, "")
87
+ end
88
+
89
+ def match?(header, pattern)
90
+ header&.match(pattern)
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,22 @@
1
+ module PadlockAuth
2
+ ##
3
+ # Railtie for PadlockAuth.
4
+ #
5
+ # Provides `padlock_authorize!` and `padlock_auth_token` methods to Rails controllers.
6
+ #
7
+ # Also adds PadlockAuth's locales to I18n.load_path.
8
+ #
9
+ class Railtie < ::Rails::Railtie
10
+ initializer "padlock_auth.helpers" do
11
+ ActiveSupport.on_load(:action_controller) do
12
+ include PadlockAuth::Rails::Helpers
13
+ end
14
+ end
15
+
16
+ initializer "padlock_auth.i18n" do
17
+ Dir.glob(File.join(File.dirname(__FILE__), "..", "..", "config", "locales", "*.yml")).each do |file|
18
+ I18n.load_path << File.expand_path(file)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,46 @@
1
+ # This module provides matchers for testing PadlockAuth access_token and strategy
2
+ # classes.
3
+ #
4
+ # In your implementations, use these shared examples to ensure that your classes
5
+ # are compliant with the PadlockAuth API.
6
+ #
7
+ # @example
8
+ # require "padlock_auth/rspec_support"
9
+ #
10
+ # RSpec.describe MyAccessToken do
11
+ # it { is_expected.to be_a_padlock_auth_access_token }
12
+ # end
13
+ #
14
+ # @example
15
+ # require "padlock_auth/rspec_support"
16
+ #
17
+ # RSpec.describe MyStrategy do
18
+ # it { is_expected.to be_a_padlock_auth_strategy }
19
+ # end
20
+ module PadlockAuth::Matchers
21
+ # Asserts that the subject matches the expected interface for a PadlockAuth access token.
22
+ #
23
+ # @return [RSpec::Matchers::BuiltIn::BaseMatcher]
24
+ #
25
+ def be_a_padlock_auth_access_token
26
+ respond_to(:acceptable?).with(1).arguments
27
+ .and(respond_to(:accessible?).with(0).arguments)
28
+ .and(respond_to(:includes_scope?).with(1).arguments)
29
+ .and(respond_to(:invalid_token_reason).with(0).arguments)
30
+ .and(respond_to(:forbidden_token_reason).with(0).arguments)
31
+ end
32
+
33
+ # Asserts that the subject matches the expected interface for a PadlockAuth strategy.
34
+ #
35
+ # @return [RSpec::Matchers::BuiltIn::BaseMatcher]
36
+ #
37
+ def be_a_padlock_auth_strategy
38
+ respond_to(:build_access_token).with(1).arguments
39
+ .and(respond_to(:build_invalid_token_response).with(1).arguments)
40
+ .and(respond_to(:build_forbidden_token_response).with(2).arguments)
41
+ end
42
+ end
43
+
44
+ RSpec.configure do |config|
45
+ config.include(PadlockAuth::Matchers)
46
+ end
@@ -0,0 +1,55 @@
1
+ module PadlockAuth
2
+ module Token
3
+ ##
4
+ # Access token for simple token authentication.
5
+ #
6
+ # Represents a string token that is compared to a secret key.
7
+ #
8
+ # Does not allow for scopes, so it will always return false for any required
9
+ class AccessToken < PadlockAuth::AbstractAccessToken
10
+ include PadlockAuth::Mixins::HideAttribute
11
+
12
+ hide_attribute :token
13
+ hide_attribute :secret_key
14
+
15
+ # Initialize the access token with a token and secret key.
16
+ #
17
+ # @param token [String] The token
18
+ #
19
+ # @param secret_key [String] The secret key
20
+ #
21
+ def initialize(token, secret_key)
22
+ @token = token
23
+ @secret_key = secret_key
24
+ end
25
+
26
+ # Check if the token matches the secret key.
27
+ #
28
+ # @return [Boolean] true if the token matches the secret key
29
+ #
30
+ def accessible?
31
+ # Compare the tokens in a time-constant manner, to mitigate timing attacks.
32
+ ActiveSupport::SecurityUtils.secure_compare(@token, @secret_key)
33
+ end
34
+
35
+ # Check if the token includes the required scopes.
36
+ #
37
+ # Simple tokens do not include scopes, so this method will return false
38
+ # for any required scopes.
39
+ #
40
+ # @return [Boolean] true if the token includes the required scopes
41
+ #
42
+ def includes_scope?(required_scopes)
43
+ required_scopes.none?.tap do |result|
44
+ Kernel.warn "[PADLOCK_AUTH] #{self.class} does not permit any required scopes" unless result
45
+ end
46
+ end
47
+
48
+ # The token secret_key does not permit any required scopes, so display a generic message
49
+ #
50
+ def forbidden_token_reason
51
+ :unknown
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,80 @@
1
+ module PadlockAuth
2
+ module Token
3
+ ##
4
+ # Strategy for token-based authentication.
5
+ #
6
+ # This strategy compares a token from the request to a secret key.
7
+ #
8
+ # It does not allow for scopes, so it will always return false for any required scopes.
9
+ #
10
+ class Strategy < PadlockAuth::AbstractStrategy
11
+ include PadlockAuth::Mixins::BuildWith
12
+ include PadlockAuth::Mixins::HideAttribute
13
+
14
+ # The configuration builder for `PadlockAuth::Token::Strategy`.
15
+ class Builder < PadlockAuth::Utils::AbstractBuilder; end
16
+
17
+ # @!method build
18
+ # @!scope class
19
+ #
20
+ # Builds the stratey instance.
21
+ #
22
+ # @yield block to configure the strategy
23
+ #
24
+ # @return [PadlockAuth::Token::Strategy] the strategy instance
25
+ #
26
+ # @example
27
+ # PadlockAuth::Token::Strategy.build do
28
+ # secret_key "my_secret_key"
29
+ # end
30
+ #
31
+ # @see PadlockAuth::Config::Builder#secure_with
32
+ build_with Builder
33
+
34
+ extend PadlockAuth::Config::Option
35
+
36
+ # @!attribute [r] secret_key
37
+ #
38
+ # The secret key used to build and authenticate access tokens.
39
+ #
40
+ # @return [String] the secret key
41
+ option :secret_key
42
+
43
+ hide_attribute :secret_key
44
+
45
+ # Builds an access token from a raw token.
46
+ #
47
+ # @param raw_token [String] The raw token from the request
48
+ #
49
+ # @return [PadlockAuth::Token::AccessToken] The access token
50
+ #
51
+ def build_access_token(raw_token)
52
+ PadlockAuth::Token::AccessToken.new(raw_token, secret_key)
53
+ end
54
+
55
+ # Builds an access token from username and password credentials.
56
+ #
57
+ # Only the password is required for this strategy, so the username is ignored.
58
+ #
59
+ # @param _username [String] The (ignored) username
60
+ #
61
+ # @param password [String] The password
62
+ #
63
+ # @return [PadlockAuth::Token::AccessToken] The access token
64
+ #
65
+ def build_access_token_from_credentials(_username, password)
66
+ PadlockAuth::Token::AccessToken.new(password, secret_key)
67
+ end
68
+
69
+ # @api private
70
+ #
71
+ # Called by the builder to validate the configuration.
72
+ #
73
+ # @raise [ArgumentError] If the secret key is missing
74
+ #
75
+ def validate!
76
+ raise ArgumentError, "secret_key is required" unless secret_key.present?
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PadlockAuth
4
+ module Utils
5
+ # Abstract base class for implementing configuration builders.
6
+ #
7
+ # Define a `validate!` method on the configuration instance to validate the configuration.
8
+ #
9
+ # @abstract
10
+ #
11
+ # @example
12
+ # class MyConfig
13
+ # class Builder < PadlockAuth::Utils::AbstractBuilder
14
+ # def name(value)
15
+ # config.instance_variable_set(:@name, value)
16
+ # end
17
+ # end
18
+ #
19
+ # def self.build(&block)
20
+ # Builder.new(self, &block).build
21
+ # end
22
+ #
23
+ # attr_reader :name
24
+ # end
25
+ #
26
+ # config = MyConfig.build do
27
+ # name 'My Name'
28
+ # end
29
+ #
30
+ class AbstractBuilder
31
+ # @return [Object] the instance being configured
32
+ attr_reader :config
33
+
34
+ # @param [Class] config instance
35
+ #
36
+ # @yield block to configure the instance
37
+ #
38
+ def initialize(config, &)
39
+ @config = config
40
+ instance_eval(&) if block_given?
41
+ end
42
+
43
+ # Builds and validates configuration.
44
+ #
45
+ # Invokes `validate!` on the configuration instance if it responds to it.
46
+ #
47
+ # @return [Object] the config instance
48
+ #
49
+ def build
50
+ @config.validate! if @config.respond_to?(:validate!)
51
+ @config
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,4 @@
1
+ module PadlockAuth
2
+ # PadlockAuth version.
3
+ VERSION = "0.1.0"
4
+ end
@@ -0,0 +1,76 @@
1
+ require "rails"
2
+
3
+ require "padlock_auth/version"
4
+ require "padlock_auth/railtie"
5
+ require "padlock_auth/errors"
6
+
7
+ # PadlockAuth allows you to secure your Rails application using access tokens
8
+ # provided by an external provider.
9
+ #
10
+ module PadlockAuth
11
+ # Abstract classes recommended for extension
12
+ autoload :AbstractAccessToken, "padlock_auth/abstract_access_token"
13
+ autoload :AbstractStrategy, "padlock_auth/abstract_strategy"
14
+
15
+ # Configuration classes
16
+ autoload :Config, "padlock_auth/config"
17
+
18
+ # Token stategy classes
19
+ module Token
20
+ autoload :AccessToken, "padlock_auth/token/access_token"
21
+ autoload :Strategy, "padlock_auth/token/strategy"
22
+ end
23
+
24
+ # Mixins for extending classes
25
+ module Mixins
26
+ autoload :BuildWith, "padlock_auth/mixins/build_with"
27
+ autoload :HideAttribute, "padlock_auth/mixins/hide_attribute"
28
+ end
29
+
30
+ module Utils
31
+ autoload :AbstractBuilder, "padlock_auth/utils/abstract_builder"
32
+ end
33
+
34
+ # HTTP response classes
35
+ module Http
36
+ autoload :ErrorResponse, "padlock_auth/http/error_response"
37
+ autoload :ForbiddenTokenResponse, "padlock_auth/http/forbidden_token_response"
38
+ autoload :InvalidTokenResponse, "padlock_auth/http/invalid_token_response"
39
+ end
40
+
41
+ # Rails-specific classes
42
+ module Rails
43
+ autoload :ConnectionHelpers, "padlock_auth/rails/connection_helpers"
44
+ autoload :Helpers, "padlock_auth/rails/helpers"
45
+ autoload :TokenFactory, "padlock_auth/rails/token_factory"
46
+ end
47
+
48
+ class << self
49
+ # Configure PadlockAuth.
50
+ #
51
+ # @yield [PadlockAuth::Config] configuration block
52
+ #
53
+ def configure(&)
54
+ @config = Config.build(&)
55
+ end
56
+
57
+ # @return [PadlockAuth::Config] configuration instance
58
+ #
59
+ def configuration
60
+ @config || configure
61
+ end
62
+
63
+ alias_method :config, :configuration
64
+
65
+ ### Strategy Delegation ###
66
+
67
+ delegate :build_access_token,
68
+ :build_access_token_from_credentials,
69
+ :build_invalid_token_response,
70
+ :build_forbidden_token_response,
71
+ to: :strategy
72
+
73
+ delegate :strategy, to: :config
74
+ private :strategy
75
+ end
76
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :padlock_auth do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,143 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: padlock_auth
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ben Morrall
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-12-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 7.2.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 7.2.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: yard
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec-rails
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: simplecov
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: standard
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: 1.41.1
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: 1.41.1
83
+ description: PadlockAuth allows you to secure your Rails application using access
84
+ tokens provided by an external provider.
85
+ email:
86
+ - bemo56@hotmail.com
87
+ executables: []
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - MIT-LICENSE
92
+ - README.md
93
+ - Rakefile
94
+ - config/locales/padlock_auth.en.yml
95
+ - lib/padlock_auth.rb
96
+ - lib/padlock_auth/abstract_access_token.rb
97
+ - lib/padlock_auth/abstract_strategy.rb
98
+ - lib/padlock_auth/config.rb
99
+ - lib/padlock_auth/config/option.rb
100
+ - lib/padlock_auth/config/scopes.rb
101
+ - lib/padlock_auth/errors.rb
102
+ - lib/padlock_auth/http/error_response.rb
103
+ - lib/padlock_auth/http/forbidden_token_response.rb
104
+ - lib/padlock_auth/http/invalid_token_response.rb
105
+ - lib/padlock_auth/mixins/build_with.rb
106
+ - lib/padlock_auth/mixins/hide_attribute.rb
107
+ - lib/padlock_auth/rails/helpers.rb
108
+ - lib/padlock_auth/rails/token_factory.rb
109
+ - lib/padlock_auth/railtie.rb
110
+ - lib/padlock_auth/rspec_support.rb
111
+ - lib/padlock_auth/token/access_token.rb
112
+ - lib/padlock_auth/token/strategy.rb
113
+ - lib/padlock_auth/utils/abstract_builder.rb
114
+ - lib/padlock_auth/version.rb
115
+ - lib/tasks/padlock_auth_tasks.rake
116
+ homepage: http://github.com/bmorrall/padlock_auth
117
+ licenses:
118
+ - MIT
119
+ metadata:
120
+ homepage_uri: http://github.com/bmorrall/padlock_auth
121
+ source_code_uri: https://github.com/bmorrall/padlock_auth
122
+ changelog_uri: https://github.com/bmorrall/padlock_auth/blob/main/CHANGELOG.md
123
+ post_install_message:
124
+ rdoc_options: []
125
+ require_paths:
126
+ - lib
127
+ required_ruby_version: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ required_rubygems_version: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ version: '0'
137
+ requirements: []
138
+ rubygems_version: 3.5.17
139
+ signing_key:
140
+ specification_version: 4
141
+ summary: Secure your Rails application using access tokens provided by an external
142
+ provider.
143
+ test_files: []