Skip to content

Commit

Permalink
Merge pull request #7 from suprjinx/interactors
Browse files Browse the repository at this point in the history
Interactors
  • Loading branch information
suprjinx authored Aug 29, 2024
2 parents e7997f7 + 4d5bf44 commit 9d00f85
Show file tree
Hide file tree
Showing 14 changed files with 159 additions and 7 deletions.
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
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 call" 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 "unsuccessful call" 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 call" 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 call" 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
24 changes: 24 additions & 0 deletions test/models/cert_isssue_request_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,30 @@ def setup
assert_includes @cert_issue_request.errors[:private_key_format], "is not included in the list"
end

test "#valid? should require a ttl greater than 0" do
@cert_issue_request.ttl = -1
assert_not @cert_issue_request.valid?
assert_includes @cert_issue_request.errors[:ttl], "must be greater than 0"
end

test "#valid? should require a ttl less than configured max" do
@cert_issue_request.ttl = Rails.configuration.astral[:cert_ttl] + 1
assert_not @cert_issue_request.valid?
assert_includes @cert_issue_request.errors[:ttl], "must be less than or equal to #{Rails.configuration.astral[:cert_ttl]}"
end

test "#valid? should prevent wildcard common_name" do
@cert_issue_request.common_name = "*.example.com"
assert_not @cert_issue_request.valid?
assert_includes @cert_issue_request.errors[:common_name], "cannot be a wildcard"
end

test "#valid? should prevent wildcard alt_names" do
@cert_issue_request.alt_names = [ "www.example.com", "*.example.com" ]
assert_not @cert_issue_request.valid?
assert_includes @cert_issue_request.errors[:alt_names], "cannot include a wildcard"
end

test "#new should have default values" do
@cert_issue_request = CertIssueRequest.new
assert_equal false, @cert_issue_request.exclude_cn_from_sans
Expand Down
1 change: 0 additions & 1 deletion test/test_helper.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
ENV["RAILS_ENV"] ||= "test"
require_relative "../config/environment"
require "rails/test_help"
require "minitest"
require "minitest/mock"

module ActiveSupport
Expand Down

0 comments on commit 9d00f85

Please sign in to comment.