Class: AtomicLti::OpenIdMiddleware
- Inherits:
-
Object
- Object
- AtomicLti::OpenIdMiddleware
- Defined in:
- lib/atomic_lti/open_id_middleware.rb
Instance Method Summary collapse
- #call(env) ⇒ Object
- #error!(body = "Error", status = 500, headers = { "Content-Type" => "text/html" }) ⇒ Object
- #handle_init(request) ⇒ Object
- #handle_lti_launch(env, request) ⇒ Object
- #handle_redirect(request) ⇒ Object
- #init_paths ⇒ Object
-
#initialize(app) ⇒ OpenIdMiddleware
constructor
A new instance of OpenIdMiddleware.
- #matches_redirect?(request) ⇒ Boolean
- #matches_target_link?(request) ⇒ Boolean
- #redirect_paths ⇒ Object
- #validate_launch(request, validate_target_link_url, destroy_state) ⇒ Object
Constructor Details
#initialize(app) ⇒ OpenIdMiddleware
Returns a new instance of OpenIdMiddleware.
7 8 9 |
# File 'lib/atomic_lti/open_id_middleware.rb', line 7 def initialize(app) @app = app end |
Instance Method Details
#call(env) ⇒ Object
219 220 221 222 223 224 225 226 227 228 229 230 |
# File 'lib/atomic_lti/open_id_middleware.rb', line 219 def call(env) request = Rack::Request.new(env) if init_paths.include?(request.path) handle_init(request) elsif matches_redirect?(request) handle_redirect(request) elsif matches_target_link?(request) && request.params["id_token"].present? handle_lti_launch(env, request) else @app.call(env) end end |
#error!(body = "Error", status = 500, headers = { "Content-Type" => "text/html" }) ⇒ Object
215 216 217 |
# File 'lib/atomic_lti/open_id_middleware.rb', line 215 def error!(body = "Error", status = 500, headers = { "Content-Type" => "text/html" }) [status, headers, [body]] end |
#handle_init(request) ⇒ Object
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
# File 'lib/atomic_lti/open_id_middleware.rb', line 23 def handle_init(request) platform = AtomicLti::Platform.find_by(iss: request.params["iss"]) if !platform raise AtomicLti::Exceptions::NoLTIPlatform.new(iss: request.params["iss"]) end nonce, state = AtomicLti::OpenId.generate_state headers = { "Content-Type" => "text/html" } Rack::Utils.( headers, "#{OPEN_ID_COOKIE_PREFIX}storage", { value: "1", path: "/", max_age: 365.days, http_only: false, secure: true, same_site: "None", partitioned: true } ) Rack::Utils.( headers, "#{OPEN_ID_COOKIE_PREFIX}#{state}", { value: 1, path: "/", max_age: 1.minute, http_only: false, secure: true, same_site: "None", partitioned: true } ) # Ensure our cookies are partitioned. This can be removed once our Rack version # understands the partitioned: argument above. headers[Rack::SET_COOKIE] = (headers[Rack::SET_COOKIE]) redirect_uri = [request.base_url, AtomicLti.oidc_redirect_path].join response_url = build_oidc_response(request, state, nonce, redirect_uri) if request..present? || !AtomicLti.enforce_csrf_protection # we know cookies will work, so redirect headers["Location"] = response_url [302, headers, ["Found"]] else # cookies might not work, so render our javascript form if request.params["lti_storage_target"].present? && AtomicLti. lti_storage_params = build_lti_storage_params(request, platform) end html = ApplicationController.renderer.render( :html, layout: false, template: "atomic_lti/shared/init", assigns: { settings: { state: state, responseUrl: response_url, ltiStorageParams: lti_storage_params, relaunchInitUrl: relaunch_init_url(request), privacyPolicyUrl: AtomicLti.privacy_policy_url, privacyPolicyMessage: AtomicLti., openIdCookiePrefix: OPEN_ID_COOKIE_PREFIX, }, }, ) [200, headers, [html]] end end |
#handle_lti_launch(env, request) ⇒ Object
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 |
# File 'lib/atomic_lti/open_id_middleware.rb', line 179 def handle_lti_launch(env, request) id_token_decoded, state, state_verified = validate_launch(request, true, true) id_token = request.params["id_token"] update_install(id_token: id_token_decoded) update_platform_instance(id_token: id_token_decoded) update_deployment(id_token: id_token_decoded) update_lti_context(id_token: id_token_decoded) errors = id_token_decoded.dig(AtomicLti::Definitions::TOOL_PLATFORM_CLAIM, "errors") if errors.present? && !errors["errors"].empty? Rails.logger.error("Detected errors in lti launch: #{errors}, id_token: #{id_token}") end env["atomic.validated.decoded_id_token"] = id_token_decoded env["atomic.validated.id_token"] = id_token platform = AtomicLti::Platform.find_by!(iss: id_token_decoded["iss"]) # Add the values needed to do client side validate to the environment env["atomic.validated.state_validation"] = { state: state, state_verified: state_verified, } if !state_verified && AtomicLti. env["atomic.validated.state_validation"][:lti_storage_params] = build_lti_storage_params(request, platform) end # Delete the state cookie status, headers, body = @app.call(env) # Rack::Utils.delete_cookie_header(headers, "#{OPEN_ID_COOKIE_PREFIX}#{state}") [status, headers, body] end |
#handle_redirect(request) ⇒ Object
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
# File 'lib/atomic_lti/open_id_middleware.rb', line 106 def handle_redirect(request) id_token_decoded, _state, _state_verified = validate_launch(request, false, false) uri = URI(request.url) # Technically the target_link_uri is not required and the certification suite # does not send it on a deep link launch. Typically target link uri will be present # but at least for the certification suite we have to have a backup default # value that can be set in the configuration of Atomic LTI using # the default_deep_link_path target_link_uri = id_token_decoded[AtomicLti::Definitions::TARGET_LINK_URI_CLAIM] || File.join("#{uri.scheme}://#{uri.host}", AtomicLti.default_deep_link_path) target = URI.parse(target_link_uri) # Optionally update the target link host to match the redirect host if AtomicLti.update_target_link_host && target.host != uri.host target.host = uri.host end # We want to strip out the redirect path params from the request params # so that we can support having the redirect path be the same as the # launch path, only differentiated by a query parameter. This is needed # because socialize journals redirects straight to /lti_launches and we # have preexisting lti keys that we won't get to migrate and can't break redirect_uri = URI.parse(AtomicLti.oidc_redirect_path) redirect_path_params = if redirect_uri.query CGI.parse(redirect_uri.query) else {} end launch_params = request.params.except(*redirect_path_params.keys) html = ApplicationController.renderer.render( :html, layout: false, template: "atomic_lti/shared/redirect", assigns: { launch_params: launch_params, launch_url: target, }, ) [200, { "Content-Type" => "text/html" }, [html]] end |
#init_paths ⇒ Object
11 12 13 14 15 |
# File 'lib/atomic_lti/open_id_middleware.rb', line 11 def init_paths [ AtomicLti.oidc_init_path, ] end |
#matches_redirect?(request) ⇒ Boolean
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
# File 'lib/atomic_lti/open_id_middleware.rb', line 152 def matches_redirect?(request) if AtomicLti.oidc_redirect_path.blank? raise AtomicLti::Exceptions::ConfigurationError.new("AtomicLti.oidc_redirect_path is not configured") end redirect_uri = URI.parse(AtomicLti.oidc_redirect_path) redirect_path_params = if redirect_uri.query CGI.parse(redirect_uri.query) else {} end matches_redirect_path = request.path == redirect_uri.path return false if !matches_redirect_path params_match = redirect_path_params.all? { |key, values| request.params[key] == values.first } matches_redirect_path && params_match end |
#matches_target_link?(request) ⇒ Boolean
173 174 175 176 177 |
# File 'lib/atomic_lti/open_id_middleware.rb', line 173 def matches_target_link?(request) AtomicLti.target_link_path_prefixes.any? do |prefix| request.path.starts_with? prefix end || request.path.starts_with?(AtomicLti.default_deep_link_path) end |
#redirect_paths ⇒ Object
17 18 19 20 21 |
# File 'lib/atomic_lti/open_id_middleware.rb', line 17 def redirect_paths [ AtomicLti.oidc_redirect_path, ] end |
#validate_launch(request, validate_target_link_url, destroy_state) ⇒ Object
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
# File 'lib/atomic_lti/open_id_middleware.rb', line 80 def validate_launch(request, validate_target_link_url, destroy_state) # Validate and decode id_token raise AtomicLti::Exceptions::NoLTIToken if request.params["id_token"].blank? id_token_decoded = AtomicLti::Authorization.validate_token(request.params["id_token"]) raise AtomicLti::Exceptions::InvalidLTIToken.new if id_token_decoded.nil? # Validate id token contents AtomicLti::Lti.validate!(id_token_decoded, request.url, validate_target_link_url) # Check for the state cookie state_verified = false state = request.params["state"] if request.["open_id_#{state}"] state_verified = true end # Validate the state and nonce if !AtomicLti::OpenId.validate_state(id_token_decoded["nonce"], state, destroy_state) raise AtomicLti::Exceptions::OpenIDStateError.new("Invalid OIDC state.") end [id_token_decoded, state, state_verified] end |