Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interactors #7

Merged
merged 10 commits into from
Aug 29, 2024
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ gem "bootsnap", require: false
# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin Ajax possible
# gem "rack-cors"

# High-level app logic
gem "interactor", "~> 3.0"

# Use the vault-ruby gem to interact with HashiCorp Vault
gem "vault"

Expand Down
2 changes: 2 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ GEM
activesupport (>= 6.1)
i18n (1.14.5)
concurrent-ruby (~> 1.0)
interactor (3.1.2)
io-console (0.7.2)
irb (1.14.0)
rdoc (>= 4.0.0)
Expand Down Expand Up @@ -270,6 +271,7 @@ DEPENDENCIES
bootsnap
brakeman
debug
interactor (~> 3.0)
jwt
puma (>= 5.0)
rails (~> 7.2.1)
Expand Down
9 changes: 6 additions & 3 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@ def info
end

def authenticate_request
token = request.headers["Authorization"]
token = token.split(" ").last if token
@identity = Services::AuthService.new.authenticate!(token)
result = AuthenticateIdentity.call(request: request)
if result.success?
@identity = result.identity
else
raise AuthError.new result.message
end
end

private
Expand Down
8 changes: 6 additions & 2 deletions app/controllers/certificates_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@ def create
req = CertIssueRequest.new(params_permitted)
if !req.valid?
render json: { error: req.errors }, status: :bad_request
end
result = IssueCert.call(request: req)
if result.success?
# TODO use jbuilder to make the json
render json: result.cert
else
cert = Services::CertificateService.new.issue_cert(req)
render json: cert
raise StandardError.new result.message
end
end

Expand Down
16 changes: 16 additions & 0 deletions app/interactors/authenticate_identity.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
class AuthenticateIdentity
include Interactor

before do
token = context.request.headers["Authorization"]
context.token = token.split(" ").last if token
end

def call
if identity = Services::AuthService.new.authenticate!(context.token)
context.identity = identity
else
context.fail!(message: "Invalid token")
end
end
end
6 changes: 6 additions & 0 deletions app/interactors/check_policy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class CheckPolicy
include Interactor

def call
end
end
5 changes: 5 additions & 0 deletions app/interactors/issue_cert.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class IssueCert
include Interactor::Organizer

organize CheckPolicy, ObtainCert, Log
end
6 changes: 6 additions & 0 deletions app/interactors/log.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class Log
include Interactor

def call
end
end
11 changes: 11 additions & 0 deletions app/interactors/obtain_cert.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class ObtainCert
include Interactor

def call
if cert = Services::CertificateService.new.issue_cert(context.request)
context.cert = cert
else
context.fail!(message: "Failed to issue certificate")
end
end
end
15 changes: 14 additions & 1 deletion app/models/cert_issue_request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,22 @@ class CertIssueRequest
validates :common_name, presence: true
validates :format, presence: true, inclusion: { in: %w[pem der pem_bundle] }
validates :private_key_format, presence: true, inclusion: { in: %w[pem der pkcs8] }

validates :ttl, numericality: {
less_than_or_equal_to: Rails.configuration.astral[:cert_ttl],
greater_than: 0
}
validate :validate_no_wildcards

def fqdns
alt_names + [ common_name ]
end

def validate_no_wildcards
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we get a little unit test on this bad boy?

if common_name.present?
errors.add(:common_name, "cannot be a wildcard") if common_name.start_with? "*"
end
alt_names.each do |fqdn|
errors.add(:alt_names, "cannot include a wildcard") if fqdn.start_with? "*"
end
end
end
30 changes: 30 additions & 0 deletions test/interactors/authenticate_identity_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
require "test_helper"

class AuthenticateIdentityTest < ActiveSupport::TestCase
def setup
@interactor = AuthenticateIdentity
@identity = Identity.new(subject: "[email protected]", groups: [ "admin_group" ])
end

test "successful authentication" do
request = OpenStruct.new(headers: { "Authorization" => "Bearer valid_token" })
srv = Minitest::Mock.new
srv.expect :authenticate!, @identity, [ "valid_token" ]
Services::AuthService.stub :new, srv do
context = @interactor.call(request: request)
assert context.success?
assert_equal @identity, context.identity
end
end

test "failed authentication" do
request = OpenStruct.new(headers: { "Authorization" => "Bearer invalid_token" })
srv = Minitest::Mock.new
srv.expect :authenticate!, nil, [ "invalid_token" ]
Services::AuthService.stub :new, srv do
context = @interactor.call(request: request)
assert_not context.success?
assert_nil context.identity
end
end
end
30 changes: 30 additions & 0 deletions test/interactors/obtain_cert_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
require "test_helper"

class ObtainCertTest < ActiveSupport::TestCase
def setup
@interactor = ObtainCert
@cert = OpenStruct.new(certificate: "certificate", ca_chain: "ca_chain")
end

test "successful issue" do
request = CertIssueRequest.new
srv = Minitest::Mock.new
srv.expect :issue_cert, @cert, [ request ]
Services::CertificateService.stub :new, srv do
context = @interactor.call(request: request)
assert context.success?
assert_equal @cert, context.cert
end
end

test "unsuccessful issue" do
request = CertIssueRequest.new
srv = Minitest::Mock.new
srv.expect :issue_cert, nil, [ request ]
Services::CertificateService.stub :new, srv do
context = @interactor.call(request: request)
assert context.failure?
assert_equal nil, context.cert
end
end
end