OpenID Connect debut work
This commit is contained in:
parent
2af02db0d6
commit
8d8faf684c
8 changed files with 220 additions and 1 deletions
3
Gemfile
3
Gemfile
|
|
@ -149,6 +149,9 @@ gem "omniauth-twitter", "1.2.1"
|
||||||
gem "twitter", "5.15.0"
|
gem "twitter", "5.15.0"
|
||||||
gem "omniauth-wordpress", "0.2.2"
|
gem "omniauth-wordpress", "0.2.2"
|
||||||
|
|
||||||
|
# OpenID Connect
|
||||||
|
gem "openid_connect"
|
||||||
|
|
||||||
# Serializers
|
# Serializers
|
||||||
|
|
||||||
gem "active_model_serializers", "0.9.3"
|
gem "active_model_serializers", "0.9.3"
|
||||||
|
|
|
||||||
47
app/controllers/openid/authorizations_controller.rb
Normal file
47
app/controllers/openid/authorizations_controller.rb
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
class AuthorizationsController < ApplicationController
|
||||||
|
rescue_from Rack::OAuth2::Server::Authorize::BadRequest do |e|
|
||||||
|
@error = e
|
||||||
|
logger.info e.backtrace[0,10].join("\n")
|
||||||
|
render :error, status: e.status
|
||||||
|
end
|
||||||
|
|
||||||
|
def new
|
||||||
|
call_authorization_endpoint
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
call_authorization_endpoint :allow_approval, params[:approve]
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def call_authorization_endpoint(allow_approval = false, approved = false)
|
||||||
|
endpoint = AuthorizationEndpoint.new allow_approval, approved
|
||||||
|
rack_response = *endpoint.call(request.env)
|
||||||
|
@client, @response_type, @redirect_uri, @scopes, @_request_, @request_uri, @request_object = *[
|
||||||
|
endpoint.client, endpoint.response_type, endpoint.redirect_uri, endpoint.scopes, endpoint._request_, endpoint.request_uri, endpoint.request_object
|
||||||
|
]
|
||||||
|
require_authentication
|
||||||
|
if (
|
||||||
|
!allow_approval &&
|
||||||
|
(max_age = @request_object.try(:id_token).try(:max_age)) &&
|
||||||
|
current_account.last_logged_in_at < max_age.seconds.ago
|
||||||
|
)
|
||||||
|
flash[:notice] = 'Exceeded Max Age, Login Again'
|
||||||
|
unauthenticate!
|
||||||
|
require_authentication
|
||||||
|
end
|
||||||
|
respond_as_rack_app *rack_response
|
||||||
|
end
|
||||||
|
|
||||||
|
def respond_as_rack_app(status, header, response)
|
||||||
|
["WWW-Authenticate"].each do |key|
|
||||||
|
headers[key] = header[key] if header[key].present?
|
||||||
|
end
|
||||||
|
if response.redirect?
|
||||||
|
redirect_to header['Location']
|
||||||
|
else
|
||||||
|
render :new
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
4
app/controllers/openid/connect_controller.rb
Normal file
4
app/controllers/openid/connect_controller.rb
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
class ConnectController < ApplicationController
|
||||||
|
def show
|
||||||
|
end
|
||||||
|
end
|
||||||
45
app/controllers/openid/discovery_controller.rb
Normal file
45
app/controllers/openid/discovery_controller.rb
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
class DiscoveryController < ApplicationController
|
||||||
|
def show
|
||||||
|
case params[:id]
|
||||||
|
when 'webfinger'
|
||||||
|
webfinger_discovery
|
||||||
|
when 'openid-configuration'
|
||||||
|
openid_configuration
|
||||||
|
else
|
||||||
|
raise HttpError::NotFound
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def webfinger_discovery
|
||||||
|
jrd = {
|
||||||
|
links: [{
|
||||||
|
rel: OpenIDConnect::Discovery::Provider::Issuer::REL_VALUE,
|
||||||
|
href: "http://0.0.0.0:3000"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
jrd[:subject] = params[:resource] if params[:resource].present?
|
||||||
|
render json: jrd, content_type: "application/jrd+json"
|
||||||
|
end
|
||||||
|
|
||||||
|
def openid_configuration
|
||||||
|
config = OpenIDConnect::Discovery::Provider::Config::Response.new(
|
||||||
|
issuer: "http://0.0.0.0:3000",
|
||||||
|
authorization_endpoint: "#{authorizations_url}/new",
|
||||||
|
token_endpoint: access_tokens_url,
|
||||||
|
userinfo_endpoint: user_info_url,
|
||||||
|
jwks_uri: "#{authorizations_url}/jwks.json",
|
||||||
|
registration_endpoint: "http://0.0.0.0:3000/connect",
|
||||||
|
scopes_supported: "iss",
|
||||||
|
response_types_supported: "Client.available_response_types",
|
||||||
|
grant_types_supported: "Client.available_grant_types",
|
||||||
|
request_object_signing_alg_values_supported: [:HS256, :HS384, :HS512],
|
||||||
|
subject_types_supported: ['public', 'pairwise'],
|
||||||
|
id_token_signing_alg_values_supported: [:RS256],
|
||||||
|
token_endpoint_auth_methods_supported: ['client_secret_basic', 'client_secret_post'],
|
||||||
|
claims_supported: ['sub', 'iss', 'name', 'email']
|
||||||
|
)
|
||||||
|
render json: config
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -240,4 +240,13 @@ Diaspora::Application.routes.draw do
|
||||||
|
|
||||||
# Startpage
|
# Startpage
|
||||||
root :to => 'home#show'
|
root :to => 'home#show'
|
||||||
|
|
||||||
|
#OpenID Connect & OAuth
|
||||||
|
resource :openid do
|
||||||
|
resources :authorizations, only: [:new, :create]
|
||||||
|
match 'connect', to: 'connect#show', via: [:get, :post]
|
||||||
|
match '.well-known/:id', to: 'discovery#show' , :via => [:get, :post]
|
||||||
|
match 'user_info', to: 'user#show', :via => [:get, :post]
|
||||||
|
post 'access_tokens', to: proc { |env| TokenEndpoint.new.call(env) }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
82
lib/openid_connect/authorization_endpoint.rb
Normal file
82
lib/openid_connect/authorization_endpoint.rb
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
class AuthorizationEndpoint
|
||||||
|
attr_accessor :app, :account, :client, :redirect_uri, :response_type, :scopes, :_request_, :request_uri, :request_object
|
||||||
|
delegate :call, to: :app
|
||||||
|
|
||||||
|
def initialize(allow_approval = false, approved = false)
|
||||||
|
@account = nil
|
||||||
|
@app = Rack::OAuth2::Server::Authorize.new do |req, res|
|
||||||
|
@client = nil # Find the client
|
||||||
|
res.redirect_uri = @redirect_uri = req.verify_redirect_uri!(@client.redirect_uris)
|
||||||
|
if res.protocol_params_location == :fragment && req.nonce.blank?
|
||||||
|
req.invalid_request! 'nonce required'
|
||||||
|
end
|
||||||
|
@scopes = req.scope.inject([]) do |_scopes_, scope|
|
||||||
|
_scopes_ << Scope.find_by_name(scope) or req.invalid_scope! "Unknown scope: #{scope}"
|
||||||
|
end
|
||||||
|
@request_object = if (@_request_ = req.request).present?
|
||||||
|
OpenIDConnect::RequestObject.decode req.request, nil # @client.secret
|
||||||
|
elsif (@request_uri = req.request_uri).present?
|
||||||
|
OpenIDConnect::RequestObject.fetch req.request_uri, nil # @client.secret
|
||||||
|
end
|
||||||
|
if Client.available_response_types.include? Array(req.response_type).collect(&:to_s).join(' ')
|
||||||
|
if allow_approval
|
||||||
|
if approved
|
||||||
|
approved! req, res
|
||||||
|
else
|
||||||
|
req.access_denied!
|
||||||
|
end
|
||||||
|
else
|
||||||
|
@response_type = req.response_type
|
||||||
|
end
|
||||||
|
else
|
||||||
|
req.unsupported_response_type!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def approved!(req, res)
|
||||||
|
response_types = Array(req.response_type)
|
||||||
|
if response_types.include? :code
|
||||||
|
authorization = account.authorizations.create!(client: @client, redirect_uri: res.redirect_uri, nonce: req.nonce)
|
||||||
|
authorization.scopes << scopes
|
||||||
|
if @request_object
|
||||||
|
authorization.create_authorization_request_object!(
|
||||||
|
request_object: RequestObject.new(
|
||||||
|
jwt_string: @request_object.to_jwt(@client.secret, :HS256)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
res.code = authorization.code
|
||||||
|
end
|
||||||
|
if response_types.include? :token
|
||||||
|
access_token = account.access_tokens.create!(client: @client)
|
||||||
|
access_token.scopes << scopes
|
||||||
|
if @request_object
|
||||||
|
access_token.create_access_token_request_object!(
|
||||||
|
request_object: RequestObject.new(
|
||||||
|
jwt_string: @request_object.to_jwt(@client.secret, :HS256)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
res.access_token = access_token.to_bearer_token
|
||||||
|
end
|
||||||
|
if response_types.include? :id_token
|
||||||
|
_id_token_ = account.id_tokens.create!(
|
||||||
|
client: @client,
|
||||||
|
nonce: req.nonce
|
||||||
|
)
|
||||||
|
if @request_object
|
||||||
|
_id_token_.create_id_token_request_object!(
|
||||||
|
request_object: RequestObject.new(
|
||||||
|
jwt_string: @request_object.to_jwt(@client.secret, :HS256)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
res.id_token = _id_token_.to_jwt(
|
||||||
|
code: (res.respond_to?(:code) ? res.code : nil),
|
||||||
|
access_token: (res.respond_to?(:access_token) ? res.access_token : nil)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
res.approve!
|
||||||
|
end
|
||||||
|
end
|
||||||
29
lib/openid_connect/token_endpoint.rb
Normal file
29
lib/openid_connect/token_endpoint.rb
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
class TokenEndpoint
|
||||||
|
attr_accessor :app
|
||||||
|
delegate :call, to: :app
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
@app = Rack::OAuth2::Server::Token.new do |req, res|
|
||||||
|
client = Client.find_by_identifier(req.client_id) || req.invalid_client!
|
||||||
|
client.secret == req.client_secret || req.invalid_client!
|
||||||
|
case req.grant_type
|
||||||
|
when :client_credentials
|
||||||
|
res.access_token = client.access_tokens.create!.to_bearer_token
|
||||||
|
when :authorization_code
|
||||||
|
authorization = client.authorizations.valid.find_by_code(req.code)
|
||||||
|
req.invalid_grant! if authorization.blank? || !authorization.valid_redirect_uri?(req.redirect_uri)
|
||||||
|
access_token = authorization.access_token
|
||||||
|
res.access_token = access_token.to_bearer_token
|
||||||
|
if access_token.accessible?(Scope::OPENID)
|
||||||
|
res.id_token = access_token.account.id_tokens.create!(
|
||||||
|
client: access_token.client,
|
||||||
|
nonce: authorization.nonce,
|
||||||
|
request_object: authorization.request_object
|
||||||
|
).to_response_object.to_jwt IdToken.config[:private_key]
|
||||||
|
end
|
||||||
|
else
|
||||||
|
req.unsupported_grant_type!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
Reference in a new issue