module Api module OpenidConnect class AuthorizationsController < ApplicationController rescue_from Rack::OAuth2::Server::Authorize::BadRequest do |e| logger.info e.backtrace[0, 10].join("\n") error, _description = e.message.split(" :: ") handle_params_error(error, "The request was malformed: please double check the client id and redirect uri.") end rescue_from OpenSSL::SSL::SSLError do |e| logger.info e.backtrace[0, 10].join("\n") handle_params_error("bad_request", e.message) end rescue_from JSON::JWS::VerificationFailed do |e| logger.info e.backtrace[0, 10].join("\n") handle_params_error("bad_request", e.message) end before_action :auth_user_unless_prompt_none! def new auth = Api::OpenidConnect::Authorization.find_by_client_id_user_and_scopes(params[:client_id], current_user, params[:scope]) reset_auth(auth) if logged_in_before?(params[:max_age]) reauthenticate(params) elsif params[:prompt] prompt = params[:prompt].split(" ") handle_prompt(prompt, auth) else handle_authorization_form(auth) end end def create restore_request_parameters process_authorization_consent(params[:approve]) end def destroy authorization = Api::OpenidConnect::Authorization.find_by(id: params[:id]) if authorization authorization.destroy else flash[:error] = I18n.t("api.openid_connect.authorizations.destroy.fail", id: params[:id]) end redirect_to api_openid_connect_user_applications_url end private def reset_auth(auth) return unless auth auth.o_auth_access_tokens.destroy_all auth.code_used = false auth.save end def handle_prompt(prompt, auth) if prompt.include? "select_account" handle_params_error("account_selection_required", "There is no support for choosing among multiple accounts") elsif prompt.include? "consent" request_authorization_consent_form else handle_authorization_form(auth) end end def handle_authorization_form(auth) if auth process_authorization_consent("true") else request_authorization_consent_form end 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 req = build_rack_request claims = claims_array.unshift(req[:scope]).join(" ") req.update_param("scope", claims) end def logged_in_before?(seconds) if seconds.nil? false else (Time.now - current_user.current_sign_in_at) > seconds.to_i end end def handle_start_point_response(endpoint) _status, header, response = endpoint.call(request.env) if response.redirect? redirect_to header["Location"] else save_params_and_render_consent_form(endpoint) end end def save_params_and_render_consent_form(endpoint) @o_auth_application = endpoint.o_auth_application @response_type = endpoint.response_type @redirect_uri = endpoint.redirect_uri @scopes = endpoint.scopes save_request_parameters @app = UserApplicationPresenter.new @o_auth_application, @scopes render :new end def save_request_parameters session[:client_id] = @o_auth_application.client_id session[:response_type] = @response_type session[:redirect_uri] = @redirect_uri session[:scopes] = scopes_as_space_seperated_values session[:state] = params[:state] session[:nonce] = params[:nonce] end def scopes_as_space_seperated_values @scopes.join(" ") end def process_authorization_consent(approved_string) endpoint = Api::OpenidConnect::AuthorizationPoint::EndpointConfirmationPoint.new( current_user, to_boolean(approved_string)) handle_confirmation_endpoint_response(endpoint) end def handle_confirmation_endpoint_response(endpoint) _status, header, _response = endpoint.call(request.env) delete_authorization_session_variables redirect_to header["Location"] end def delete_authorization_session_variables session.delete(:client_id) session.delete(:response_type) session.delete(:redirect_uri) session.delete(:scopes) session.delete(:state) session.delete(:nonce) end def to_boolean(str) str.downcase == "true" end def restore_request_parameters req = build_rack_request req.update_param("client_id", session[:client_id]) 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("state", session[:state]) req.update_param("nonce", session[:nonce]) end def build_rack_request Rack::Request.new(request.env) end def response_type_as_space_seperated_values [*session[:response_type]].join(" ") end def handle_params_error(error, error_description) if params[:client_id] && params[:redirect_uri] handle_params_error_when_client_id_and_redirect_uri_exists(error, error_description) else render_error I18n.t("api.openid_connect.error_page.could_not_authorize"), error_description end end def handle_params_error_when_client_id_and_redirect_uri_exists(error, error_description) app = Api::OpenidConnect::OAuthApplication.find_by(client_id: params[:client_id]) if app && app.redirect_uris.include?(params[:redirect_uri]) redirect_prompt_error_display(error, error_description) else render_error I18n.t("api.openid_connect.error_page.could_not_authorize"), "Invalid client id or redirect uri" end end def redirect_prompt_error_display(error, error_description) redirect_params_hash = {error: error, error_description: error_description, state: params[:state]} redirect_fragment = redirect_params_hash.compact.map {|key, value| key.to_s + "=" + value }.join("&") redirect_to params[:redirect_uri] + "?" + redirect_fragment end def auth_user_unless_prompt_none! prompt = params[:prompt] if prompt && prompt.include?("none") handle_prompt_none elsif prompt && prompt.include?("login") new_params = params.except("controller", "action").permit!.to_h.merge(prompt: prompt.remove("login")) reauthenticate(new_params) else authenticate_user! end end def handle_prompt_none if params[:prompt] == "none" if user_signed_in? handle_prompt_with_signed_in_user else handle_params_error("login_required", "User must already be logged in when `prompt` is `none`") end else handle_params_error("invalid_request", "The 'none' value cannot be used with any other prompt value") end end def handle_prompt_with_signed_in_user client_id = params[:client_id] if client_id auth = Api::OpenidConnect::Authorization.find_by_client_id_user_and_scopes(client_id, current_user, params[:scope]) if auth process_authorization_consent("true") else handle_params_error("interaction_required", "User must already be authorized when `prompt` is `none`") end else handle_params_error("bad_request", "Client ID is missing from request") end end def reauthenticate(params) sign_out current_user redirect_to new_api_openid_connect_authorization_path(params) end def render_error(error_description, detailed_error=nil) @error_description = error_description @detailed_error = detailed_error if request.format == :mobile render "api/openid_connect/error/error.mobile", layout: "application.mobile" else render "api/openid_connect/error/error", layout: "with_header_with_footer" end end end end end