Commit 68f6057
bin/idp
@@ -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
bin/sp
@@ -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)