From 1faacf8dda27d4eef0a4440deda82326262e0a89 Mon Sep 17 00:00:00 2001 From: mo khan Date: Thu, 6 Mar 2025 13:18:30 -0700 Subject: feat: provide JWT token and GlobalID to make the remote authorization decision --- bin/api | 29 +++++++++++++++++++++++++---- bin/e2e | 2 +- bin/idp | 3 ++- lib/authx.rb | 8 +++++++- lib/authx/rpc/ability_handler.rb | 34 +++++++++++++++++++++++++++++----- 5 files changed, 64 insertions(+), 12 deletions(-) diff --git a/bin/api b/bin/api index d3c53175..06500207 100755 --- a/bin/api +++ b/bin/api @@ -7,6 +7,7 @@ gemfile do 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" @@ -26,6 +27,16 @@ $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 @@ -49,6 +60,12 @@ class Project 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'] @@ -77,13 +94,17 @@ class API private - def authorized?(request, permission) + 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) - client = ::Authx::Rpc::AbilityClient.new("http://idp.example.com:8080/twirp") - response = client.allowed(subject: "", permission: 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 + response.error.nil? && response.data.result end def json_not_found diff --git a/bin/e2e b/bin/e2e index bcc2b67c..f4824ec5 100755 --- a/bin/e2e +++ b/bin/e2e @@ -19,6 +19,6 @@ $BROWSER http://ui.example.com:8080/saml/new $BROWSER http://ui.example.com:8080/oidc/new curl http://api.example.com:8080/projects.json -curl -i -XPOST http://api.example.com:8080/projects --data '{"name": "gitlab"}' +curl -H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI2YmYxZTlmMy02OGIwLTQ4NmYtOGVlZi0wODFmZTg2YjJlODMiLCJpYXQiOjE3NDEyOTAzMzJ9.e30=' -XPOST http://api.example.com:8080/projects --data '{"name": "gitlab"}' curl http://api.example.com:8080/projects.json diff --git a/bin/idp b/bin/idp index 47ec2054..48a77f77 100755 --- a/bin/idp +++ b/bin/idp @@ -6,8 +6,9 @@ gemfile do source "https://rubygems.org" gem "declarative_policy", "~> 1.0" - gem "google-protobuf", "~> 3.0" gem "erb", "~> 4.0" + gem "globalid", "~> 1.0" + gem "google-protobuf", "~> 3.0" gem "rack", "~> 3.0" gem "rackup", "~> 2.0" gem "saml-kit", "~> 1.0" diff --git a/lib/authx.rb b/lib/authx.rb index 3c4a467a..0c62039b 100644 --- a/lib/authx.rb +++ b/lib/authx.rb @@ -5,9 +5,15 @@ require "declarative_policy" require "authx/rpc" module Authx - class ProjectPolicy < DeclarativePolicy::Base + class OrganizationPolicy < DeclarativePolicy::Base condition(:owner) { true } rule { owner }.enable :create_project end + + DeclarativePolicy.configure do + name_transformation do |name| + "Authx::#{name}Policy" + end + end end diff --git a/lib/authx/rpc/ability_handler.rb b/lib/authx/rpc/ability_handler.rb index 9f9b8fe3..5f977e64 100644 --- a/lib/authx/rpc/ability_handler.rb +++ b/lib/authx/rpc/ability_handler.rb @@ -1,10 +1,19 @@ # frozen_string_literal: true +class Organization + class << self + def find(id) + new + end + end +end + module Authx module Rpc + class AbilityHandler def allowed(request, env) - puts [request, env].inspect + puts [request, env, can?(request)].inspect { result: can?(request) @@ -14,12 +23,27 @@ module Authx private def can?(request) - policy_for(request).can?(request.permission) + subject = subject_of(request.subject) + resource = resource_from(request.resource) + policy = DeclarativePolicy.policy_for(subject, resource) + policy.can?(request.permission.to_sym) + end + + def subject_of(token) + _header, claims, _signature = from_jwt(token) + claims[:sub] + end + + def resource_from(global_id) + # TODO:: Parse global id and convert to class + GlobalID::Locator.locate(global_id) end - def policy_for(request) - # TODO:: convert subject in form of GlobalID to Resource Type - DeclarativePolicy.policy_for(request.subject, request.resource) + # TODO:: validate signature + def from_jwt(token) + token + .split('.', 3) + .map { |x| JSON.parse(Base64.strict_decode64(x), symbolize_names: true) } end end end -- cgit v1.2.3