# RuboCops - Documentation # ------------------------------------------------------------------------------ # See: https://rubocop.readthedocs.io/en/latest/ # RuboCops - Disabled Cops # ------------------------------------------------------------------------------ # rubocop:disable Metrics/BlockLength # rubocop:disable Metrics/ClassLength # rubocop:disable Metrics/LineLength # rubocop:disable Style/StringLiterals # rubocop:disable Style/Documentation # rubocop:disable Metrics/BlockNesting # rubocop:disable Layout/ClosingParenthesisIndentation # rubocop:disable Layout/LeadingCommentSpace # rubocop:disable Layout/EmptyLines # rubocop:disable Layout/EmptyLinesAroundBlockBody # rubocop:disable Layout/FirstParameterIndentation # rubocop:disable Layout/CommentIndentation # rubocop:disable Layout/AlignParameters # rubocop:disable Layout/AlignHash # rubocop:disable Layout/TrailingWhitespace # rubocop:disable Layout/IndentHash # rubocop:disable Layout/SpaceAroundOperators # rubocop:disable Layout/ExtraSpacing # rubocop:disable Style/UnlessElse # rubocop:disable Style/HashSyntax # ------------------------------------------------------------------------------ # ~/lib/j1_auth_manager/auth_manager/.rb # # Provides authentication services based on Warden|OmniAuth # # Product/Info: # https://jekyll.one # # Copyright (C) 2019 Juergen Adams # # J1 Template is licensed under the MIT License. # See: https://github.com/jekyll-one-org/j1_template/blob/master/LICENSE # # ------------------------------------------------------------------------------ # NOTES # # ------------------------------------------------------------------------------ # frozen_string_literal: true module J1App class AuthManager < Sinatra::Base include J1App::Helpers include J1App::GithubHelpers # ========================================================================== # Sinatra Framework settings # ========================================================================== # NOTE: https://stackoverflow.com/questions/7847536/sinatra-in-facebook-iframe # #set :protection, :except => :frame_options # Check: http://sinatrarb.com/intro.html # #set :static_cache_control, [:public, :max_age => 10] # ========================================================================== # Base App and Warden Framework settings # ========================================================================== session_data = {} # user_state_data = { # :authenticated => 'false', # :requested_page => '/', # :user_name => 'unknown', # :users_allowed => 'unknown', # :user_id => 'unknown', # :provider => 'unknown', # :provider_url => '/', # :payment_info => 'unknown', # :permissions => 'unknown', # :writer => 'middleware' # } user_state_data = { :authenticated => 'false', :requested_page => '/', :user_name => 'visitor', :users_allowed => 'all', :user_id => 'unknown', :provider => 'j1', :provider_membership => 'guest', :provider_site_url => 'https://jekyll.one', :provider_home_url => 'https://jekyll.one', :provider_blog_url => 'https://jekyll.one', :provider_member_url => 'https://jekyll.one', :payment_info => 'unknown', :provider_permissions => 'public', :creator => 'middleware', :writer => 'middleware', :mode => 'app' } # Enable SSL for the rack session if configured # -------------------------------------------------------------------------- require 'rack-ssl-enforcer' if J1App.ssl? use Rack::SslEnforcer if J1App.ssl? # Set the session cookie used by Rack to track all relevant data # for the authentication service # -------------------------------------------------------------------------- use Rack::Session::Cookie, http_only: true, # if set to 'true', make session cookie visible to the browser (document) for HTTP key: 'j1.app.session', secret: ENV['J1_SESSION_SECRET'] || SecureRandom.hex # use Rack::Cache do |config| # # # # ------------------------------------------------------------------------ # config.middleware.delete(Rack::Cache) # end # ========================================================================== # Warden Framework initialisation # ========================================================================== # Define what (user) data should be put (serialized) into the session # on requests and responses from Rack environment into the warden # environment (env['warden']). # -------------------------------------------------------------------------- Warden::Manager.serialize_into_session do |user| user end Warden::Manager.serialize_from_session do |user| user end # ========================================================================== # OmniAuth|Warden Framework initialisation # ========================================================================== # Set the 'default' authentication strategy and exception handler # (for warden) if the user was not explicitly signed in (signin dialog). # If 'signin' fails, the default exception 'signin_failure' is thrown # (used for all OmniAuth strategies registered). # -------------------------------------------------------------------------- signin_failure = ->(_e) { Rack::Response.new("Can't login", 401).finish } use Warden::Manager do |config| # OmniAuth strategies are name-spaced by 'omni' (see: warden_omniauth.rb) # ------------------------------------------------------------------------ config.default_strategies :"omni_#{J1App.default_provider}" config.failure_app = signin_failure end use OmniAuth::Builder do |config| # Rescue OmniAuth::Strategies::OAuth2::CallbackError # ------------------------------------------------------------------------ config.on_failure do new_path = '/redirect_on_failure' Rack::Response.new(['302 Moved'], 302, 'Location' => new_path).finish end # Detect and set supported authentication strategies for OmniAuth # ------------------------------------------------------------------------ # Additional (strategy) option skip_extra, default: true # # If true, skips the collection of raw data (extra) to NOT blow # up the session cookie (as it is limited to 4K) skip_extra = true if J1App.active_providers.include? 'patreon' scope = J1App.auth_config['providers']['patreon']['scope'].join(',') data_collection = J1App.auth_config['providers']['patreon']['data_fields'].join(',') skip_extra = false if data_collection =~ /raw/i provider :patreon, ENV['PATREON_CLIENT_ID'], ENV['PATREON_CLIENT_SECRET'], scope: "#{scope}", skip_extra: skip_extra end if J1App.active_providers.include? 'disqus' scope = J1App.auth_config['providers']['disqus']['scope'].join(',') data_collection = J1App.auth_config['providers']['disqus']['data_fields'].join(',') skip_extra = false if data_collection =~ /raw/i provider :disqus, ENV['DISQUS_CLIENT_ID'], ENV['DISQUS_CLIENT_SECRET'], scope: "#{scope}", skip_extra: skip_extra end if J1App.active_providers.include? 'facebook' scope = J1App.auth_config['providers']['facebook']['scope'].join(',') data_collection = J1App.auth_config['providers']['facebook']['data_fields'].join(',') skip_extra = false if data_collection =~ /raw/i provider :facebook, ENV['FACEBOOK_CLIENT_ID'], ENV['FACEBOOK_CLIENT_SECRET'], scope: "#{scope}", skip_extra: skip_extra end if J1App.active_providers.include? 'github' scope = J1App.auth_config['providers']['github']['scope'].join(',') data_collection = J1App.auth_config['providers']['github']['data_fields'].join(',') skip_extra = false if data_collection =~ /raw/i provider :github, ENV['GITHUB_CLIENT_ID'], ENV['GITHUB_CLIENT_SECRET'], scope: "#{scope}", skip_extra: skip_extra end if J1App.active_providers.include? 'twitter' scope = J1App.auth_config['providers']['twitter']['scope'].join(',') data_collection = J1App.auth_config['providers']['twitter']['data_fields'].join(',') skip_extra = false if data_collection =~ /raw/i provider :twitter, ENV['TWITTER_CLIENT_ID'], ENV['TWITTER_CLIENT_SECRET'], scope: "#{scope}", skip_extra: skip_extra end end # Set the (internal) endpoint if a user is successfully authenticated # -------------------------------------------------------------------------- use J1WardenOmniAuth do |config| config.redirect_after_callback = '/post_authentication' end # Add the internal logger from Rack to the middleware's of the stack # -------------------------------------------------------------------------- use Rack::Logger # Load user profiles, permissions, conditions and strategies # -------------------------------------------------------------------------- providers = J1App.auth_config['providers'] permissions = J1App.permissions # ========================================================================== # Sinatra (before) FILTER to preprocess all page requests # ========================================================================== # Prepare root (index) page for app detection # -------------------------------------------------------------------------- before '/' do log_info! "ROOT", "Prepare", 'Page access' # read existing/current cookie 'j1.user.state' to update all data # of user_state_data (hash) otherwise set initial data # ------------------------------------------------------------------------ unless env['HTTP_COOKIE'] == nil log_info! "ROOT", 'Cookie', 'Read current user state data' user_state_data = readCookie('j1.user.state') else requested_page = env['REQUEST_URI'] session_data['requested_page'] = "#{env['REQUEST_URI']}" end # Create|Initialize the J1 web session cookie # ------------------------------------------------------------------------ if warden.authenticated? user = warden.user log_info! "ROOT", 'AuthCheck', 'User detected', "#{user[:provider]}" log_info! "ROOT", 'AuthCheck', 'User detected as signed in' session_data['authenticated'] = 'true' session_data['user_name'] = user[:info]['nickname'] session_data['users_allowed'] = providers["#{user[:provider]}"]['users'] session_data['user_id'] = user[:uid] session_data['provider'] = user[:provider] session_data['provider_membership'] = 'member' session_data['provider_url'] = providers["#{user[:provider]}"]['provider_url'] session_data['provider_permissions'] = providers["#{user[:provider]}"]['permissions'] session_data['payment_status'] = user[:info][:payment_status] else log_info! "ROOT", 'AuthCheck', 'User detected', 'signed out' session_data['authenticated'] = 'false' session_data['users_allowed'] = 'all' session_data['user_name'] = 'visitor' session_data['user_id'] = 'unknown' session_data['payment_status'] = 'unknown' session_data['provider'] = 'j1' session_data['provider_membership'] = 'guest' session_data['provider_url'] = 'https://jekyll.one' session_data['provider_permissions'] = 'public' end end # General content (type) detection (auth pre-flight) # -------------------------------------------------------------------------- before '/(apps|pages|posts)/*' do log_info! 'AuthManager', 'PreFlight', 'Initial checks initiated' if env['HTTP_COOKIE'].include? 'j1.user.state' session_encoded = request.cookies['j1.user.state'] session_decoded = Base64.decode64(session_encoded) # See: https://stackoverflow.com/questions/86653/how-can-i-pretty-format-my-json-output-in-ruby-on-rails session_pretty = JSON.pretty_generate(session_decoded) user_state_data = JSON.parse(session_decoded) log_info! 'PreFlight', 'Cookie', 'Read user state data' #, "#{session_decoded}" # ,"#{session_pretty}" else requested_page = env['REQUEST_URI'] session_data['requested_page'] = "#{env['REQUEST_URI']}" end # Create|Initialize the J1 web session cookie # ------------------------------------------------------------------------ log_info! 'PreFlight', 'AuthCheck', 'Check authentication state' if warden.authenticated? user = warden.user session_data['authenticated'] = 'true' session_data['user_name'] = user[:info]['nickname'] session_data['user_id'] = user[:uid] session_data['provider'] = user[:provider] session_data['provider_url'] = providers["#{user[:provider]}"]['provider_url'] session_data['users_allowed'] = providers["#{user[:provider]}"]['users']# session_data['provider_permissions'] = providers["#{user[:provider]}"]['permissions'] session_data['provider_membership'] = 'member' session_data['payment_status'] = user[:info][:payment_status] session_data['writer'] = 'middleware' user_state_data = merge( user_state_data, session_data ) log_info! 'PreFlight', 'AuthCheck', 'User authenticated', "#{user[:info]['nickname']}" end # User state|content detection for implicit authentication # ------------------------------------------------------------------------ log_info! 'PreFlight', 'CheckConfig', 'Authentication check', 'disabled' if authentication_enabled? == false log_info! 'PreFlight', 'AuthCheck', 'Pass for all pages' if authentication_enabled? == false pass if authentication_enabled? == false log_info! 'PreFlight', 'CheckConfig', 'Authentication check', 'enabled' log_info! 'PreFlight', 'DetectContent', 'Public content detected' if public_content? log_info! 'PreFlight', 'DetectContent', 'Pass all public content' if public_content? pass if public_content? log_info! 'PreFlight', 'DetectCookieConsent', 'Cookie Consent', "#{user_state_data['cookies_accepted']}" log_info! 'PreFlight', 'DetectContent', 'Check content type' requested_page = env['REQUEST_URI'] requested_page.scan(/(protected|private)/) do |match| category = match[0] log_info! 'PreFlight', 'DetectContent', 'Content type', "#{category}" log_info! 'PreFlight', 'AuthCheck', 'Check authorisation status' if warden.authenticated? user_name = user[:info]['nickname'] log_info! 'PreFlight', 'AuthCheck', 'User detected', "#{user_name}" current_provider = warden.user[:provider] strategy = providers["#{current_provider}"]['strategy'] provider_strategy = :"#{strategy}" user_state_data['user_name'] = user_name user_state_data['provider_url'] = providers["#{current_provider}"]['provider_url'] user_state_data['users_allowed'] = providers["#{current_provider}"]['users'] user_state_data['provider_permissions'] = providers["#{user[:provider]}"]['permissions'] user_state_data['requested_page'] = requested_page log_info! 'PreFlight', 'ContentCheck', 'Check permissions' if permissions[:"#{category}"].include? current_provider log_info! 'PreFlight', 'ContentCheck', 'Provider detected', "#{current_provider}" log_info! 'PreFlight', 'ContentCheck', 'Category detected', "#{category}" log_info! 'PreFlight', 'ContentCheck', 'Category support', 'enabled' # Check permissions # #log_info! 'Authorisation', 'ConditionCheck', 'Check permissions for provider', "#{current_provider}" #conditions = J1App.conditions current_provider # if conditions["#{category}"] # log_info! 'Authorisation', 'ConditionCheck', 'Conditions detected', "#{category}" # conditions["#{category}"].each do |k, v| # case k # when 'enabled' # log_info! 'Authorisation', 'ConditionCheck', "#{k}", "#{v}" # when 'users' # log_info! 'Authorisation', 'ConditionCheck', 'users' # v.each do |k, v| # log_info! 'Authorisation', 'ConditionCheck', "users - #{k}", "#{v}" # end # when 'payment' # log_info! 'Authorisation', 'ConditionCheck', 'payment' # v.each do |k, v| # case k # when 'tiers' # log_info! 'Authorisation', 'ConditionCheck', "payment - #{k}", "#{v}" # when 'tier' # v.each do |k, v| # log_info! 'Authorisation', 'ConditionCheck', 'payment - tiers - tier : ' "#{k}", "#{v}" # end # end # end # end # end # end else provider = permissions[:"#{category}"][0] log_info! 'PreFlight', 'ContentCheck', 'Provider detected', "#{current_provider}" log_info! 'PreFlight', 'ContentCheck', 'Category detected', "#{category}" log_info! 'PreFlight', 'ContentCheck', 'Category supported', 'NO' log_info! 'PreFlight', 'AuthCheck', 'Authorisation failed for user', "#{user_name}" log_info! 'PreFlight', 'SignOut', 'Sign out user', "#{user_name}" warden.logout session.clear session_json = user_state_data.to_json log_info! 'PreFlight', 'Cookie', 'Write web session data', "#{session_json}" session_encoded = Base64.encode64(session_json) response.set_cookie( 'j1.user.state', domain: false, value: session_encoded.to_s, path: '/' ) log_info! 'PreFlight', 'Redirect', 'Call API request', 'PageValidate' redirect "/page_validation?page=#{requested_page}" end time = Time.now.ctime.to_s log_info! 'PreFlight', 'AuthCheck', 'Pass to requested page', "#{requested_page}" log_info! 'PreFlight', 'AuthCheck', 'Set X-Response-Headers' # See: https://stackoverflow.com/questions/10438276/how-to-disable-static-file-caching-in-rails-3-thin-on-windows # response.headers["Cache-Control"] = 'no-cache, no-store, max-age=0, must-revalidate' # response.headers["Pragma"] = 'no-cache' # response.headers["Expires"] = 'Fri, 01 Jan 1990 00:00:00 GMT' # response.headers['X-J1-AuthManager'] = "page-validated;category=#{category};called=" + time pass else log_info! 'PreFlight', 'AuthCheck', 'User detected', 'signed out' default_provider = permissions[:"#{category}"][0] log_info! 'PreFlight', 'AuthCheck', 'Set default provider', "#{default_provider}" strategy = providers["#{default_provider}"]['strategy'] provider_strategy = :"#{strategy}" log_info! 'PreFlight', 'AuthCheck', 'Start processing provider', "#{default_provider}" log_info! 'PreFlight', 'AuthCheck', 'Authentication strategy', "#{provider_strategy}" case provider_strategy when :org warden.authenticate! github_organization_authenticate! ENV['GITHUB_ORG_NAME'] logger.info "Hi There, #{user_state_data[:user_name]}! You have access to the #{params['id']} organization" when :team warden.authenticate! github_team_authenticate! ENV['GITHUB_TEAM_ID'] logger.info "Hi There, #{user_state_data[:user_name]}! You have access to the #{params['id']} team" when :teams warden.authenticate! github_teams_authenticate! ENV['GITHUB_TEAM_IDS'].split(',') logger.info "Hi There, #{user_state_data[:user_name]}! You have access to the #{params['id']} team" when :member log_info! 'PreFlight', 'AuthCheck', 'Process authentication strategy' if env['HTTP_COOKIE'].include? 'j1.user.state' session_encoded = request.cookies['j1.user.state'] session_decoded = Base64.decode64(session_encoded) log_info! 'PreFlight', 'Cookie', 'Read user state data' # "#{session_decoded}" user_state_data = JSON.parse(session_decoded) end # Update cookie data # ---------------------------------------------------------------------- user_state_data['provider_url'] = providers["#{default_provider}"]['provider_url'] user_state_data['users_allowed'] = providers["#{default_provider}"]['users'] user_state_data['provider_permissions'] = providers["#{default_provider}"]['permissions'] user_state_data['requested_page'] = env['REQUEST_URI'] user_state_data['writer'] = 'middleware' # write updated J1 session cookie # session_json = user_state_data.to_json log_info! 'PreFlight', 'Cookie', 'Write web session data', "#{session_json}" writeCookie('j1.user.state', session_json) log_info! 'PreFlight', 'Redirect', 'Call API request', 'PageValidate' allowed_users = providers["#{default_provider}"]['users'].join(',') requested_page = env['REQUEST_URI'] # redirect "/page_validation?provider=#{default_provider}&category=#{category}&page=#{requested_page}&allowed_users=#{allowed_users}" redirect "/page_validation?page=#{requested_page}" else raise J1App::ConfigError end end end end # ========================================================================== # API ENDPOINTS (Sinatra HANDLERS) # ========================================================================== # ENDPOINT authentication (called from WEB by auth client) # -------------------------------------------------------------------------- get '/authentication' do request = params.fetch('request') provider = params.fetch('provider') log_info! 'API', 'Authentication', 'Authentication request received' # SignIn # ------------------------------------------------------------------------ if request === 'signin' log_info! 'Authentication', 'SignIn', 'Called for provider', "#{provider}" # collect (additional) GET parameter|s # ---------------------------------------------------------------------- allowed_users = params.fetch('allowed_users') if warden.authenticated? log_info! 'Authentication', 'SignIn', 'User already signed in', "#{warden.user[:info]['nickname']}" requested_page = user_state_data['requested_page'] log_info! 'Authentication', 'SignIn', 'Pass user for requested page', "#{requested_page}" redirect "#{requested_page}" else log_info! 'Authentication', 'SignIn', 'Initiate OmniAuth authentication' # Make (really) sure that old session is cleared before login # -------------------------------------------------------------------- warden.logout session.clear warden.authenticate! :"omni_#{provider}" end # SignOut # ------------------------------------------------------------------------ elsif request === 'signout' # collect (additional) GET parameter|s provider_signout = params.fetch('provider_signout') log_info! 'Authentication', 'SignOut', 'Called for provider', #{provider}" if warden.authenticated? user = warden.user[:info]['nickname'] provider = warden.user[:provider] provider_url = user_state_data['provider_url'] log_info! 'Authentication', 'SignOut', 'Sign out user', "#{user}" warden.logout session.clear # Read current J1 web session cookie # -------------------------------------------------------------------- if env['HTTP_COOKIE'].include? 'j1.user.state' session_encoded = env['rack.request.cookie_hash']['j1.user.state'] session_decoded = Base64.decode64(session_encoded) log_info! 'Authentication', 'Cookie', 'Read user state data' # #{session_decoded}" user_state_data = JSON.parse(session_decoded) else user_state_data['requested_page'] = env['REQUEST_URI'] end if provider_signout === 'true' log_info! 'Authentication', 'SignOut', 'Sign out user', "#{user}" log_info! 'Authentication', 'SignOut', 'Sign out from', "#{provider}" log_info! 'Authentication', 'Redirect', 'Pass to provider', "#{provider_url}" redirect "#{provider_url}" else log_info! 'Authentication', 'SignOut', 'Sign out user', "#{user}" log_info! 'Authentication', 'SignOut', 'Sign out from', "session" # If signed out, redirect ONLY for PUBLIC pages # ------------------------------------------------------------------ if redirect_whitelisted?user_state_data['requested_page'] log_info! 'Authentication', 'Redirect', 'Pass to page', "#{user_state_data['requested_page']}" redirect user_state_data['requested_page'] else log_info! 'Authentication', 'Redirect', 'Redirect NOT whitelisted' log_info! 'Authentication', 'Redirect', 'Pass to page', "/" redirect '/' end end else # THIS condition should NEVER REACHED because NO logout dialog # (modal) is provided by the auth client if a user isn't signed in. # Kept this alternative for cases something went wrong. # -------------------------------------------------------------------- log_info! 'Authentication', 'API', 'DEAD PATH: Called for sign out', 'NOT signed in' # Read current J1 session cookie # -------------------------------------------------------------------- if env['HTTP_COOKIE'].include? 'j1.user.state' session_encoded = env['rack.request.cookie_hash']['j1.user.state'] session_decoded = Base64.decode64(session_encoded) user_state_data = JSON.parse(session_decoded) log_info! 'Authentication', 'Cookie', 'DEAD PATH. Read user state data' # #{session_decoded}" else user_state_data['requested_page'] = env['REQUEST_URI'] end log_info! 'Post Authentication', 'Redirect', 'DEAD PATH: Pass to requested page', "#{user_state_data['requested_page']}" redirect user_state_data['requested_page'] end else raise J1App::ConfigError end end # END: get '/authentication' # -------------------------------------------------------------------------- # ENDPOINT post_authentication (called after a user is back from OAuth Provider) # -------------------------------------------------------------------------- get '/post_authentication' do reward = { :id => 'unknown', :name => 'unknown', :link => '#' } campaign = { :id => 'unknown', :link => '#' } log_info! 'API', 'Post Authentication', 'Identification request received' log_info! 'Post Authentication', 'Cookie', 'Read user state data' session_encoded = request.cookies['j1.user.state'] session_decoded = Base64.decode64(session_encoded) user_state_data = JSON.parse(session_decoded) log_info! 'Post Authentication', 'Cookie', 'Requested page read', "#{user_state_data['requested_page']}" user = warden.user user_json = user.to_json if user[:provider] === 'disqus' user[:info][:urls][:site] = "https://disqus.com" user[:info][:urls][:home] = user[:info]['urls']['profileUrl'] user[:info][:urls][:blog] = "https://disqus.com/by/juergen_adams/" user[:info][:urls][:member] = user[:info]['urls']['profileUrl'] end if user[:provider] === 'github' user[:info][:urls][:site] = "https://github.com" user[:info][:urls][:home] = user[:info]['urls']['GitHub'] user[:info][:urls][:blog] = "https://github.com/jekyll-one" user[:info][:urls][:member] = user[:info]['urls']['Blog'] end if user[:provider] === 'patreon' user[:info][:urls][:site] = "https://patreon.com" user[:info][:urls][:home] = "https://patreon.com/home" user[:info][:urls][:blog] = "https://patreon.com/jekyll_one" unless user[:info]['payment_info'].empty? reward_url = user[:info]['payment_info']['relationships']['reward']['links']['related'] reward_json = RestClient.get "#{reward_url}", {:content_type => :json, :accept => :json} reward_data = JSON.parse(reward_json) user[:info][:urls][:member] = "https://patreon.com" + reward_data['data']['attributes']['url'] user[:info][:payment_status] = user[:info]['payment_info']['attributes']['declined_since'].nil? ? 'true' : 'false' else reward_url = "" reward_json = "" reward_data = "" user[:info][:payment_status] = 'false' end unless reward_data.empty? reward[:id] = reward_data['data']['id'] reward[:name] = reward_data['data']['attributes']['title'] reward[:link] = "https://patreon.com" + reward_data['data']['attributes']['url'] campaign[:id] = reward_data['data']['relationships']['campaign']['data']['id'] campaign[:link] = reward_data['data']['relationships']['campaign']['links']['related'] else reward[:id] = "" reward[:name] = "no tiers" reward[:link] = "" campaign[:id] = "" campaign[:link] = "" end end user[:extra][:reward] = reward user[:extra][:campaign] = campaign if user.nil? # Collection of session data failed (e.g cookie > 4K) # log_info! 'Post Authentication', 'Identification', 'Internal error', 'User identification failed' warden.logout session.clear log_info! 'Post Authentication', 'Redirect', 'Pass to error page (access_denied)' description_title = "Access Denied" redirect "/access_denied?provider=unknown&user=unknown&category=unknown&title=#{description_title}" else log_info! 'Post Authentication', 'Identification', 'User identified successfully' user_state_data['user_name'] = user[:info]['nickname'] user_state_data['user_id'] = user[:uid] user_state_data['provider'] = user[:provider] user_state_data['provider_membership'] = 'member' user_state_data['provider_permissions'] = providers["#{user[:provider]}"]['permissions'] user_state_data['users_allowed'] = providers["#{user[:provider]}"]['users'] user_state_data['authenticated'] = 'true' user_state_data['payment_status'] = user[:info][:payment_status] user_state_data['writer'] = 'middleware' current_user = user[:info]['nickname'] = user[:info]['nickname'] current_provider = user[:provider] user_state_data['requested_page'].scan(/(protected|private)/) do |match| # Set category from requested page # category = match[0] log_info! 'Post Authentication', 'Identification', 'Process content type', "#{category}" # Check if user is allowed to access protected content in GENERAL # log_info! 'Post Authentication', 'Identification', 'Check for allowed users' unless user_state_data['users_allowed'].include? 'all' unless user_state_data['users_allowed'].include? "#{current_user}" log_info! 'Post Authentication', 'Identification', 'User not allowed', "#{current_user}" log_info! 'Post Authentication', 'Identification', 'Allowed users', "#{user_state_data['users_allowed']}" log_info! 'Post Authentication', 'Identification', 'Logout user from current session', "#{current_user}" warden.logout session.clear log_info! 'Post Authentication', 'Redirect', 'Pass to error page (access_denied)' description_title = "Access Denied" redirect "/access_denied?provider=#{current_provider}&user=#{current_user}&category=#{category}&title=#{description_title}" end end log_info! 'Post Authentication', 'Identification', 'Allowed users', "#{user_state_data['users_allowed']}" # Check conditions to access protected content (if any) # log_info! 'Post Authentication', 'Identification', 'Check for conditions', "#{current_provider}" check_conditions = providers["#{user[:provider]}"]['conditions'][category]['enabled'] if check_conditions if providers["#{user[:provider]}"]['conditions'][category]['users']['whitelist'].nil? category_whitelist = 'all' else category_whitelist = providers["#{user[:provider]}"]['conditions'][category]['users']['whitelist'] end # Check if user is BLACKLISTED # blacklist = providers["#{user[:provider]}"]['conditions'][category]['users']['blacklist'] if blacklist.include? "#{current_user}" log_info! 'Post Authentication', 'Identification', 'Check blacklisting' log_info! 'Post Authentication', 'Identification', 'User blacklisted', "#{current_user}" user[:info][:blacklisted] = 'true' log_info! 'Post Authentication', 'Identification', 'Logout user from current session', "#{current_user}" warden.logout session.clear log_info! 'Post Authentication', 'Redirect', 'Pass to error page (access_denied)' description_title = "Access Denied" redirect "/access_denied?provider=#{current_provider}&user=#{current_user}&category=#{category}&title=#{description_title}" end log_info! 'Post Authentication', 'Identification', 'Check whitelisting' if category_whitelisted? category_whitelist, current_user user[:info][:whitelisted] = 'true' reward[:name] = 'whitelisted' log_info! 'Post Authentication', 'Identification', 'User whitelisted', "#{current_user}" log_info! 'Post Authentication', 'Identification', 'Reward set to', 'Whitelisted' else log_info! 'Post Authentication', 'Identification', 'No whitelisting found', "#{current_user}" end log_info! 'Post Authentication', 'Identification', 'Check conditions' unless category_whitelisted? category_whitelist, current_user log_info! 'Post Authentication', 'Identification', 'Check rewards' payment_tiers = providers["#{user[:provider]}"]['conditions'][category]['payment']['activated'] log_info! 'Post Authentication', 'Identification', 'Check rewards', "#{current_user}" if payment_activated? payment_tiers log_info! 'Post Authentication', 'Identification', 'Reward found', "#{reward[:name]}" # Check if any payment exists for that user # log_info! 'Post Authentication', 'Identification', 'Check payment status' if user[:info]['payment_info'].empty? log_info! 'Post Authentication', 'Identification', 'Payment status: NOT AVAILABLE', "#{current_user}" log_info! 'Post Authentication', 'Identification', 'Logout user from current session', "#{current_user}" warden.logout session.clear log_info! 'Post Authentication', 'Redirect', 'Pass to error page (access_denied)' description_title = "Access Denied" redirect "/access_denied?provider=#{current_provider}&user=#{current_user}&category=#{category}&title=#{description_title}" end # Check for VALID payments (scope: pledge-to-me) # payment_status = user[:info]['payment_info']['attributes']['declined_since'] unless payment_valid? payment_status log_info! 'Post Authentication', 'Identification', 'Payment status INVALID', "#{current_user}" log_info! 'Post Authentication', 'Identification', 'Logout user from current session', "#{current_user}" warden.logout session.clear log_info! 'Post Authentication', 'Redirect', 'Pass to error page (access_denied)' description_title = "Access Denied" redirect "/access_denied?provider=#{current_provider}&user=#{current_user}&category=#{category}&title=#{description_title}" else log_info! 'Post Authentication', 'Identification', 'Payment status VALID', "#{current_user}" end end end # end category_whitelisted else category_condition_state = providers["#{user[:provider]}"]['conditions'][category]['enabled'] log_info! 'Post Authentication', 'Identification', 'Category check failed for', "#{current_provider}" log_info! 'Post Authentication', 'Identification', "Category checked", "#{category}" log_info! 'Post Authentication', 'Identification', "Category support", "#{category_condition_state}" warden.logout session.clear log_info! 'Post Authentication', 'Redirect', 'Pass to error page (access_denied)' description_title = "Access Denied" redirect "/access_denied?provider=#{current_provider}&user=#{current_user}&category=#{category}&title=#{description_title}" end # end check conditions end # end protected content end # end user.nil? log_info! 'Post Authentication', 'Identification', 'Provider', "#{user[:provider]}" log_info! 'Post Authentication', 'Identification', 'User', "#{user[:info]['nickname']}" # jadams, 2019-07-22: Check if it's needed to modify the respose header # # time = Time.now.ctime.to_s # log_info! 'API', 'Auth Request', 'Set Last-Modified', "#{time}" # response.headers['Last-Modified'] = time # response.headers['Cache-Control'] = 'private,max-age=0,must-revalidate,no-store' # response.headers['X-J1-AuthManager'] = "page-validated;category=#{category};called=" + time log_info! 'Post Authentication', 'Redirect', 'Pass to requested page', "#{user_state_data['requested_page']}" redirect user_state_data['requested_page'] end # END: get /post_authentication # -------------------------------------------------------------------------- # ENDPOINT status (called from WEB to get current state of an user) # -------------------------------------------------------------------------- get '/status' do requested_page = params.fetch('page') category = 'public' requested_page.scan(/(public|protected|private)/) do |match| category = match[0] end log_info! 'API', 'Status Request', 'Info request received' log_info! 'API', 'Status Request', 'Page requested', "#{requested_page}" log_info! 'API', 'Status Request', 'Content type', "#{category}" # if request.warden.user.respond_to?(:info) # if warden.authenticated? user_name = warden.user[:info]['nickname'] user_id = warden.user[:uid] users_allowed = providers[warden.user[:provider]]['users'] provider = warden.user[:provider] provider_permissions = user_state_data['provider_permissions'] provider_site_url = warden.user[:info][:urls][:site] provider_home_url = warden.user[:info][:urls][:home] provider_blog_url = warden.user[:info][:urls][:blog] provider_member_url = warden.user[:info][:urls][:member] if provider == 'patreon' provider_membership = warden.user[:extra][:reward][:name] provider_member_url = warden.user[:extra][:reward][:link] else provider_membership = 'member' provider_member_url = '#' end log_info! 'API', 'Status Request', 'User detected', "#{user_name}" log_info! 'API', 'Status Request', 'User detected as signed in' else user_name = 'unknown' log_info! 'API', 'Status Request', 'User detected', 'signed out' end # time = Time.now.ctime.to_s time = Time.now.strftime("%Y-%m-%d %H:%M:%S") # if request.warden.authenticated? # if user_name != 'unknown' log_info! 'API', 'Status Request', 'Send data', 'SIGNED_IN' content_type 'application/json' { user_name: user_name, user_id: user_id, users_allowed: users_allowed, provider: provider, provider_membership: provider_membership, provider_permissions: provider_permissions, provider_site_url: provider_site_url, provider_home_url: provider_home_url, provider_blog_url: provider_blog_url, provider_member_url: provider_member_url, page_permission: category, requested_page: requested_page, authenticated: 'true', processed: time }.to_json else log_info! 'API', 'Status Request', 'Send data', 'SIGNED_OUT' content_type 'application/json' { user_name: 'visitor', user_id: 'unknown', users_allowed: 'all', provider: 'j1', provider_membership: 'guest', provider_permissions: 'public', provider_site_url: '#', provider_home_url: '#', provider_blog_url: '#', provider_member_url: '#', page_permission: category, requested_page: requested_page, authenticated: 'false', processed: time }.to_json end end # END: get /status # -------------------------------------------------------------------------- # ENDPOINT access_denied (exception, called from the app|auth manager) # -------------------------------------------------------------------------- get '/access_denied' do provider = params.fetch('provider') category = params.fetch('category') user = params.fetch('user') description_title = params.fetch('title') log_info! 'API', 'ExceptionHandler', 'Request received' log_info! 'ExceptionHandler', 'ERROR', 'Access Denied' session_encoded = request.cookies['j1.user.state'] session_decoded = Base64.decode64(session_encoded) user_state_data = JSON.parse(session_decoded) log_info! 'ExceptionHandler', 'Redirect', 'Pass to error page', "Access Denied" # Capitalize first char provider = provider.sub(/^./, &:upcase) route = '/' @route = route @provider = provider @modal = "centralModalInfo" @info_type = "danger" @modal_icon = "account-off" @modal_ok_text = "Ok, understood" @modal_title = "Authentication Manager" @modal_description = "