Skip to content

Commit

Permalink
feat: move basic auth code in from pact-broker-docker
Browse files Browse the repository at this point in the history
  • Loading branch information
bethesque committed Jul 20, 2021
1 parent 6e3d0e6 commit 869bcd6
Show file tree
Hide file tree
Showing 11 changed files with 578 additions and 52 deletions.
4 changes: 2 additions & 2 deletions example/basic_auth/Gemfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
source "https://rubygems.org"

gem "pact_broker"
gem "pg"
gem "pact_broker", path: "../.."
gem "sqlite3"
gem "thin"
28 changes: 19 additions & 9 deletions example/basic_auth/config.ru
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,28 @@ require "fileutils"
require "logger"
require "sequel"
require "pact_broker"
require "pg"
require "pact_broker/api/middleware/basic_auth"
require "pact_broker/config/basic_auth_configuration"
require "pact_broker/api/authorization/resource_access_policy"
require "pact_broker/initializers/database_connection"

use Rack::Auth::Basic, "Restricted Area" do |username, password|
username == ENV["PACT_BROKER_USERNAME"] and password == ENV["PACT_BROKER_PASSWORD"]
SemanticLogger.add_appender(io: $stdout)
SemanticLogger.default_level = :info
$logger = SemanticLogger['pact-broker']

basic_auth_configuration = PactBroker::Config::BasicAuthRuntimeConfiguration.new
basic_auth_configuration.log_configuration($logger)

if basic_auth_configuration.use_basic_auth?
policy = PactBroker::Api::Authorization::ResourceAccessPolicy.build(basic_auth_configuration.allow_public_read, basic_auth_configuration.public_heartbeat)
use PactBroker::Api::Middleware::BasicAuth,
basic_auth_configuration.write_credentials,
basic_auth_configuration.read_credentials,
policy
end

app = PactBroker::App.new do | config |
# change these from their default values if desired
# config.log_dir = "./log"
# config.auto_migrate_db = true
# config.use_hal_browser = true
config.database_connection = Sequel.connect(ENV["DATABASE_URL"], adapter: "postgres", encoding: "utf8")
config.database_connection = PactBroker.create_database_connection(config.logger, config.database_configuration, 0)
end

run app
run app
5 changes: 5 additions & 0 deletions example/basic_auth/config/pact_broker.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
basic_auth_username: "user"
basic_auth_password: "pass"
basic_auth_read_only_username: "rouser"
basic_auth_read_only_password: "ropass"
database_url: sqlite:////tmp/pact_broker.sqlite3
62 changes: 62 additions & 0 deletions lib/pact_broker/api/authorization/resource_access_policy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
require "pact_broker/api/authorization/resource_access_rules"

module PactBroker
module Api
module Authorization
class ResourceAccessPolicy
READ_METHODS = %w{GET OPTIONS HEAD}.freeze
ALL_METHODS = %w{GET POST PUT PATCH DELETE HEAD OPTIONS}.freeze
POST = "POST".freeze

ALL_PATHS = %r{.*}.freeze
PACT_BADGE_PATH = %r{^/pacts/provider/[^/]+/consumer/.*/badge(?:\.[A-Za-z]+)?$}.freeze
MATRIX_BADGE_PATH = %r{^/matrix/provider/[^/]+/latest/[^/]+/consumer/[^/]+/latest/[^/]+/badge(?:\.[A-Za-z]+)?$}.freeze
HEARTBEAT_PATH = %r{^/diagnostic/status/heartbeat$}.freeze
PACTS_FOR_VERIFICATION_PATH = %r{^/pacts/provider/[^/]+/for-verification$}.freeze

PUBLIC = 0
READ = 1
WRITE = 2

def initialize(resource_access_rules)
@resource_access_rules = resource_access_rules
end

def public_access_allowed?(env)
resource_access_rules.access_allowed?(env, PUBLIC)
end

def read_access_allowed?(env)
resource_access_rules.access_allowed?(env, READ)
end

def self.build(allow_public_read_access, allow_public_access_to_heartbeat)
rules = [
[WRITE, ALL_METHODS, ALL_PATHS],
[READ, READ_METHODS, ALL_PATHS],
[READ, [POST], PACTS_FOR_VERIFICATION_PATH],
[PUBLIC, READ_METHODS, PACT_BADGE_PATH],
[PUBLIC, READ_METHODS, MATRIX_BADGE_PATH]
]

if allow_public_access_to_heartbeat
rules.unshift([PUBLIC, READ_METHODS, HEARTBEAT_PATH])
end

if allow_public_read_access
rules.unshift([PUBLIC, READ_METHODS, ALL_PATHS])
rules.unshift([PUBLIC, [POST], PACTS_FOR_VERIFICATION_PATH])
end

ResourceAccessPolicy.new(ResourceAccessRules.new(rules))
end

private

attr_reader :resource_access_rules
end

end
end
end

40 changes: 40 additions & 0 deletions lib/pact_broker/api/authorization/resource_access_rules.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
require "rack"

module PactBroker
module Api
module Authorization
class ResourceAccessRules
PATH_INFO = Rack::PATH_INFO
REQUEST_METHOD = Rack::REQUEST_METHOD

def initialize(rules)
@rules = rules
end

def access_allowed?(env, level)
!!rules.find do | rule_level, allowed_methods, path_pattern |
level_allowed?(level, rule_level) &&
method_allowed?(env, allowed_methods) &&
path_allowed?(env, path_pattern)
end
end

private

attr_reader :rules

def level_allowed?(level, rule_level)
level >= rule_level
end

def path_allowed?(env, pattern)
env[PATH_INFO] =~ pattern
end

def method_allowed?(env, allowed_methods)
allowed_methods.include?(env[REQUEST_METHOD])
end
end
end
end
end
60 changes: 60 additions & 0 deletions lib/pact_broker/api/middleware/basic_auth.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
require "rack"

module PactBroker
module Api
module Middleware
class BasicAuth
def initialize(app, write_credentials, read_credentials, policy)
@app = app
@write_credentials = write_credentials
@read_credentials = read_credentials
@app_with_write_auth = build_app_with_write_auth
@app_with_read_auth = build_app_with_read_auth
@policy = policy
end

def call(env)
if policy.public_access_allowed?(env)
app.call(env)
elsif policy.read_access_allowed?(env)
app_with_read_auth.call(env)
else
app_with_write_auth.call(env)
end
end

protected

def write_credentials_match(*credentials)
credentials == write_credentials
end

def read_credentials_match(*credentials)
is_set(read_credentials[0]) && credentials == read_credentials
end

private

attr_reader :app, :app_with_read_auth, :app_with_write_auth, :write_credentials, :read_credentials, :policy

def build_app_with_write_auth
this = self
Rack::Auth::Basic.new(app, "Restricted area") do |username, password|
this.write_credentials_match(username, password)
end
end

def build_app_with_read_auth
this = self
Rack::Auth::Basic.new(app, "Restricted area") do |username, password|
this.write_credentials_match(username, password) || this.read_credentials_match(username, password)
end
end

def is_set(string)
string && string.strip.size > 0
end
end
end
end
end
34 changes: 34 additions & 0 deletions lib/pact_broker/config/basic_auth_configuration.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
require "pact_broker/config/runtime_configuration_logging_methods"

module PactBroker
module Config
class BasicAuthRuntimeConfiguration < Anyway::Config
include RuntimeConfigurationLoggingMethods

config_name :pact_broker

attr_config(
basic_auth_username: nil,
basic_auth_password: nil,
basic_auth_read_only_username: nil,
basic_auth_read_only_password: nil,
allow_public_read: false,
public_heartbeat: false
)

sensitive_values(:basic_auth_password, :basic_auth_read_only_password)

def write_credentials
[basic_auth_username, basic_auth_password]
end

def read_credentials
[basic_auth_read_only_username, basic_auth_read_only_password]
end

def use_basic_auth?
basic_auth_username && basic_auth_password != "" && basic_auth_password && basic_auth_password != ""
end
end
end
end
15 changes: 0 additions & 15 deletions lib/pact_broker/config/runtime_configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,6 @@ class RuntimeConfiguration < Anyway::Config
include RuntimeConfigurationDatabaseMethods
include RuntimeConfigurationCoercionMethods

class << self
def sensitive_values(*values)
@sensitive_values ||= []
if values
@sensitive_values.concat([*values])
else
@sensitive_values
end
end

def sensitive_value?(value)
sensitive_values.any? { |key| key == value || key == value.to_sym || key.kind_of?(Regexp) && key =~ value }
end
end

DATABASE_ATTRIBUTES = {
database_adapter: "postgres",
database_username: nil,
Expand Down
74 changes: 48 additions & 26 deletions lib/pact_broker/config/runtime_configuration_logging_methods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,62 @@
module PactBroker
module Config
module RuntimeConfigurationLoggingMethods
def log_configuration(logger)
logger.info "------------------------------------------------------------------------"
logger.info "PACT BROKER CONFIGURATION:"
to_source_trace.sort_by { |key, _| key }.each { |key, value| log_config_inner(key, value, logger) }
logger.info "------------------------------------------------------------------------"
end
module ClassMethods
def sensitive_values(*values)
@sensitive_values ||= []
if values
@sensitive_values.concat([*values])
else
@sensitive_values
end
end

def log_config_inner(key, value, logger)
if !value.has_key? :value
value.sort_by { |inner_key, _| inner_key }.each { |inner_key, inner_value| log_config_inner("#{key}:#{inner_key}", inner_value) }
elsif self.class.sensitive_value?(key)
logger.info "#{key}=#{redact(key, value[:value])} source=[#{value[:source]}]"
else
logger.info "#{key}=#{value[:value]} source=[#{value[:source]}]"
def sensitive_value?(value)
sensitive_values.any? { |key| key == value || key == value.to_sym || key.kind_of?(Regexp) && key =~ value }
end
end
private :log_config_inner

def redact name, value
if value && name.to_s.end_with?("_url")
begin
uri = URI(value)
uri.password = "*****"
uri.to_s
rescue StandardError
module InstanceMethods
def log_configuration(logger)
logger.info "------------------------------------------------------------------------"
logger.info "PACT BROKER CONFIGURATION:"
to_source_trace.sort_by { |key, _| key }.each { |key, value| log_config_inner(key, value, logger) }
logger.info "------------------------------------------------------------------------"
end

def log_config_inner(key, value, logger)
if !value.has_key? :value
value.sort_by { |inner_key, _| inner_key }.each { |inner_key, inner_value| log_config_inner("#{key}:#{inner_key}", inner_value) }
elsif self.class.sensitive_value?(key)
logger.info "#{key}=#{redact(key, value[:value])} source=[#{value[:source]}]"
else
logger.info "#{key}=#{value[:value]} source=[#{value[:source]}]"
end
end
private :log_config_inner

def redact name, value
if value && name.to_s.end_with?("_url")
begin
uri = URI(value)
uri.password = "*****"
uri.to_s
rescue StandardError
"*****"
end
elsif !value.nil?
"*****"
else
nil
end
elsif !value.nil?
"*****"
else
nil
end
private :redact
end

def self.included(receiver)
receiver.extend ClassMethods
receiver.send :include, InstanceMethods
end
private :redact
end
end
end
Loading

0 comments on commit 869bcd6

Please sign in to comment.