diff --git a/.devcontainer/app_reg_db.json b/.devcontainer/app_reg_db.json new file mode 100644 index 0000000..5197214 --- /dev/null +++ b/.devcontainer/app_reg_db.json @@ -0,0 +1,18 @@ +{ + "domain-names": [ + { + "id": "example.com", + "fullyQualifiedDomainName": "example.com", + "ownerDelegatedRequestsToTeam": true, + "autoApprovedGroups": "group1", + "autoApprovedServiceAccounts": "john.doe@example.com" + }, + { + "id": "example2.com", + "fullyQualifiedDomainName": "example2.com", + "ownerDelegatedRequestsToTeam": true, + "autoApprovedGroups": "group1", + "autoApprovedServiceAccounts": "john.doe@example.com" + } + ] +} \ No newline at end of file diff --git a/.devcontainer/app_reg_routes.json b/.devcontainer/app_reg_routes.json new file mode 100644 index 0000000..ba3845e --- /dev/null +++ b/.devcontainer/app_reg_routes.json @@ -0,0 +1,3 @@ +{ + "/api/v1beta1/*": "/$1" +} diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 56426d5..350315d 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -19,6 +19,8 @@ services: VAULT_ADDR: http://10.1.10.100:8200 VAULT_TOKEN: root_token JWT_SIGNING_KEY: jwt_secret + APP_REGISTRY_ADDR: http://10.1.10.150:8800 + APP_REGISTRY_TOKEN: app_reg_token vault: image: hashicorp/vault:latest @@ -32,6 +34,20 @@ services: astral: ipv4_address: "10.1.10.100" + app_registry: + image: node:latest + restart: unless-stopped + ports: + - 8800:8800 + volumes: + - .:/data + networks: + astral: + ipv4_address: "10.1.10.150" + command: > + sh -c "npm install -g json-server@0.17.4 && + json-server /data/app_reg_db.json --routes /data/app_reg_routes.json --port 8800 --host 0.0.0.0" + networks: astral: ipam: diff --git a/Gemfile b/Gemfile index c062096..abcd601 100644 --- a/Gemfile +++ b/Gemfile @@ -6,8 +6,7 @@ gem "rails", "~> 7.2.1" gem "sqlite3", ">= 1.4" # Use the Puma web server [https://github.com/puma/puma] gem "puma", ">= 5.0" -# Build JSON APIs with ease [https://github.com/rails/jbuilder] -# gem "jbuilder" + # Use Redis adapter to run Action Cable in production # gem "redis", ">= 4.0.1" @@ -38,9 +37,13 @@ gem "vault" # Use the jwt gem to decode access tokens gem "jwt" -# Use the jbuilder gem +# Use the jbuilder gem to render JSON views gem "jbuilder" +# Use the faraday gem for http client operations +gem "faraday" +gem "faraday-retry" + group :development, :test do # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem gem "debug", platforms: %i[ mri mswin ], require: "debug/prelude" diff --git a/Gemfile.lock b/Gemfile.lock index 6ab9662..61be8b8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -91,6 +91,13 @@ GEM reline (>= 0.3.8) drb (2.2.1) erubi (1.13.0) + faraday (2.11.0) + faraday-net_http (>= 2.0, < 3.4) + logger + faraday-net_http (3.3.0) + net-http + faraday-retry (2.2.1) + faraday (~> 2.0) globalid (1.2.1) activesupport (>= 6.1) i18n (1.14.5) @@ -120,6 +127,8 @@ GEM mini_mime (1.1.5) minitest (5.25.1) msgpack (1.7.2) + net-http (0.4.1) + uri net-imap (0.4.14) date net-protocol @@ -245,6 +254,7 @@ GEM tzinfo (2.0.6) concurrent-ruby (~> 1.0) unicode-display_width (2.5.0) + uri (0.13.1) useragent (0.16.10) vault (0.18.2) aws-sigv4 @@ -274,6 +284,8 @@ DEPENDENCIES bootsnap brakeman debug + faraday + faraday-retry interactor (~> 3.0) jbuilder jwt diff --git a/app/interactors/authenticate_identity.rb b/app/interactors/authenticate_identity.rb index 8626708..b7462e8 100644 --- a/app/interactors/authenticate_identity.rb +++ b/app/interactors/authenticate_identity.rb @@ -8,7 +8,7 @@ class AuthenticateIdentity end def call - if identity = Services::AuthService.new.authenticate!(context.token) + if identity = Services::AuthService.authenticate!(context.token) context.identity = identity else context.fail!(message: "Invalid token") diff --git a/app/interactors/authorize_request.rb b/app/interactors/authorize_request.rb index bbcef74..d768383 100644 --- a/app/interactors/authorize_request.rb +++ b/app/interactors/authorize_request.rb @@ -3,6 +3,13 @@ class AuthorizeRequest include FailOnError def call - Services::DomainOwnershipService.new.authorize!(context.identity, context.request) + context.request.fqdns.each do |fqdn| + domain = Domain.where(fqdn: fqdn).first + raise AuthError.new("Common or alt name not recognized") unless domain + raise AuthError.new("No subject or group authorization") unless + domain.users_array.include?(context.identity.subject) || + (domain.group_delegation? && (domain.groups_array & context.identity.groups).any?) + end + nil end end diff --git a/app/interactors/fail_on_error.rb b/app/interactors/fail_on_error.rb index 76d5723..d164847 100644 --- a/app/interactors/fail_on_error.rb +++ b/app/interactors/fail_on_error.rb @@ -4,7 +4,10 @@ module FailOnError included do around do |interactor| interactor.call + rescue Interactor::Failure => e + raise e rescue => e + Rails.logger.error("Error in #{self.class.name}: #{e.class.name} - #{e.message}") context.fail!(error: e) end end diff --git a/app/interactors/issue_cert.rb b/app/interactors/issue_cert.rb index 9a80f80..667043f 100644 --- a/app/interactors/issue_cert.rb +++ b/app/interactors/issue_cert.rb @@ -1,5 +1,6 @@ class IssueCert include Interactor::Organizer + include FailOnError - organize AuthorizeRequest, ObtainCert, Log + organize RefreshDomain, AuthorizeRequest, ObtainCert, Log end diff --git a/app/interactors/obtain_cert.rb b/app/interactors/obtain_cert.rb index 8cd9b87..54c893a 100644 --- a/app/interactors/obtain_cert.rb +++ b/app/interactors/obtain_cert.rb @@ -3,7 +3,7 @@ class ObtainCert include FailOnError def call - if cert = Services::CertificateService.new.issue_cert(context.request) + if cert = Services::CertificateService.issue_cert(context.request) context.cert = cert else context.fail!(message: "Failed to issue certificate") diff --git a/app/interactors/refresh_domain.rb b/app/interactors/refresh_domain.rb new file mode 100644 index 0000000..bae405a --- /dev/null +++ b/app/interactors/refresh_domain.rb @@ -0,0 +1,20 @@ +class RefreshDomain + include Interactor + + def call + domain_info = Services::DomainOwnershipService.get_domain_info(context.request.common_name) + domain_record = Domain.find_or_create_by!(fqdn: context.request.common_name) + if !domain_info + domain_record.destroy! + return + end + + domain_record.update!( + group_delegation: domain_info.group_delegation, + groups: domain_info.groups, + users: domain_info.users + ) + rescue => e + Rails.logger.warn("Continuing after error in #{self.class.name}: #{e.class.name}: #{e.message}") + end +end diff --git a/app/lib/services/app_registry_service.rb b/app/lib/services/app_registry_service.rb new file mode 100644 index 0000000..3424fc8 --- /dev/null +++ b/app/lib/services/app_registry_service.rb @@ -0,0 +1,53 @@ +module Services + class AppRegistryService + class << self + def get_domain_info(fqdn) + rslt = client.get("/api/v1beta1/domain-names/#{fqdn}").body + convert(rslt) + rescue Faraday::ResourceNotFound => e + nil + end + + private + + def client + Faraday.new(ssl: ssl_opts, url: Rails.configuration.astral[:app_registry_addr]) do |faraday| + faraday.request :authorization, "Bearer", -> { Rails.configuration.astral[:app_registry_token] } + faraday.request :retry, retry_opts + faraday.response :json + faraday.response :raise_error, include_request: true + end + end + + def convert(domain_info) + if !domain_info || domain_info["isDeleted"] + return nil + end + + OpenStruct.new( + fqdn: domain_info["fullyQualifiedDomainName"], + group_delegation: domain_info["ownerDelegatedRequestsToTeam"], + groups: domain_info["autoApprovedGroups"], + users: domain_info["autoApprovedServiceAccounts"] + ) + end + + def ssl_opts + { + ca_file: Rails.configuration.astral[:app_registry_ca_file], + client_cert: Rails.configuration.astral[:app_registry_client_cert], + client_key: Rails.configuration.astral[:app_registry_client_key] + } + end + + def retry_opts + { + max: 3, + interval: 0.05, + interval_randomness: 0.5, + backoff_factor: 2 + } + end + end + end +end diff --git a/app/lib/services/auth_service.rb b/app/lib/services/auth_service.rb index 2d1bd7b..037b2f2 100644 --- a/app/lib/services/auth_service.rb +++ b/app/lib/services/auth_service.rb @@ -1,29 +1,23 @@ module Services class AuthService - def initialize - @domain_ownership_service = DomainOwnershipService.new - end - - def authenticate!(token) - identity = decode(token) - raise AuthError unless identity - # TODO verify identity with authority? - identity - end - - def authorize!(identity, cert_issue_req) - @domain_ownership_service.authorize!(identity, cert_issue_req) - end + class << self + def authenticate!(token) + identity = decode(token) + raise AuthError unless identity + # TODO verify identity with authority? + identity + end - private + private - def decode(token) - # Decode a JWT access token using the configured base. - body = JWT.decode(token, Rails.application.config.astral[:jwt_signing_key])[0] - Identity.new(body) - rescue => e - Rails.logger.warn "Unable to decode token: #{e}" - nil + def decode(token) + # Decode a JWT access token using the configured base. + body = JWT.decode(token, Rails.configuration.astral[:jwt_signing_key])[0] + Identity.new(body) + rescue => e + Rails.logger.warn "Unable to decode token: #{e}" + nil + end end end end diff --git a/app/lib/services/certificate_service.rb b/app/lib/services/certificate_service.rb index 3b63dfa..15141c8 100644 --- a/app/lib/services/certificate_service.rb +++ b/app/lib/services/certificate_service.rb @@ -1,12 +1,16 @@ module Services class CertificateService - def initialize - # TODO this should select an implementation service based on config - @impl = VaultService.new - end + class << self + def issue_cert(cert_issue_request) + impl.issue_cert(cert_issue_request) + end + + private - def issue_cert(cert_issue_request) - @impl.issue_cert(cert_issue_request) + def impl + # TODO this should select an implementation service based on config + VaultService + end end end end diff --git a/app/lib/services/domain_ownership_service.rb b/app/lib/services/domain_ownership_service.rb index 1035e37..6ecfd14 100644 --- a/app/lib/services/domain_ownership_service.rb +++ b/app/lib/services/domain_ownership_service.rb @@ -1,14 +1,16 @@ module Services class DomainOwnershipService - def authorize!(identity, cert_req) - cert_req.fqdns.each do |fqdn| - domain = Domain.where(fqdn: fqdn).first - raise AuthError unless domain.present? && - (domain.owner == identity.subject || - (domain.group_delegation && - (domain.groups & identity.groups).any?)) + class << self + def get_domain_info(fqdn) + impl.get_domain_info(fqdn) + end + + private + + def impl + # TODO this should select an implementation service based on config + AppRegistryService end - nil end end end diff --git a/app/lib/services/vault_service.rb b/app/lib/services/vault_service.rb index d6e77f8..889e5c3 100644 --- a/app/lib/services/vault_service.rb +++ b/app/lib/services/vault_service.rb @@ -1,18 +1,22 @@ module Services class VaultService - def initialize - # TODO create a new token for use in the session - @client = Vault::Client.new( - address: Rails.application.config.astral[:vault_addr], - token: Rails.application.config.astral[:vault_token] - ) - end + class << self + def issue_cert(cert_issue_request) + opts = cert_issue_request.attributes + # Generate the TLS certificate using the intermediate CA + tls_cert = client.logical.write(Rails.configuration.astral[:vault_cert_path], opts) + OpenStruct.new tls_cert.data + end + + private - def issue_cert(cert_issue_request) - opts = cert_issue_request.attributes - # Generate the TLS certificate using the intermediate CA - tls_cert = @client.logical.write(Rails.application.config.astral[:vault_cert_path], opts) - OpenStruct.new tls_cert.data + def client + # TODO create a new token for use in the session + Vault::Client.new( + address: Rails.configuration.astral[:vault_addr], + token: Rails.configuration.astral[:vault_token] + ) + end end end end diff --git a/app/models/domain.rb b/app/models/domain.rb index b0bd65c..72003bb 100644 --- a/app/models/domain.rb +++ b/app/models/domain.rb @@ -1,10 +1,11 @@ class Domain < ApplicationRecord - serialize :groups, coder: YAML, type: Array - before_save :clean_groups + validates :fqdn, presence: true - validates :fqdn, :owner, presence: true + def groups_array + (groups || "").split(",").sort.uniq + end - def clean_groups - self.groups = groups.sort.uniq + def users_array + (users || "").split(",").sort.uniq end end diff --git a/config/astral.yml b/config/astral.yml index c708d75..f4d61c6 100644 --- a/config/astral.yml +++ b/config/astral.yml @@ -4,6 +4,11 @@ shared: vault_cert_path: "pki_int/issue/learn" jwt_signing_key: <%= ENV["JWT_SIGNING_KEY"] %> cert_ttl: <%= ENV["CERT_TTL"] %> + app_registry_addr: <%= ENV["APP_REGISTRY_ADDR"] %> + app_registry_token: <%= ENV["APP_REGISTRY_TOKEN"] %> + app_registry_ca_file: <%= ENV["APP_REGISTRY_CA_FILE"] %> + app_registry_client_cert: <%= ENV["APP_REGISTRY_CLIENT_CERT"] %> + app_registry_client_key: <%= ENV["APP_REGISTRY_CLIENT_KEY"] %> test: cert_ttl: <%= 24.hours.in_seconds %> diff --git a/db/migrate/20240904175652_create_domains.rb b/db/migrate/20240904175652_create_domains.rb index 1225da9..9070c1e 100644 --- a/db/migrate/20240904175652_create_domains.rb +++ b/db/migrate/20240904175652_create_domains.rb @@ -1,12 +1,11 @@ class CreateDomains < ActiveRecord::Migration[7.2] def change create_table :domains do |t| - t.string :fqdn, null: false - t.string :owner, null: false + t.string :fqdn, null: false, index: { unique: true } + t.text :users t.text :groups t.boolean :group_delegation, default: false t.timestamps - t.index :fqdn, unique: true end end end diff --git a/db/schema.rb b/db/schema.rb index 4e5f14d..e14708e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -13,7 +13,7 @@ ActiveRecord::Schema[7.2].define(version: 2024_09_04_175652) do create_table "domains", force: :cascade do |t| t.string "fqdn", null: false - t.string "owner", null: false + t.text "users" t.text "groups" t.boolean "group_delegation", default: false t.datetime "created_at", null: false diff --git a/db/seeds.rb b/db/seeds.rb index 11fded0..fa0adc1 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -10,5 +10,5 @@ # this seed is for development only if Rails.env.development? - Domain.first_or_create!(fqdn: "example.com", owner: "john.doe@example.com") + Domain.find_or_create_by!(fqdn: "example.com", users: "john.doe@example.com") end diff --git a/test/fixtures/domains.yml b/test/fixtures/domains.yml index a0ecfad..82dc778 100644 --- a/test/fixtures/domains.yml +++ b/test/fixtures/domains.yml @@ -1,18 +1,16 @@ owner_match: fqdn: example.com - owner: john.doe@example.com + users: john.doe@example.com,some.other@example.com group_delegation: false group_match: fqdn: example2.com - owner: some.other@example2.com + users: some.other@example2.com group_delegation: true - groups: - - "group1" + groups: group1,group2 no_match: fqdn: example3.com - owner: some.other@example2.com + users: some.other@example2.com,yet.another@example2.com group_delegation: true - groups: - - "group3" + groups: group3,group4 diff --git a/test/interactors/authenticate_identity_test.rb b/test/interactors/authenticate_identity_test.rb index c9361c9..eb0c8d9 100644 --- a/test/interactors/authenticate_identity_test.rb +++ b/test/interactors/authenticate_identity_test.rb @@ -6,24 +6,24 @@ def setup @identity = Identity.new(subject: "test@example.com", groups: [ "admin_group" ]) end - test "successful call" do + test ".call success" 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 + mock = Minitest::Mock.new + mock.expect :call, @identity, [ "valid_token" ] + Services::AuthService.stub :authenticate!, mock do context = @interactor.call(request: request) assert context.success? assert_equal @identity, context.identity end end - test "unsuccessful call" do + test ".call failure" 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 + mock = Minitest::Mock.new + mock.expect :call, nil, [ "invalid_token" ] + Services::AuthService.stub :authenticate!, mock do context = @interactor.call(request: request) - assert_not context.success? + assert context.failure? assert_nil context.identity end end diff --git a/test/interactors/authorize_request_test.rb b/test/interactors/authorize_request_test.rb index 30cd3e6..b878f3d 100644 --- a/test/interactors/authorize_request_test.rb +++ b/test/interactors/authorize_request_test.rb @@ -3,31 +3,35 @@ class AuthorizeRequestTest < ActiveSupport::TestCase def setup @domain = domains(:group_match) - @identity = Identity.new(subject: @domain.owner) + @identity = Identity.new(subject: @domain.users_array.first) @cr = CertIssueRequest.new(common_name: @domain.fqdn) @interactor = AuthorizeRequest end - test "successful call" do - request = CertIssueRequest.new(common_name: @domain.fqdn) - srv = Minitest::Mock.new - srv.expect :authorize!, nil, [ @identity, @cr ] - Services::DomainOwnershipService.stub :new, srv do - context = @interactor.call(identity: @identity, request: @cr) - assert context.success? - end + test ".call with matching owner" do + rslt = @interactor.call(identity: @identity, request: @cr) + assert rslt.success? end - test "unsuccessful call" do - request = CertIssueRequest.new(common_name: @domain.fqdn) - srv = Services::DomainOwnershipService.new - Services::DomainOwnershipService.stub :new, srv do - err = ->(_, _) { raise AuthError.new "no can do" } - srv.stub :authorize!, err do - context = @interactor.call(identity: @identity, request: @cr) - assert_not context.success? - assert_kind_of AuthError, context.error - end - end + test ".call with non-matching owner" do + @identity.subject = "different_owner@example.com" + rslt = @interactor.call(identity: @identity, request: @cr) + assert_not rslt.success? + assert_kind_of AuthError, rslt.error + end + + test ".call with matching group" do + @domain.update(users: "different_owner@example.com") + @identity.groups = [ @domain.groups_array.first ] + rslt = @interactor.call(identity: @identity, request: @cr) + assert rslt.success? + end + + test ".call with non-matching group" do + @domain.update(users: "different_owner@example.com") + @identity.groups = [ "different_group" ] + rslt = @interactor.call(identity: @identity, request: @cr) + assert_not rslt.success? + assert_kind_of AuthError, rslt.error end end diff --git a/test/interactors/obtain_cert_test.rb b/test/interactors/obtain_cert_test.rb index 3211ae2..efd79b7 100644 --- a/test/interactors/obtain_cert_test.rb +++ b/test/interactors/obtain_cert_test.rb @@ -6,22 +6,22 @@ def setup @cert = OpenStruct.new(certificate: "certificate", ca_chain: "ca_chain") end - test "successful call" do + test ".call success" do request = CertIssueRequest.new - srv = Minitest::Mock.new - srv.expect :issue_cert, @cert, [ request ] - Services::CertificateService.stub :new, srv do + mock = Minitest::Mock.new + mock.expect :call, @cert, [ request ] + Services::CertificateService.stub :issue_cert, mock do context = @interactor.call(request: request) assert context.success? assert_equal @cert, context.cert end end - test "unsuccessful call" do + test ".call failure" do request = CertIssueRequest.new - srv = Minitest::Mock.new - srv.expect :issue_cert, nil, [ request ] - Services::CertificateService.stub :new, srv do + mock = Minitest::Mock.new + mock.expect :call, nil, [ request ] + Services::CertificateService.stub :issue_cert, mock do context = @interactor.call(request: request) assert context.failure? assert_nil context.cert diff --git a/test/interactors/refresh_domain_test.rb b/test/interactors/refresh_domain_test.rb new file mode 100644 index 0000000..07ba149 --- /dev/null +++ b/test/interactors/refresh_domain_test.rb @@ -0,0 +1,41 @@ +require "test_helper" + +class RefreshDomainTest < ActiveSupport::TestCase + def setup + @domain = domains(:owner_match) + @identity = Identity.new(subject: @domain.users_array.first) + @cr = CertIssueRequest.new(common_name: @domain.fqdn) + @interactor = RefreshDomain + end + + test ".call updates db record with service response when 200" do + rslt = @interactor.call(identity: @identity, request: @cr) + assert rslt.success? + reloaded = Domain.where(fqdn: @domain.fqdn).first! + assert_not_equal @domain.users, reloaded.users + assert_not_equal @domain.groups, reloaded.groups + assert_not_equal @domain.group_delegation, reloaded.group_delegation + end + + test ".call deletes db record when service 404" do + @domain = domains(:no_match) # this fixture should have no match + @cr = CertIssueRequest.new(common_name: @domain.fqdn) + rslt = @interactor.call(identity: @identity, request: @cr) + assert rslt.success? + reloaded = Domain.where(fqdn: @domain.fqdn).first + assert_nil reloaded + end + + test ".call leaves db record as-is when service has error" do + mock = Minitest::Mock.new + err = ->(_) { raise Faraday::TimeoutError.new } + mock.expect(:call, err) do + Services::DomainOwnershipService.stub :get_domain_info, mock do + rslt = @interactor.call(identity: @identity, request: @cr) + assert rslt.success? + reloaded = Domain.where(fqdn: @domain.fqdn).first! + assert_equal @domain.users, reloaded.users + end + end + end +end diff --git a/test/lib/services/app_registry_service_test.rb b/test/lib/services/app_registry_service_test.rb new file mode 100644 index 0000000..3cda7b8 --- /dev/null +++ b/test/lib/services/app_registry_service_test.rb @@ -0,0 +1,21 @@ +require "test_helper" + +class AppRegistryServiceTest < ActiveSupport::TestCase + setup do + @service = Services::AppRegistryService + end + + test "#get_domain_info fetches from configured api server" do + domain_info = @service.get_domain_info(domains(:owner_match).fqdn) + assert_not_nil domain_info + assert_equal "group1", domain_info.groups + assert_equal "john.doe@example.com", domain_info.users + assert_equal "example.com", domain_info.fqdn + assert domain_info.group_delegation + end + + test "#get_domain_info returns nil for unmatched fqdn" do + domain_info = @service.get_domain_info(domains(:no_match).fqdn) + assert_nil domain_info + end +end diff --git a/test/lib/services/domain_ownership_service_test.rb b/test/lib/services/domain_ownership_service_test.rb deleted file mode 100644 index 4f5815a..0000000 --- a/test/lib/services/domain_ownership_service_test.rb +++ /dev/null @@ -1,35 +0,0 @@ -require "test_helper" - -class DomainOwnershipServiceTest < ActiveSupport::TestCase - def setup - @domain = domains(:group_match) - @identity = Identity.new(subject: @domain.owner) - @cr = CertIssueRequest.new(common_name: @domain.fqdn) - @ds = Services::DomainOwnershipService.new - end - - test "#authorize! with matching owner" do - assert_nil(@ds.authorize!(@identity, @cr)) - end - - test "#authorize! with non-matching owner" do - @identity.subject = "different_owner@example.com" - assert_raises(AuthError) do - @ds.authorize!(@identity, @cr) - end - end - - test "#authorize! with matching group" do - @domain.update(owner: "different_owner@example.com") - @identity.groups = @domain.groups - assert_nil(@ds.authorize!(@identity, @cr)) - end - - test "#authorize! with non-matching group" do - @domain.update(owner: "different_owner@example.com") - @identity.groups = [ "different_group" ] - assert_raises(AuthError) do - @ds.authorize!(@identity, @cr) - end - end -end diff --git a/test/models/domain_test.rb b/test/models/domain_test.rb index 059b142..ec8f8c1 100644 --- a/test/models/domain_test.rb +++ b/test/models/domain_test.rb @@ -5,7 +5,7 @@ class DomainTest < ActiveSupport::TestCase def setup @attributes = { fqdn: "example4.com", - owner: "john.doe@example.com" + users: "john.doe@example.com" } @domain = Domain.new(@attributes) end @@ -26,15 +26,13 @@ def setup assert_includes @domain.errors[:fqdn], "can't be blank" end - test "#valid? should require an owner" do - @domain.owner = nil - assert_not @domain.valid? - assert_includes @domain.errors[:owner], "can't be blank" + test "#groups_array should convert to array, sort, and dedupe groups" do + @domain.groups = "two,two,one" + assert_equal [ "one", "two" ], @domain.groups_array end - test "before_save should sort and dedupe groups" do - @domain.groups = [ "two", "two", "one" ] - @domain.save - assert_equal [ "one", "two" ], @domain.groups + test "#users_array should convert to array, sort, and dedupe users" do + @domain.users = "two,two,one" + assert_equal [ "one", "two" ], @domain.users_array end end