module Doorkeeper module OpenidConnect module Helpers module Controller private def authenticate_resource_owner! super.tap do |owner| next unless oidc_authorization_request? handle_oidc_prompt_param!(owner) handle_oidc_max_age_param!(owner) end rescue Errors::OpenidConnectError => exception handle_oidc_error!(exception) end def oidc_authorization_request? controller_path == Doorkeeper::Rails::Routes.mapping[:authorizations][:controllers] && action_name == 'new' && pre_auth.client && pre_auth.scopes.include?('openid') end def handle_oidc_error!(exception) # clear the previous response body to avoid a DoubleRenderError self.response_body = nil # FIXME: workaround for Rails 5, see https://github.com/rails/rails/issues/25106 @_response_body = nil error_response = if pre_auth.valid? ::Doorkeeper::OAuth::ErrorResponse.new( name: exception.error_name, state: params[:state], redirect_uri: params[:redirect_uri] ) else pre_auth.error_response end response.headers.merge!(error_response.headers) if error_response.redirectable? render json: error_response.body, status: :found, location: error_response.redirect_uri else render json: error_response.body, status: error_response.status end end def handle_oidc_prompt_param!(owner) prompt_values ||= params[:prompt].to_s.split(/ +/).uniq prompt_values.each do |prompt| case prompt when 'none' then raise Errors::InvalidRequest if (prompt_values - [ 'none' ]).any? raise Errors::LoginRequired unless owner raise Errors::ConsentRequired if oidc_consent_required?(owner) when 'login' then reauthenticate_oidc_resource_owner(owner) if owner when 'consent' then render :new when 'select_account' then # TODO: let the user implement this raise Errors::AccountSelectionRequired else raise Errors::InvalidRequest end end end def handle_oidc_max_age_param!(owner) max_age = params[:max_age].to_i return unless max_age > 0 && owner auth_time = instance_exec owner, &Doorkeeper::OpenidConnect.configuration.auth_time_from_resource_owner if !auth_time || (Time.zone.now - auth_time) > max_age reauthenticate_oidc_resource_owner(owner) end end def reauthenticate_oidc_resource_owner(owner) return_to = URI.parse(request.path) return_to.query = request.query_parameters.tap do |params| params['prompt'] = params['prompt'].to_s.sub(/\blogin\s*\b/, '').strip params.delete('prompt') if params['prompt'].blank? end.to_query instance_exec owner, return_to.to_s, &Doorkeeper::OpenidConnect.configuration.reauthenticate_resource_owner raise Errors::LoginRequired unless performed? end def matching_tokens_for_oidc_resource_owner(owner) Doorkeeper::AccessToken.authorized_tokens_for(pre_auth.client.id, owner.id).select do |token| Doorkeeper::AccessToken.scopes_match?(token.scopes, pre_auth.scopes, pre_auth.client.scopes) end end def oidc_consent_required?(owner) return false if skip_authorization? matching_tokens_for_oidc_resource_owner(owner).blank? end end end end Helpers::Controller.send :prepend, OpenidConnect::Helpers::Controller end