Commit 4d0c6c3

mo khan <mo@mokhan.ca>
2025-03-20 15:13:08
feat: add html pages to ui service that fetches data from rest api
1 parent 136c4df
Changed files (4)
bin/api
@@ -80,6 +80,7 @@ end
 
 module HTTPHelpers
   def authorized?(request, permission, resource)
+    raise [permission, resource].inspect if resource.nil?
     authorization = Rack::Auth::AbstractRequest.new(request.env)
     return false unless authorization.provided?
 
@@ -138,7 +139,12 @@ class API
       when "/organizations", "/organizations.json"
         return json_ok(Organization.all.map(&:to_h))
       when "/groups", "/groups.json"
-        return json_ok(Group.all.map(&:to_h))
+        resource = Organization.default
+        if authorized?(request, :read_group, resource)
+          return json_ok(Group.all.map(&:to_h))
+        else
+          return json_unauthorized(:read_group, resource)
+        end
       when "/projects", "/projects.json"
         resource = Organization.default
         if authorized?(request, :read_project, resource)
bin/idp
@@ -328,6 +328,7 @@ module Authz
     condition(:owner) { true }
 
     rule { owner }.enable :read_project
+    rule { owner }.enable :read_group
     rule { owner }.enable :create_project
   end
 
@@ -566,6 +567,7 @@ module Authz
           <head><title></title></head>
           <body>
             <h2>Authorize?</h2>
+            <p>Client ID: <%= oauth_params['client_id'] %></p>
             <form id="authorize-form" action="/oauth/authorize" method="post">
               <input type="hidden" name="client_id" value="<%= oauth_params['client_id'] %>" />
               <input type="hidden" name="scope" value="<%= oauth_params['scope'] %>" />
bin/ui
@@ -108,6 +108,10 @@ module OAuth
 end
 
 module HTTPHelpers
+  def current_user?(request)
+    request.session[:id_token]
+  end
+
   def not_found
     [404, { 'X-Backend-Server' => 'UI' }, []]
   end
@@ -119,7 +123,6 @@ module HTTPHelpers
       [302, { 'Location' => "http://ui.example.com:8080#{location}" }, []]
     end
   end
-
 end
 
 class UI
@@ -132,10 +135,22 @@ class UI
   end
 
   def call(env)
-    path = env['PATH_INFO']
-    case env['REQUEST_METHOD']
-    when 'GET'
-      case path
+    request = Rack::Request.new(env)
+    case request.request_method
+    when Rack::GET
+      case request.path
+      when "/groups.html"
+        if current_user?(request)
+          return get_groups(request)
+        else
+          return redirect_to("/oidc/new")
+        end
+      when /\A\/groups\/\d+\/projects.html\z/
+        if current_user?(request)
+          return get_projects(request)
+        else
+          return redirect_to("/oidc/new")
+        end
       when "/oauth/callback"
         return oauth_callback(Rack::Request.new(env))
       when "/oidc/new"
@@ -147,8 +162,8 @@ class UI
       else
         return redirect_to("/saml/new")
       end
-    when 'POST'
-      case path
+    when Rack::POST
+      case request.path
       when "/saml/assertions"
         return saml_assertions(Rack::Request.new(env))
       else
@@ -177,7 +192,119 @@ class UI
 
   def oauth_callback(request)
     response = oauth_client.exchange(grant_type: "authorization_code", code: request.params['code'])
-    [response.code, response.header, [response.body]]
+    if response.code == "200"
+      tokens = JSON.parse(response.body, symbolize_names: true)
+      request.session[:access_token] = tokens[:access_token]
+      request.session[:id_token] = tokens[:id_token]
+      request.session[:refresh_token] = tokens[:access_token]
+
+      template = <<~ERB
+        <!DOCTYPE html>
+        <html>
+          <head><title></title></head>
+          <body>
+            <pre style="display: none;"><%= response.body %></pre>
+            <pre><%= JSON.pretty_generate(request.session[:access_token]) %></pre>
+            <a href="/groups.html">Groups</a>
+          </body>
+        </html>
+      ERB
+      html = ERB.new(template, trim_mode: '-').result(binding)
+      [200, { 'Content-Type' => "text/html" }, [html]]
+    else
+      [response.code, response.header, [response.body]]
+    end
+  end
+
+  def get_groups(request)
+    http = Net::Hippie::Client.new(headers: ::Net::Hippie::Client::DEFAULT_HEADERS.merge({
+      'Authorization' => Net::Hippie.bearer_auth(request.session[:access_token])
+    }))
+
+    response = http.get("http://api.example.com:8080/groups.json")
+    if response.code == "200"
+      groups = JSON.parse(response.body, symbolize_names: true)
+      template = <<~ERB
+        <!DOCTYPE html>
+        <html>
+          <head>
+            <title></title>
+          </head>
+          <body>
+            <a href="/groups.html">Groups</a>
+            <table>
+              <thead>
+                <tr>
+                  <th>ID</th>
+                  <th>Name</th>
+                  <th>Organization ID</th>
+                  <th>Parent ID</th>
+                  <th>&nbsp;</th>
+                </tr>
+              </thead>
+              <tbody>
+              <%- groups.each do |group| -%>
+                <tr>
+                  <td><%= group[:id] %></td>
+                  <td><%= group[:name] %></td>
+                  <td><%= group[:organization_id] %></td>
+                  <td><%= group[:parent_id] %></td>
+                  <td><a href="/groups/<%= group[:id] %>/projects.html">Projects</a></td>
+                </tr>
+              <%- end -%>
+              </tbody>
+            </table>
+          </body>
+        </html>
+      ERB
+      html = ERB.new(template, trim_mode: '-').result(binding)
+      [200, { 'Content-Type' => "text/html" }, [html]]
+    else
+      [response.code, response.header, [response.body]]
+    end
+  end
+
+  def get_projects(request)
+    http = Net::Hippie::Client.new(headers: ::Net::Hippie::Client::DEFAULT_HEADERS.merge({
+      'Authorization' => Net::Hippie.bearer_auth(request.session[:access_token])
+    }))
+
+    response = http.get("http://api.example.com:8080/projects.json")
+    if response.code == "200"
+      projects = JSON.parse(response.body, symbolize_names: true)
+
+      template = <<~ERB
+        <!DOCTYPE html>
+        <html>
+          <head>
+            <title></title>
+          </head>
+          <body>
+            <a href="/groups.html">Groups</a>
+            <table>
+              <thead>
+                <tr>
+                  <th>Name</th>
+                  <th>Group ID</th>
+                </tr>
+              </thead>
+              <tbody>
+              <%- projects.each do |project| -%>
+                <tr>
+                  <td><%= project[:name] %></td>
+                  <td><%= project[:group_id] %></td>
+                </tr>
+              <%- end -%>
+              </tbody>
+            </table>
+          </body>
+        </html>
+      ERB
+      html = ERB.new(template, trim_mode: '-').result(binding)
+      [200, { 'Content-Type' => "text/html" }, [html]]
+    else
+      [response.code, response.header, [response.body]]
+    end
   end
 
   def saml_post_to_idp(request)
@@ -216,7 +343,6 @@ class UI
     saml_response = saml_binding.deserialize(request.params)
     raise saml_response.errors unless saml_response.valid?
 
-    request.session[:access_token] = saml_response.attributes[:access_token]
     template = <<~ERB
       <!doctype html>
       <html>
policy.csv
@@ -9,3 +9,4 @@ p, *, idp.example.com, (GET)|(POST), /sessions*
 p, *, ui.example.com, (GET)|(POST), /oauth*
 p, *, ui.example.com, (GET)|(POST), /oidc*
 p, *, ui.example.com, (GET)|(POST), /saml*
+p, *, ui.example.com, (GET), /*.html