require 'httparty' module PandaPal class Platform require_relative "platform/canvas" def public_jwks require "json/jwt" jwk_json = Rails.cache.fetch("panda_pal/jwks/#{jwks_url}") jwk_json ||= begin response = HTTParty.get(jwks_url) response.success? ? response.body : nil end return nil unless jwk_json.present? Rails.cache.write("panda_pal/jwks/#{jwks_url}", jwk_json, expires_in: 24.hours) JSON::JWK::Set.new(JSON.parse(jwk_json)) rescue nil end def self.from_serialized(ser) cls = ser[:platform_class].safe_constantize cls.deserialize(ser) end def self.deserialize(params) new(params) end def serialize { platform_class: self.class.to_s, } end def self.resolve_platform(params) resolved = resolve_raw_platform(params) resolved.is_a?(Class) ? resolved.new({ iss: params[:iss] }) : resolved end def self.resolve_platform_class(params) resolved = resolve_raw_platform(params) resolved.is_a?(Class) ? resolved : resolved.class end def self.resolve_raw_platform(params) platform = PandaPal.lti_options[:platform] || "canvas.instructure.com" if platform.is_a? Symbol platform_const = Object.send(platform, params) return platform_const if platform_const elsif platform.is_a? Proc platform_const = platform.call(params) return platform_const if platform_const else return Platform::Canvas if platform == "canvas.instructure.com" platform_const = platform.safe_constantize return platform_const if platform_const.is_a? Class end # TODO Default logic? return Platform::Canvas if Platform::Canvas::TRUSTED_ISSUERS.include?(params[:iss]) raise "Unknown platform '#{platform}'" end def self.organization_api return PandaPal::Organization unless defined?(self::OrgExtension) return PandaPal::Organization if PandaPal::Organization < self::OrgExtension oext = self::OrgExtension @organization_api ||= self::OrganizationApi ||= Class.new(PandaPal::Organization) do include oext end end protected def self.find_org_setting(paths, org = current_organization) paths.each do |p| p = p.split('.').map(&:to_sym) val = org.settings.dig(*p) return val if val.present? end nil end end class Platform::Generic < Platform def self.from_urls(base_url, jwks: nil, auth_redirect: nil, grant: nil) new({ base_url: base_url, jwks_url: jwks, auth_redirect_url: auth_redirect, grant_url: grant, }) end def initialize(options) @options = options end def platform_uri options[:issuer] || options[:base_url] end def jwks_url URI.join(options[:base_url], options[:jwks_url] || "/api/lti/security/jwks").to_s end def authentication_redirect_url URI.join(options[:base_url], options[:auth_redirect_url] || "/api/lti/authorize_redirect").to_s end def grant_url URI.join(options[:base_url], options[:grant_url] || "/login/oauth2/token").to_s end def serialize super.merge(options) end end end