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 "omniauth-wordpress", "0.2.2"
|
||||
|
||||
# OpenID Connect
|
||||
gem "openid_connect"
|
||||
|
||||
# Serializers
|
||||
|
||||
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
|
||||
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
|
||||
|
|
|
|||
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