From 0fbcb7125552c07c0eb31c0b97c64f6fa480762c Mon Sep 17 00:00:00 2001 From: theworldbright <kent@kentshikama.com> Date: Fri, 23 Oct 2015 17:26:55 -0700 Subject: [PATCH] Add support for request_uri and claims --- .../authorizations_controller.rb | 21 +++++++---- .../openid_connect/discovery_controller.rb | 7 ++-- .../openid_connect/user_info_controller.rb | 8 ++++- .../api/openid_connect/authorization.rb | 2 +- app/serializers/user_info_serializer.rb | 6 +++- .../authorization_point/endpoint.rb | 14 ++++---- .../endpoint_confirmation_point.rb | 8 +++++ .../endpoint_start_point.rb | 26 ++++++++++++++ .../authorizations_controller_spec.rb | 35 +++++++++++++++++++ spec/factories.rb | 6 ++-- 10 files changed, 112 insertions(+), 21 deletions(-) diff --git a/app/controllers/api/openid_connect/authorizations_controller.rb b/app/controllers/api/openid_connect/authorizations_controller.rb index 9cf9399b64..4ee7d391d0 100644 --- a/app/controllers/api/openid_connect/authorizations_controller.rb +++ b/app/controllers/api/openid_connect/authorizations_controller.rb @@ -76,10 +76,22 @@ module Api end def request_authorization_consent_form + add_claims_to_scopes endpoint = Api::OpenidConnect::AuthorizationPoint::EndpointStartPoint.new(current_user) handle_start_point_response(endpoint) 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) if seconds.nil? false @@ -112,14 +124,12 @@ module Api end 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.redirect_uri, endpoint.scopes, endpoint.request_object + endpoint.redirect_uri, endpoint.scopes ] save_request_parameters - @app = UserApplicationPresenter.new @o_auth_application, @scopes - render :new end @@ -128,7 +138,6 @@ module Api session[:response_type] = @response_type session[:redirect_uri] = @redirect_uri session[:scopes] = scopes_as_space_seperated_values - session[:request_object] = @request_object session[:nonce] = params[:nonce] end @@ -153,7 +162,6 @@ module Api session.delete(:response_type) session.delete(:redirect_uri) session.delete(:scopes) - session.delete(:request_object) session.delete(:nonce) end @@ -167,7 +175,6 @@ module Api req.update_param("redirect_uri", session[:redirect_uri]) req.update_param("response_type", response_type_as_space_seperated_values) req.update_param("scope", session[:scopes]) - req.update_param("request_object", session[:request_object]) req.update_param("nonce", session[:nonce]) end diff --git a/app/controllers/api/openid_connect/discovery_controller.rb b/app/controllers/api/openid_connect/discovery_controller.rb index 5a12c60076..368d361c0a 100644 --- a/app/controllers/api/openid_connect/discovery_controller.rb +++ b/app/controllers/api/openid_connect/discovery_controller.rb @@ -22,11 +22,14 @@ module Api jwks_uri: api_openid_connect_url, scopes_supported: Api::OpenidConnect::Authorization::SCOPES, 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), id_token_signing_alg_values_supported: %i(RS256), 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 diff --git a/app/controllers/api/openid_connect/user_info_controller.rb b/app/controllers/api/openid_connect/user_info_controller.rb index d5e7a8cf04..bcd6394019 100644 --- a/app/controllers/api/openid_connect/user_info_controller.rb +++ b/app/controllers/api/openid_connect/user_info_controller.rb @@ -8,7 +8,13 @@ module Api end 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 def current_user diff --git a/app/models/api/openid_connect/authorization.rb b/app/models/api/openid_connect/authorization.rb index 87cc056612..cff556fa77 100644 --- a/app/models/api/openid_connect/authorization.rb +++ b/app/models/api/openid_connect/authorization.rb @@ -17,7 +17,7 @@ module Api 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 self.refresh_token = SecureRandom.hex(32) diff --git a/app/serializers/user_info_serializer.rb b/app/serializers/user_info_serializer.rb index 4fae962a39..7e75206936 100644 --- a/app/serializers/user_info_serializer.rb +++ b/app/serializers/user_info_serializer.rb @@ -1,11 +1,15 @@ class UserInfoSerializer < ActiveModel::Serializer - attributes :sub, :nickname, :profile, :picture + attributes :sub, :name, :nickname, :profile, :picture def sub auth = serialization_options[:authorization] Api::OpenidConnect::SubjectIdentifierCreator.createSub(auth) end + def name + (object.first_name || "") + (object.last_name || "") + end + def nickname object.name end diff --git a/lib/api/openid_connect/authorization_point/endpoint.rb b/lib/api/openid_connect/authorization_point/endpoint.rb index 680d494f80..8f3392bd97 100644 --- a/lib/api/openid_connect/authorization_point/endpoint.rb +++ b/lib/api/openid_connect/authorization_point/endpoint.rb @@ -3,12 +3,13 @@ module Api module AuthorizationPoint class Endpoint 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 def initialize(user) @user = user @app = Rack::OAuth2::Server::Authorize.new do |req, res| + build_from_request_object(req) build_attributes(req, res) if OAuthApplication.available_response_types.include? Array(req.response_type).join(" ") handle_response_type(req, res) @@ -29,10 +30,6 @@ module Api raise NotImplementedError # Implemented by subclass end - def scopes - Api::OpenidConnect::Authorization::SCOPES - end - private def build_client(req) @@ -48,12 +45,17 @@ module Api end def build_scopes(req) + replace_profile_scope_with_specific_claims(req) @scopes = req.scope.map {|scope| 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 + + def auth_scopes + Api::OpenidConnect::Authorization::SCOPES + end end end end diff --git a/lib/api/openid_connect/authorization_point/endpoint_confirmation_point.rb b/lib/api/openid_connect/authorization_point/endpoint_confirmation_point.rb index dbb9abca52..c411b293b4 100644 --- a/lib/api/openid_connect/authorization_point/endpoint_confirmation_point.rb +++ b/lib/api/openid_connect/authorization_point/endpoint_confirmation_point.rb @@ -19,6 +19,14 @@ module Api end end + def replace_profile_scope_with_specific_claims(req) + # Empty + end + + def build_from_request_object(req) + # Empty + end + private def approved!(req, res) diff --git a/lib/api/openid_connect/authorization_point/endpoint_start_point.rb b/lib/api/openid_connect/authorization_point/endpoint_start_point.rb index 65cdaa1a34..3ce63cacf8 100644 --- a/lib/api/openid_connect/authorization_point/endpoint_start_point.rb +++ b/lib/api/openid_connect/authorization_point/endpoint_start_point.rb @@ -2,9 +2,35 @@ module Api module OpenidConnect module AuthorizationPoint 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) @response_type = req.response_type 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 diff --git a/spec/controllers/api/openid_connect/authorizations_controller_spec.rb b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb index befcdb35b5..0e48a44046 100644 --- a/spec/controllers/api/openid_connect/authorizations_controller_spec.rb +++ b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb @@ -22,6 +22,41 @@ describe Api::OpenidConnect::AuthorizationsController, type: :controller do 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 it "should return a form page" do post :new, client_id: client.client_id, redirect_uri: "http://localhost:3000/", response_type: "id_token", diff --git a/spec/factories.rb b/spec/factories.rb index 988a6b754f..a7a5a8dca0 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -348,19 +348,19 @@ FactoryGirl.define do factory :auth_with_read, class: Api::OpenidConnect::Authorization do o_auth_application user - scopes %w(openid read) + scopes %w(openid sub aud profile picture nickname name read) end factory :auth_with_read_and_ppid, class: Api::OpenidConnect::Authorization do association :o_auth_application, factory: :o_auth_application_with_ppid user - scopes %w(openid read) + scopes %w(openid sub aud profile picture nickname name read) end factory :auth_with_read_and_write, class: Api::OpenidConnect::Authorization do o_auth_application user - scopes %w(openid read write) + scopes %w(openid sub aud profile picture nickname name read write) end # Factories for the DiasporaFederation-gem -- GitLab