Add support for request_uri and claims
This commit is contained in:
parent
2a002d90c4
commit
0fbcb71255
10 changed files with 112 additions and 21 deletions
|
|
@ -76,10 +76,22 @@ module Api
|
||||||
end
|
end
|
||||||
|
|
||||||
def request_authorization_consent_form
|
def request_authorization_consent_form
|
||||||
|
add_claims_to_scopes
|
||||||
endpoint = Api::OpenidConnect::AuthorizationPoint::EndpointStartPoint.new(current_user)
|
endpoint = Api::OpenidConnect::AuthorizationPoint::EndpointStartPoint.new(current_user)
|
||||||
handle_start_point_response(endpoint)
|
handle_start_point_response(endpoint)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def add_claims_to_scopes
|
||||||
|
return unless params[:claims]
|
||||||
|
claims_json = JSON.parse(params[:claims])
|
||||||
|
return unless claims_json
|
||||||
|
claims_array = claims_json["userinfo"].try(:keys)
|
||||||
|
return unless claims_array
|
||||||
|
claims = claims_array.join(" ")
|
||||||
|
req = build_rack_request
|
||||||
|
req.update_param("scope", req[:scope] + " " + claims)
|
||||||
|
end
|
||||||
|
|
||||||
def logged_in_before?(seconds)
|
def logged_in_before?(seconds)
|
||||||
if seconds.nil?
|
if seconds.nil?
|
||||||
false
|
false
|
||||||
|
|
@ -112,14 +124,12 @@ module Api
|
||||||
end
|
end
|
||||||
|
|
||||||
def save_params_and_render_consent_form(endpoint)
|
def save_params_and_render_consent_form(endpoint)
|
||||||
@o_auth_application, @response_type, @redirect_uri, @scopes, @request_object = *[
|
@o_auth_application, @response_type, @redirect_uri, @scopes = *[
|
||||||
endpoint.o_auth_application, endpoint.response_type,
|
endpoint.o_auth_application, endpoint.response_type,
|
||||||
endpoint.redirect_uri, endpoint.scopes, endpoint.request_object
|
endpoint.redirect_uri, endpoint.scopes
|
||||||
]
|
]
|
||||||
save_request_parameters
|
save_request_parameters
|
||||||
|
|
||||||
@app = UserApplicationPresenter.new @o_auth_application, @scopes
|
@app = UserApplicationPresenter.new @o_auth_application, @scopes
|
||||||
|
|
||||||
render :new
|
render :new
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -128,7 +138,6 @@ module Api
|
||||||
session[:response_type] = @response_type
|
session[:response_type] = @response_type
|
||||||
session[:redirect_uri] = @redirect_uri
|
session[:redirect_uri] = @redirect_uri
|
||||||
session[:scopes] = scopes_as_space_seperated_values
|
session[:scopes] = scopes_as_space_seperated_values
|
||||||
session[:request_object] = @request_object
|
|
||||||
session[:nonce] = params[:nonce]
|
session[:nonce] = params[:nonce]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -153,7 +162,6 @@ module Api
|
||||||
session.delete(:response_type)
|
session.delete(:response_type)
|
||||||
session.delete(:redirect_uri)
|
session.delete(:redirect_uri)
|
||||||
session.delete(:scopes)
|
session.delete(:scopes)
|
||||||
session.delete(:request_object)
|
|
||||||
session.delete(:nonce)
|
session.delete(:nonce)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -167,7 +175,6 @@ module Api
|
||||||
req.update_param("redirect_uri", session[:redirect_uri])
|
req.update_param("redirect_uri", session[:redirect_uri])
|
||||||
req.update_param("response_type", response_type_as_space_seperated_values)
|
req.update_param("response_type", response_type_as_space_seperated_values)
|
||||||
req.update_param("scope", session[:scopes])
|
req.update_param("scope", session[:scopes])
|
||||||
req.update_param("request_object", session[:request_object])
|
|
||||||
req.update_param("nonce", session[:nonce])
|
req.update_param("nonce", session[:nonce])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,11 +22,14 @@ module Api
|
||||||
jwks_uri: api_openid_connect_url,
|
jwks_uri: api_openid_connect_url,
|
||||||
scopes_supported: Api::OpenidConnect::Authorization::SCOPES,
|
scopes_supported: Api::OpenidConnect::Authorization::SCOPES,
|
||||||
response_types_supported: Api::OpenidConnect::OAuthApplication.available_response_types,
|
response_types_supported: Api::OpenidConnect::OAuthApplication.available_response_types,
|
||||||
request_object_signing_alg_values_supported: %i(HS256 HS384 HS512),
|
request_object_signing_alg_values_supported: %i(none),
|
||||||
|
request_parameter_supported: true,
|
||||||
|
request_uri_parameter_supported: true,
|
||||||
subject_types_supported: %w(public pairwise),
|
subject_types_supported: %w(public pairwise),
|
||||||
id_token_signing_alg_values_supported: %i(RS256),
|
id_token_signing_alg_values_supported: %i(RS256),
|
||||||
token_endpoint_auth_methods_supported: %w(client_secret_basic client_secret_post private_key_jwt),
|
token_endpoint_auth_methods_supported: %w(client_secret_basic client_secret_post private_key_jwt),
|
||||||
claims_supported: %w(sub nickname profile picture)
|
claims_parameter_supported: true,
|
||||||
|
claims_supported: %w(sub name nickname profile picture)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,13 @@ module Api
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
render json: current_user, serializer: UserInfoSerializer, authorization: current_token.authorization
|
serializer = UserInfoSerializer.new(current_user)
|
||||||
|
auth = current_token.authorization
|
||||||
|
serializer.serialization_options = { authorization: auth }
|
||||||
|
attributes_without_essential = serializer.attributes.with_indifferent_access.select{|scope| auth.scopes.include? scope }
|
||||||
|
attributes = attributes_without_essential.merge(
|
||||||
|
sub: serializer.sub)
|
||||||
|
render json: attributes.to_json
|
||||||
end
|
end
|
||||||
|
|
||||||
def current_user
|
def current_user
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ module Api
|
||||||
|
|
||||||
scope :with_redirect_uri, ->(given_uri) { where redirect_uri: given_uri }
|
scope :with_redirect_uri, ->(given_uri) { where redirect_uri: given_uri }
|
||||||
|
|
||||||
SCOPES = %w(openid read write)
|
SCOPES = %w(openid sub aud name nickname profile picture read write)
|
||||||
|
|
||||||
def setup
|
def setup
|
||||||
self.refresh_token = SecureRandom.hex(32)
|
self.refresh_token = SecureRandom.hex(32)
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
class UserInfoSerializer < ActiveModel::Serializer
|
class UserInfoSerializer < ActiveModel::Serializer
|
||||||
attributes :sub, :nickname, :profile, :picture
|
attributes :sub, :name, :nickname, :profile, :picture
|
||||||
|
|
||||||
def sub
|
def sub
|
||||||
auth = serialization_options[:authorization]
|
auth = serialization_options[:authorization]
|
||||||
Api::OpenidConnect::SubjectIdentifierCreator.createSub(auth)
|
Api::OpenidConnect::SubjectIdentifierCreator.createSub(auth)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def name
|
||||||
|
(object.first_name || "") + (object.last_name || "")
|
||||||
|
end
|
||||||
|
|
||||||
def nickname
|
def nickname
|
||||||
object.name
|
object.name
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,13 @@ module Api
|
||||||
module AuthorizationPoint
|
module AuthorizationPoint
|
||||||
class Endpoint
|
class Endpoint
|
||||||
attr_accessor :app, :user, :o_auth_application, :redirect_uri, :response_type,
|
attr_accessor :app, :user, :o_auth_application, :redirect_uri, :response_type,
|
||||||
:scopes, :_request_, :request_uri, :request_object, :nonce
|
:scopes, :request_uri, :request_object, :nonce
|
||||||
delegate :call, to: :app
|
delegate :call, to: :app
|
||||||
|
|
||||||
def initialize(user)
|
def initialize(user)
|
||||||
@user = user
|
@user = user
|
||||||
@app = Rack::OAuth2::Server::Authorize.new do |req, res|
|
@app = Rack::OAuth2::Server::Authorize.new do |req, res|
|
||||||
|
build_from_request_object(req)
|
||||||
build_attributes(req, res)
|
build_attributes(req, res)
|
||||||
if OAuthApplication.available_response_types.include? Array(req.response_type).join(" ")
|
if OAuthApplication.available_response_types.include? Array(req.response_type).join(" ")
|
||||||
handle_response_type(req, res)
|
handle_response_type(req, res)
|
||||||
|
|
@ -29,10 +30,6 @@ module Api
|
||||||
raise NotImplementedError # Implemented by subclass
|
raise NotImplementedError # Implemented by subclass
|
||||||
end
|
end
|
||||||
|
|
||||||
def scopes
|
|
||||||
Api::OpenidConnect::Authorization::SCOPES
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def build_client(req)
|
def build_client(req)
|
||||||
|
|
@ -48,12 +45,17 @@ module Api
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_scopes(req)
|
def build_scopes(req)
|
||||||
|
replace_profile_scope_with_specific_claims(req)
|
||||||
@scopes = req.scope.map {|scope|
|
@scopes = req.scope.map {|scope|
|
||||||
scope.tap do |scope_name|
|
scope.tap do |scope_name|
|
||||||
req.invalid_scope! "Unknown scope: #{scope_name}" unless scopes.include? scope_name
|
req.invalid_scope! "Unknown scope: #{scope_name}" unless auth_scopes.include? scope_name
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def auth_scopes
|
||||||
|
Api::OpenidConnect::Authorization::SCOPES
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,14 @@ module Api
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def replace_profile_scope_with_specific_claims(req)
|
||||||
|
# Empty
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_from_request_object(req)
|
||||||
|
# Empty
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def approved!(req, res)
|
def approved!(req, res)
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,35 @@ module Api
|
||||||
module OpenidConnect
|
module OpenidConnect
|
||||||
module AuthorizationPoint
|
module AuthorizationPoint
|
||||||
class EndpointStartPoint < Endpoint
|
class EndpointStartPoint < Endpoint
|
||||||
|
def build_from_request_object(req)
|
||||||
|
request_object = build_request_object(req)
|
||||||
|
return unless request_object
|
||||||
|
claims = request_object.raw_attributes.with_indifferent_access[:claims].try(:[], :userinfo).try(:keys)
|
||||||
|
return unless claims
|
||||||
|
req.update_param("scope", req.scope + claims)
|
||||||
|
end
|
||||||
|
|
||||||
def handle_response_type(req, _res)
|
def handle_response_type(req, _res)
|
||||||
@response_type = req.response_type
|
@response_type = req.response_type
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def replace_profile_scope_with_specific_claims(req)
|
||||||
|
profile_claims = %w(sub aud name nickname profile picture)
|
||||||
|
scopes_as_claims = req.scope.map { |scope| scope == "profile" ? profile_claims : [scope] }.flatten!.uniq
|
||||||
|
req.update_param("scope", scopes_as_claims)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def build_request_object(req)
|
||||||
|
if req.request_uri.present?
|
||||||
|
OpenIDConnect::RequestObject.fetch req.request_uri
|
||||||
|
elsif req.request.present?
|
||||||
|
OpenIDConnect::RequestObject.decode req.request
|
||||||
|
else
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,41 @@ describe Api::OpenidConnect::AuthorizationsController, type: :controller do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "using claims" do
|
||||||
|
it "should return a form page" do
|
||||||
|
get :new, client_id: client.client_id, redirect_uri: "http://localhost:3000/", response_type: "id_token",
|
||||||
|
scope: "openid", claims: "{\"userinfo\": {\"name\": {\"essential\": true}}}", nonce: SecureRandom.hex(16),
|
||||||
|
state: SecureRandom.hex(16)
|
||||||
|
expect(response.body).to match("Diaspora Test Client")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "as a request object" do
|
||||||
|
it "should return a form page" do
|
||||||
|
header = JWT.encoded_header("none")
|
||||||
|
payload_hash = { client_id: client.client_id, redirect_uri: "http://localhost:3000/", response_type: "id_token",
|
||||||
|
scope: "openid", nonce: "hello", state: "hello", claims: { userinfo: { name: { essential: true } } } }
|
||||||
|
payload = JWT.encoded_payload(JSON.parse(payload_hash.to_json))
|
||||||
|
request_object = header + "." + payload + "."
|
||||||
|
get :new, client_id: client.client_id, redirect_uri: "http://localhost:3000/", response_type: "id_token",
|
||||||
|
scope: "openid", nonce: "hello", state: "hello", request: request_object
|
||||||
|
expect(response.body).to match("Diaspora Test Client")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "as a request object with no claims" do
|
||||||
|
it "should return a form page" do
|
||||||
|
header = JWT.encoded_header("none")
|
||||||
|
payload_hash = { client_id: client.client_id, redirect_uri: "http://localhost:3000/",
|
||||||
|
response_type: "id_token", scope: "openid", nonce: "hello", state: "hello" }
|
||||||
|
payload = JWT.encoded_payload(JSON.parse(payload_hash.to_json))
|
||||||
|
request_object = header + "." + payload + "."
|
||||||
|
get :new, client_id: client.client_id, redirect_uri: "http://localhost:3000/", response_type: "id_token",
|
||||||
|
scope: "openid", nonce: "hello", state: "hello", request: request_object
|
||||||
|
expect(response.body).to match("Diaspora Test Client")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context "as POST request" do
|
context "as POST request" do
|
||||||
it "should return a form page" do
|
it "should return a form page" do
|
||||||
post :new, client_id: client.client_id, redirect_uri: "http://localhost:3000/", response_type: "id_token",
|
post :new, client_id: client.client_id, redirect_uri: "http://localhost:3000/", response_type: "id_token",
|
||||||
|
|
|
||||||
|
|
@ -348,19 +348,19 @@ FactoryGirl.define do
|
||||||
factory :auth_with_read, class: Api::OpenidConnect::Authorization do
|
factory :auth_with_read, class: Api::OpenidConnect::Authorization do
|
||||||
o_auth_application
|
o_auth_application
|
||||||
user
|
user
|
||||||
scopes %w(openid read)
|
scopes %w(openid sub aud profile picture nickname name read)
|
||||||
end
|
end
|
||||||
|
|
||||||
factory :auth_with_read_and_ppid, class: Api::OpenidConnect::Authorization do
|
factory :auth_with_read_and_ppid, class: Api::OpenidConnect::Authorization do
|
||||||
association :o_auth_application, factory: :o_auth_application_with_ppid
|
association :o_auth_application, factory: :o_auth_application_with_ppid
|
||||||
user
|
user
|
||||||
scopes %w(openid read)
|
scopes %w(openid sub aud profile picture nickname name read)
|
||||||
end
|
end
|
||||||
|
|
||||||
factory :auth_with_read_and_write, class: Api::OpenidConnect::Authorization do
|
factory :auth_with_read_and_write, class: Api::OpenidConnect::Authorization do
|
||||||
o_auth_application
|
o_auth_application
|
||||||
user
|
user
|
||||||
scopes %w(openid read write)
|
scopes %w(openid sub aud profile picture nickname name read write)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Factories for the DiasporaFederation-gem
|
# Factories for the DiasporaFederation-gem
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue