padlock_auth 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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: []