diff --git a/app/controllers/v1/omniauth_callbacks_controller.rb b/app/controllers/v1/omniauth_callbacks_controller.rb new file mode 100644 index 0000000000000000000000000000000000000000..4bffba452e309a0916576aae140a5d7e07f0d6fb --- /dev/null +++ b/app/controllers/v1/omniauth_callbacks_controller.rb @@ -0,0 +1,254 @@ + class V1::OmniauthCallbacksController < DeviseTokenAuth::ApplicationController + + attr_reader :auth_params + skip_before_action :set_user_by_token, raise: false + skip_after_action :update_auth_header + + # intermediary route for successful omniauth authentication. omniauth does + # not support multiple models, so we must resort to this terrible hack. + def redirect_callbacks + + # derive target redirect route from 'resource_class' param, which was set + # before authentication. + devise_mapping = [request.env['omniauth.params']['namespace_name'], + request.env['omniauth.params']['resource_class'].underscore.gsub('/', '_')].compact.join('_') + redirect_route = "#{request.protocol}#{request.host_with_port}/#{Devise.mappings[devise_mapping.to_sym].fullpath}/#{params[:provider]}/callback" + + # preserve omniauth info for success route. ignore 'extra' in twitter + # auth response to avoid CookieOverflow. + session['dta.omniauth.auth'] = request.env['omniauth.auth'].except('extra') + session['dta.omniauth.params'] = request.env['omniauth.params'] + + redirect_to redirect_route + end + + def omniauth_success + get_resource_from_auth_hash + create_token_info + set_token_on_resource + create_auth_params + + if resource_class.devise_modules.include?(:confirmable) + # don't send confirmation email!!! + @resource.skip_confirmation! + end + + sign_in(:user, @resource, store: false, bypass: false) + + @resource.save! + + yield @resource if block_given? + + render_data_or_redirect('deliverCredentials', @auth_params.as_json, @resource.as_json) + end + + def omniauth_failure + @error = params[:message] + render_data_or_redirect('authFailure', {error: @error}) + end + + protected + + # this will be determined differently depending on the action that calls + # it. redirect_callbacks is called upon returning from successful omniauth + # authentication, and the target params live in an omniauth-specific + # request.env variable. this variable is then persisted thru the redirect + # using our own dta.omniauth.params session var. the omniauth_success + # method will access that session var and then destroy it immediately + # after use. In the failure case, finally, the omniauth params + # are added as query params in our monkey patch to OmniAuth in engine.rb + def omniauth_params + if !defined?(@_omniauth_params) + if request.env['omniauth.params'] && request.env['omniauth.params'].any? + @_omniauth_params = request.env['omniauth.params'] + elsif session['dta.omniauth.params'] && session['dta.omniauth.params'].any? + @_omniauth_params ||= session.delete('dta.omniauth.params') + @_omniauth_params + elsif params['omniauth_window_type'] + @_omniauth_params = params.slice('omniauth_window_type', 'auth_origin_url', 'resource_class', 'origin') + else + @_omniauth_params = {} + end + end + @_omniauth_params + + end + + # break out provider attribute assignment for easy method extension + def assign_provider_attrs(user, auth_hash) + user.assign_attributes({ + nickname: auth_hash['info']['nickname'], + name: auth_hash['info']['name'], + avatar: auth_hash['info']['image'], + email: auth_hash['info']['email'] + }) + end + + # derive allowed params from the standard devise parameter sanitizer + def whitelisted_params + whitelist = params_for_resource(:sign_up) + + whitelist.inject({}){|coll, key| + param = omniauth_params[key.to_s] + if param + coll[key] = param + end + coll + } + end + + def resource_class(mapping = nil) + if omniauth_params['resource_class'] + omniauth_params['resource_class'].constantize + elsif params['resource_class'] + params['resource_class'].constantize + else + raise "No resource_class found" + end + end + + def resource_name + resource_class + end + + def omniauth_window_type + omniauth_params['omniauth_window_type'] + end + + def auth_origin_url + omniauth_params['auth_origin_url'] || omniauth_params['origin'] + end + + # in the success case, omniauth_window_type is in the omniauth_params. + # in the failure case, it is in a query param. See monkey patch above + def omniauth_window_type + omniauth_params.nil? ? params['omniauth_window_type'] : omniauth_params['omniauth_window_type'] + end + + # this sesison value is set by the redirect_callbacks method. its purpose + # is to persist the omniauth auth hash value thru a redirect. the value + # must be destroyed immediatly after it is accessed by omniauth_success + def auth_hash + @_auth_hash ||= session.delete('dta.omniauth.auth') + @_auth_hash + end + + # ensure that this controller responds to :devise_controller? conditionals. + # this is used primarily for access to the parameter sanitizers. + def assert_is_devise_resource! + true + end + + # necessary for access to devise_parameter_sanitizers + def devise_mapping + if omniauth_params + Devise.mappings[[omniauth_params['namespace_name'], + omniauth_params['resource_class'].underscore].compact.join('_').to_sym] + else + request.env['devise.mapping'] + end + end + + def set_random_password + # set crazy password for new oauth users. this is only used to prevent + # access via email sign-in. + p = SecureRandom.urlsafe_base64(nil, false) + @resource.password = p + @resource.password_confirmation = p + end + + def create_token_info + # create token info + @client_id = SecureRandom.urlsafe_base64(nil, false) + @token = SecureRandom.urlsafe_base64(nil, false) + @expiry = (Time.now + DeviseTokenAuth.token_lifespan).to_i + @config = omniauth_params['config_name'] + end + + def create_auth_params + @auth_params = { + auth_token: @token, + client_id: @client_id, + uid: @resource.uid, + expiry: @expiry, + config: @config + } + @auth_params.merge!(oauth_registration: true) if @oauth_registration + @auth_params + end + + def set_token_on_resource + @resource.tokens[@client_id] = { + token: BCrypt::Password.create(@token), + expiry: @expiry + } + end + + def render_data(message, data) + @data = data.merge({ + message: message + }) + render :layout => nil, :template => "devise_token_auth/omniauth_external_window" + end + + def render_data_or_redirect(message, data, user_data = {}) + + # We handle inAppBrowser and newWindow the same, but it is nice + # to support values in case people need custom implementations for each case + # (For example, nbrustein does not allow new users to be created if logging in with + # an inAppBrowser) + # + # See app/views/devise_token_auth/omniauth_external_window.html.erb to understand + # why we can handle these both the same. The view is setup to handle both cases + # at the same time. + if ['inAppBrowser', 'newWindow'].include?(omniauth_window_type) + render_data(message, user_data.merge(data)) + + elsif auth_origin_url # default to same-window implementation, which forwards back to auth_origin_url + + # build and redirect to destination url + redirect_to DeviseTokenAuth::Url.generate(auth_origin_url, data.merge(blank: true)) + else + + # there SHOULD always be an auth_origin_url, but if someone does something silly + # like coming straight to this url or refreshing the page at the wrong time, there may not be one. + # In that case, just render in plain text the error message if there is one or otherwise + # a generic message. + fallback_render data[:error] || 'An error occurred' + end + end + + def fallback_render(text) + render inline: %Q| + + <html> + <head></head> + <body> + #{text} + </body> + </html>| + end + + def get_resource_from_auth_hash + # find or create user by provider and provider uid + @resource = resource_class.where({ + uid: auth_hash['uid'], + provider: auth_hash['provider'] + }).first_or_initialize + + if @resource.new_record? + @oauth_registration = true + set_random_password + end + + # sync user info with provider, update/generate auth token + assign_provider_attrs(@resource, auth_hash) + + # assign any additional (whitelisted) attributes + extra_params = whitelisted_params + @resource.assign_attributes(extra_params) if extra_params + + @resource + end + + end diff --git a/app/models/collection.rb b/app/models/collection.rb index 54212efb4ecf76278f3e6d93dd9c8bb1e090392b..f3b7f2745949ffd7685e585600c73272172b3165 100644 --- a/app/models/collection.rb +++ b/app/models/collection.rb @@ -65,7 +65,8 @@ class Collection < ApplicationRecord created_at: created_at, likes: likes_count, downloads: downloads_count, - review_average: review_ratings_average + review_average: review_ratings_average, + empty: collection_items.empty? } end diff --git a/app/services/search_service/collection.rb b/app/services/search_service/collection.rb index c6f805616be02c01391a98b623f62e0aba342bfc..5bd29469709d51634c3d8489b27f653afed1edd1 100644 --- a/app/services/search_service/collection.rb +++ b/app/services/search_service/collection.rb @@ -26,6 +26,7 @@ module SearchService filter << { in: { tags: @search.tags } } unless @search.tags.blank? filter << { in: { subjects: @search.subjects } } unless @search.subjects.blank? filter << { in: { educational_stages: @search.educational_stages } } unless @search.educational_stages.blank? + filter << { terms: { empty: show_empty } } filter end @@ -40,5 +41,11 @@ module SearchService when 'review_average' then { review_average: { order: :desc } } end end + + def show_empty + return [true,false] if !@user.nil? && @user.is_admin? + [false] + end + end end diff --git a/app/services/search_service/learning_object.rb b/app/services/search_service/learning_object.rb index ebd525e6335b141e4f75c47c9513937f5c575da3..bff63f89f5d272e4c242eab493cea34bac8e619f 100644 --- a/app/services/search_service/learning_object.rb +++ b/app/services/search_service/learning_object.rb @@ -27,7 +27,7 @@ module SearchService filter << { in: { subjects: @search.subjects } } unless @search.subjects.blank? filter << { in: { educational_stages: @search.educational_stages } } unless @search.educational_stages.blank? filter << { in: { object_type: @search.object_types.map(&:to_i) } } unless @search.object_types.blank? - filter << { term: { state: validate_object } } + filter << { terms: { state: validate_object } } filter end @@ -44,7 +44,7 @@ module SearchService end def validate_object - return ::LearningObject.states[:published] unless !@user.nil? && @user.is_admin? + return [::LearningObject.states[:published]] unless !@user.nil? && @user.is_admin? ::LearningObject.states.values end end diff --git a/app/services/search_service/model.rb b/app/services/search_service/model.rb index 632825a4fb309a5f1fc45aaafc861284777ecbe1..eda8a5d6e734389b82930cd87790fab5ba1354f8 100644 --- a/app/services/search_service/model.rb +++ b/app/services/search_service/model.rb @@ -37,8 +37,13 @@ module SearchService end def mount_query - # TODO: match_all don't work with filter, fix when possible - return { "match_all": {} } if @search.query == "*" + match_all_query = { + filtered:{ + query: { match_all: {} }, + filter: mount_filter + } + } + return match_all_query if @search.query == "*" { function_score: { diff --git a/app/workers/attachment_cache_worker.rb b/app/workers/attachment_cache_worker.rb index 248337bdde8d43bb9b6d5cafd512240fd78b2e73..5e8bb82d87587fa994e28a5faada9e5a57603d9e 100644 --- a/app/workers/attachment_cache_worker.rb +++ b/app/workers/attachment_cache_worker.rb @@ -18,6 +18,7 @@ class AttachmentCacheWorker FileUtils.mkdir_p(directory_root) FileUtils.mv(file.path, file_root) FileUtils.chmod 0644, file_root.to_s, verbose: true + FileUtils.chown_R 'portalmec', 'www-data', directory_root.to_s, verbose: true @attachment.update(cache_link: cache_link) ensure file.close if !file.nil? && File.exist?(file.path) diff --git a/config/initializers/devise_token_auth.rb b/config/initializers/devise_token_auth.rb index ba76d510ae984c0236119db596223b166f04a930..13b395649a738df1c0c4b0eb19fe5a41a6622f2a 100644 --- a/config/initializers/devise_token_auth.rb +++ b/config/initializers/devise_token_auth.rb @@ -22,7 +22,7 @@ DeviseTokenAuth.setup do |config| # This route will be the prefix for all oauth2 redirect callbacks. For # example, using the default '/omniauth', the github oauth2 provider will # redirect successful authentications to '/omniauth/github/callback' - # config.omniauth_prefix = "/omniauth" + config.omniauth_prefix = "/omniauth" # By default sending current password is not needed for the password update. # Uncomment to enforce current_password param to be checked before all diff --git a/config/initializers/omniauth.rb b/config/initializers/omniauth.rb index 8863174a07d6df1a886558eb45f381b4b5c120be..1ed6c505fd34e364209e98ed848648c6a99bb98e 100644 --- a/config/initializers/omniauth.rb +++ b/config/initializers/omniauth.rb @@ -1,5 +1,5 @@ Rails.application.config.middleware.use OmniAuth::Builder do provider :twitter, ENV['TWITTER_KEY'], ENV['TWITTER_SECRET'] - provider :facebook, ENV['FACEBOOK_KEY'], ENV['FACEBOOK_SECRET'] + provider :facebook, ENV['FACEBOOK_KEY'], ENV['FACEBOOK_SECRET'], secure_image_url: true provider :google_oauth2, ENV['GOOGLE_KEY'], ENV['GOOGLE_SECRET'] end diff --git a/config/routes.rb b/config/routes.rb index 10df93354e6cb9993ede0ede3a34635f62a5e4a1..e96849f41e0a794ed060653cca0145c9f308d5e9 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -74,7 +74,9 @@ Rails.application.routes.draw do end scope :v1 do - mount_devise_token_auth_for 'User', at: 'auth' + mount_devise_token_auth_for 'User', at: 'auth', controllers: { + omniauth_callbacks: 'v1/omniauth_callbacks' + } end namespace :v1 do diff --git a/lib/tasks/attachment_maintaining_service.rake b/lib/tasks/attachment_maintaining_service.rake index 6587edd4c8c5d01ee8f47c379fdbe34b4b06925d..13e3f6a29bccc88b0a2d3d09cb075d2edb5a5852 100644 --- a/lib/tasks/attachment_maintaining_service.rake +++ b/lib/tasks/attachment_maintaining_service.rake @@ -1,25 +1,26 @@ require 'fileutils' namespace :attachment_maintaining_service do - desc 'Removing 10 attachments' - task remove_attachments: :environment do - m = sort_directories.first(10) - m.each do |mm| - #puts (mm) - FileUtils.rm_r(mm) - end - end - def sort_directories - files_sorted_by_time = Dir[dir_path].sort_by{ |f| File.atime(f) } - files_sorted_by_time + desc 'Removing attachments using random politic' + task :remove_attachments, [:limit] => :environment do |t, args| + args.with_defaults(limit: 10.gigabyte) + attachments_dir = attachment_dir + "/*" + + while dir_size(attachment_dir) > args.limit.to_i do + # TODO now the politic is random, verify if politics like LRU or NRU improve the efficiency + FileUtils.rm_r Dir[attachments_dir].sample + end end private - def dir_path - c = Rails.root.join('public','attachments','*').to_s - c + def dir_size(dir) + `du -sb #{dir} | cut -f 1`.to_i + end + + def attachment_dir + Rails.root.join('public','attachments').to_s end end diff --git a/spec/controllers/v1/omniauth_callbacks_controller_spec.rb b/spec/controllers/v1/omniauth_callbacks_controller_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..a7bb409edcb96cebfd6491d500b11824d5a06f11 --- /dev/null +++ b/spec/controllers/v1/omniauth_callbacks_controller_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe V1::OmniauthCallbacksController, type: :controller do + +end