diff options
| -rwxr-xr-x | bin/idp | 130 | ||||
| -rwxr-xr-x | bin/sp | 26 |
2 files changed, 104 insertions, 52 deletions
@@ -50,10 +50,49 @@ class IdentityProvider @storage = {} end + def call(env) + path = env['PATH_INFO'] + case env['REQUEST_METHOD'] + when 'GET' + case path + when '/.well-known/openid-configuration' + return openid_metadata + when '/.well-known/oauth-authorization-server' + return oauth_metadata + when '/.well-known/webfinger' # RFC-7033 + return not_found + when "/metadata.xml" + return saml_metadata + when "/sessions/new" + return saml_post_back(Rack::Request.new(env)) + when "/oauth/authorize" # RFC-6749 + return get_authorize(Rack::Request.new(env)) + else + return not_found + end + when 'POST' + case path + when "/sessions/new" + return saml_post_back(Rack::Request.new(env)) + when "/oauth/authorize" # RFC-6749 + return post_authorize(Rack::Request.new(env)) + when "/oauth/token" # RFC-6749 + return not_found + when "/oauth/revoke" # RFC-7009 + return not_found + else + return not_found + end + end + not_found + end + + private + # Download IDP Metadata # # GET /metadata.xml - def metadata + def saml_metadata xml = Saml::Kit::Metadata.build_xml do |builder| builder.contact_email = 'hi@example.com' builder.organization_name = "Acme, Inc" @@ -96,17 +135,58 @@ class IdentityProvider })]] end + def get_authorize(request) + template = <<~ERB + <!doctype html> + <html> + <head><title></title></head> + <body> + <h2>Authorize?</h2> + <form action="/oauth/authorize" method="post"> + <input type="hidden" name="client_id" value="<%= request.params['client_id'] %>" /> + <input type="hidden" name="scope" value="<%= request.params['scope'] %>" /> + <input type="hidden" name="redirect_uri" value="<%= request.params['redirect_uri'] %>" /> + <input type="hidden" name="response_mode" value="<%= request.params['response_mode'] %>" /> + <input type="hidden" name="response_type" value="<%= request.params['response_type'] %>" /> + <input type="hidden" name="state" value="<%= request.params['state'] %>" /> + <input type="hidden" name="code_challenge_method" value="<%= request.params['code_challenge_method'] %>" /> + <input type="hidden" name="code_challenge" value="<%= request.params['code_challenge'] %>" /> + <input type="submit" value="Submit" /> + </form> + </body> + </html> + ERB + html = ERB.new(template, trim_mode: '-').result(binding) + [200, { 'Content-Type' => "text/html" }, [html]] + end + + def post_authorize(request) + params = request.params.slice('client_id', 'redirect_uri', 'response_type', 'response_mode', 'state', 'code_challenge_method', 'code_challenge', 'scope') + case params['response_type'] + when 'code' + case params['response_mode'] + when 'fragment' + return [302, { 'Location' => "#{params['redirect_uri']}#code=#{SecureRandom.uuid}&state=#{params['state']}" }, []] + when 'query' + return [302, { 'Location' => "#{params['redirect_uri']}?code=#{SecureRandom.uuid}&state=#{params['state']}" }, []] + else + # TODO:: form post + end + + when 'token' + return not_found + else + return not_found + end + end + # GET /.well-known/openid-configuration def openid_metadata [200, { 'Content-Type' => "application/json" }, [JSON.pretty_generate({ issuer: "http://localhost:8282/.well-known/oauth-authorization-server", authorization_endpoint: "http://localhost:8282/oauth/authorize", token_endpoint: "http://localhost:8282/oauth/token", - # token_endpoint_auth_methods_supported: [], - # token_endpoint_auth_signing_alg_values_supported: [], userinfo_endpoint: "http://localhost:8282/oidc/user/", - # check_session_iframe: nil, - # end_session_endpoint: nil, jwks_uri: "", # RFC-7517 registration_endpoint: nil, scopes_supported: ["openid", "profile", "email"], @@ -156,45 +236,7 @@ class IdentityProvider })]] end - # auth service - def call(env) - path = env['PATH_INFO'] - case env['REQUEST_METHOD'] - when 'GET' - case path - when '/.well-known/openid-configuration' - return openid_metadata - when '/.well-known/oauth-authorization-server' - return oauth_metadata - when '/.well-known/webfinger' # RFC-7033 - return not_found - when "/metadata.xml" - return metadata - when "/sessions/new" - return post_back(Rack::Request.new(env)) - when "oauth/authorize" # RFC-6749 - return not_found - else - return not_found - end - when 'POST' - case path - when "/sessions/new" - return post_back(Rack::Request.new(env)) - when "oauth/token" # RFC-6749 - return not_found - when "oauth/revoke" # RFC-7009 - return not_found - else - return not_found - end - end - not_found - end - - private - - def post_back(request) + def saml_post_back(request) params = saml_params_from(request) saml_request = binding_for(request).deserialize(params) @builder = nil @@ -59,15 +59,17 @@ class ServiceProvider case path when "/metadata.xml" return metadata + when "/openid/new" + return redirect_to("http://localhost:8282/oauth/authorize?client_id=service-provider&state=example&redirect_uri=http://localhost:8283/oauth/callback&response_type=code&response_mode=query&scope=openid") + when "/oauth/callback" + return oauth_callback(Rack::Request.new(env)) else - # TODO Generate a post to the IdP - return post_to_idp(Rack::Request.new(env)) + return saml_post_to_idp(Rack::Request.new(env)) end when 'POST' case path when "/assertions" - # TODO:: Render the SAMLResponse from the IdP - return assertions(Rack::Request.new(env)) + return saml_assertions(Rack::Request.new(env)) else return not_found end @@ -81,7 +83,16 @@ class ServiceProvider [404, {}, []] end - def post_to_idp(request) + def redirect_to(location) + [302, { 'Location' => location }, []] + end + + def oauth_callback(request) + # TODO:: Exchange grant (authorization_code) for an access token + [200, { "Content-Type" => "application/json" }, [JSON.pretty_generate(request.params)]] + end + + def saml_post_to_idp(request) idp = Saml::Kit.registry.metadata_for('http://localhost:8282/metadata.xml') relay_state = Base64.strict_encode64(JSON.generate(redirect_to: '/dashboard')) @@ -107,12 +118,11 @@ class ServiceProvider </body> </html> ERB - erb = ERB.new(template, trim_mode: '-') - html = erb.result(binding) + html = ERB.new(template, trim_mode: '-').result(binding) [200, { 'Content-Type' => "text/html" }, [html]] end - def assertions(request) + def saml_assertions(request) sp = Saml::Kit.registry.metadata_for('http://localhost:8283/metadata.xml') saml_binding = sp.assertion_consumer_service_for(binding: :http_post) saml_response = saml_binding.deserialize(request.params) |
