#!/usr/bin/env ruby require 'bundler/inline' gemfile do source 'https://rubygems.org' gem "declarative_policy", "~> 1.0" gem "erb", "~> 4.0" gem "globalid", "~> 1.0" gem "google-protobuf", "~> 3.0" gem "json", "~> 2.0" gem "logger", "~> 1.0" gem "rack", "~> 3.0" gem "rackup", "~> 2.0" gem "securerandom", "~> 0.1" gem "twirp", "~> 1.0" gem "webrick", "~> 1.0" end lib_path = Pathname.new(__FILE__).parent.parent.join('lib').realpath.to_s $LOAD_PATH.unshift(lib_path) unless $LOAD_PATH.include?(lib_path) require 'authx' $scheme = ENV.fetch("SCHEME", "http") $port = ENV.fetch("PORT", 8284).to_i $host = ENV.fetch("HOST", "localhost:#{$port}") class Organization def initialize(attributes = {}) @attributes = attributes end def id @attributes[:id] end end class Project class << self def all @projects ||= [] end def create!(attributes) new({ id: SecureRandom.uuid }.merge(attributes)).tap do |item| all << item end end end def initialize(attributes = {}) @attributes = attributes end def to_h @attributes end end class API attr_reader :rpc def initialize @rpc = ::Authx::Rpc::AbilityClient.new("http://idp.example.com:8080/twirp") end def call(env) request = Rack::Request.new(env) path = env['PATH_INFO'] case env['REQUEST_METHOD'] when 'GET' case path when '/projects.json' return json_ok(Project.all.map(&:to_h)) else return json_not_found end when 'POST' case path when "/projects" if authorized?(request, :create_project) return json_created(Project.create!(JSON.parse(request.body.read, symbolize_names: true))) else return json_unauthorized(:create_project) end else return json_not_found end end json_not_found end private def authorized?(request, permission, resource = Organization.new(id: 1)) # TODO:: Check the JWT for the appropriate claim # Connect to the Authz RPC endpoint Ability.allowed?(subject, permission, resource) token = request&.get_header('HTTP_AUTHORIZATION')&.split(' ', 2)&.last response = rpc.allowed( subject: token, permission: permission, resource: ::GlobalID.create(resource, app: "example").to_s ) puts response.inspect response.error.nil? && response.data.result end def json_not_found http_response(code: 404) end def json_ok(body) http_response(code: 200, body: JSON.pretty_generate(body)) end def json_created(body) http_response(code: 201, body: JSON.pretty_generate(body.to_h)) end def json_unauthorized(permission) http_response(code: 401, body: JSON.pretty_generate({ error: { code: 401, message: "`#{permission}` is required", } })) end def http_response(code:, headers: { 'Content-Type' => 'application/json' }, body: nil) [ code, headers.merge({ 'X-Backend-Server' => 'REST' }), [body].compact ] end end if __FILE__ == $0 app = Rack::Builder.new do use Rack::CommonLogger use Rack::Reloader run API.new end.to_app Rackup::Server.start(app: app, Port: $port) end