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