Commit 44a0ee1
bin/api
@@ -101,7 +101,6 @@ class API
permission: permission,
resource: ::GlobalID.create(resource, app: "example").to_s
)
- puts response.inspect
response.error.nil? && response.data.result
end
bin/idp
@@ -10,6 +10,7 @@ gemfile do
gem "globalid", "~> 1.0"
gem "google-protobuf", "~> 3.0"
gem "rack", "~> 3.0"
+ gem "rack-session", "~> 2.0"
gem "rackup", "~> 2.0"
gem "saml-kit", "~> 1.0"
gem "twirp", "~> 1.0"
@@ -28,7 +29,7 @@ $host = ENV.fetch("HOST", "localhost:#{$port}")
module HTTPHelpers
def default_headers
{
- 'X-Powered-By' => 'IDP'
+ 'X-Powered-By' => 'IdP'
}
end
@@ -43,6 +44,18 @@ end
module Authn
class User
+ class << self
+ def find_by_username(username)
+ User.new(id: SecureRandom.uuid, username: username, email: "#{username}@example.com")
+ end
+
+ def find_by_credentials(params = {})
+ user = find_by_username(params["username"])
+ return user if user.valid_password?(params["password"])
+
+ end
+ end
+
def initialize(attributes)
@attributes = attributes
end
@@ -55,12 +68,20 @@ module Authn
end
end
+ def create_access_token
+ ::Authz::JWT.new(sub: @attributes[:id], iat: Time.now.to_i)
+ end
+
def assertion_attributes_for(request)
{
email: @attributes[:email],
- access_token: ::Authz::JWT.new(sub: SecureRandom.uuid, iat: Time.now.to_i).to_jwt,
}
end
+
+ def valid_password?(entered_password)
+ # TODO:: BCrypt hash secure compare
+ true
+ end
end
class OnDemandRegistry < Saml::Kit::DefaultRegistry
@@ -91,7 +112,6 @@ module Authn
x.add_single_sign_on_service("#{scheme}://#{host}/saml/new", binding: :http_post)
x.name_id_formats = [Saml::Kit::Namespaces::PERSISTENT, Saml::Kit::Namespaces::EMAIL_ADDRESS]
x.attributes << :email
- x.attributes << :access_token
end
end
end
@@ -106,14 +126,18 @@ module Authn
{ 'Content-Type' => "application/samlmetadata+xml" },
saml_metadata.to_xml(pretty: true)
)
- when "/new"
- # TODO:: render a login page
- return saml_post_back(Rack::Request.new(env))
end
when 'POST'
case path
when "/new"
- return saml_post_back(Rack::Request.new(env))
+ return login_page(Rack::Request.new(env))
+ when "/login"
+ request = Rack::Request.new(env)
+ if (user = User.find_by_credentials(request.params))
+ return saml_post_back(request, user)
+ else
+ return login_page(request)
+ end
end
end
@@ -124,20 +148,48 @@ module Authn
attr_reader :saml_metadata
- def saml_post_back(request)
+ def login_page(request)
+ saml_params = saml_params_from(request)
+ saml_request = binding_for(request).deserialize(saml_params)
+ raise saml_request.errors unless saml_request.valid?
+
+ template = <<~ERB
+ <!DOCTYPE html>
+ <html>
+ <head><title></title></head>
+ <body>
+ <h2>Recieved SAML Request</h2>
+ <textarea readonly="readonly" disabled="disabled" cols=225 rows=6><%=- saml_request.to_xml(pretty: true) -%></textarea>
+
+ <form action="/saml/login" method="post">
+ <input type="input" placeholder="Username" name="username" value="" />
+ <input type="password" placeholder="Password" name="password" value="" />
+ <%- saml_params.each do |(key, value)| -%>
+ <input type="hidden" name="<%= key %>" value="<%= value %>" />
+ <%- end -%>
+ <input type="submit" value="Login" />
+ </form>
+ </body>
+ </html>
+ ERB
+ erb = ERB.new(template, trim_mode: '-')
+ html = erb.result(binding)
+ [200, { 'Content-Type' => "text/html" }, [html]]
+ end
+
+ def saml_post_back(request, user)
params = saml_params_from(request)
saml_request = binding_for(request).deserialize(params)
+ request.session[:access_token] = user.create_access_token
+
@builder = nil
url, saml_params = saml_request.response_for(
- User.new({ id: SecureRandom.uuid, email: "example@example.com" }),
+ user,
binding: :http_post,
relay_state: params[:RelayState]
- ) do |builder|
- builder.embed_signature = true
- @builder = builder
- end
+ ) { |builder| @builder = builder }
template = <<~ERB
- <!doctype html>
+ <!DOCTYPE html>
<html>
<head><title></title></head>
<body>
@@ -274,6 +326,9 @@ module Authz
when "/authorize" # RFC-6749
return post_authorize(Rack::Request.new(env))
when "/token" # RFC-6749
+ request = Rack::Request.new(env)
+ request.body
+
return [200, { 'Content-Type' => "application/json" }, [JSON.pretty_generate({
access_token: ::Authz::JWT.new(sub: SecureRandom.uuid, iat: Time.now.to_i).to_jwt,
token_type: "Bearer",
@@ -292,7 +347,7 @@ module Authz
def get_authorize(request)
template = <<~ERB
- <!doctype html>
+ <!DOCTYPE html>
<html>
<head><title></title></head>
<body>
@@ -449,6 +504,8 @@ if __FILE__ == $0
app = Rack::Builder.new do
use Rack::CommonLogger
use Rack::Reloader
+ use Rack::Session::Cookie, { domain: $host.split(":", 2)[0], path: "/", secret: SecureRandom.hex(64) }
+
map "/twirp" do
# https://github.com/arthurnn/twirp-ruby/wiki/Service-Handlers
run ::Authx::Rpc::AbilityService.new(::Authz::Rpc::Ability.new)
bin/ui
@@ -9,6 +9,7 @@ gemfile do
gem "erb", "~> 4.0"
gem "net-hippie", "~> 1.0"
gem "rack", "~> 3.0"
+ gem "rack-session", "~> 2.0"
gem "rackup", "~> 2.0"
gem "saml-kit", "~> 1.0"
gem "webrick", "~> 1.0"
@@ -137,6 +138,7 @@ 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>
@@ -157,6 +159,8 @@ if __FILE__ == $0
app = Rack::Builder.new do
use Rack::CommonLogger
use Rack::Reloader
+ use Rack::Session::Cookie, { domain: $host.split(":", 2)[0], path: "/", secret: SecureRandom.hex(64) }
+
run UI.new
end.to_app