Commit 8d8faf68 authored by augier's avatar augier Committed by theworldbright

OpenID Connect debut work

parent 2af02db0
......@@ -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"
......
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
class ConnectController < ApplicationController
def show
end
end
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
......@@ -419,7 +419,7 @@ ActiveRecord::Schema.define(version: 20151003142048) do
t.string "location", limit: 255
t.string "full_name", limit: 70
t.boolean "nsfw", default: false
t.boolean "public_details", default: false
t.boolean "public_details", default: false
end
add_index "profiles", ["full_name", "searchable"], name: "index_profiles_on_full_name_and_searchable", using: :btree
......
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
\ No newline at end of file
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
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment