Commit 1faacf8

mo khan <mo@mokhan.ca>
2025-03-06 20:18:30
feat: provide JWT token and GlobalID to make the remote authorization decision
1 parent 68aaeca
Changed files (5)
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
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
 
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"
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
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