diff --git a/Gemfile b/Gemfile index b289639f0..72f8f4597 100644 --- a/Gemfile +++ b/Gemfile @@ -61,7 +61,7 @@ group :development, :test do gem 'pry' gem 'pry-byebug' gem 'pry-rails' - + gem 'solargraph' # vscode gem 'database_cleaner', '~> 2.0' gem 'launchy' # for capybara's save_and_open_page gem 'mimic', '~> 0.4' diff --git a/Gemfile.lock b/Gemfile.lock index 16c14e824..5ca06311b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -76,6 +76,8 @@ GEM ast (2.4.2) autoprefixer-rails (10.2.5.0) execjs (< 2.8.0) + backport (1.2.0) + benchmark (0.1.1) bootstrap (4.6.0) autoprefixer-rails (>= 9.1.0) popper_js (>= 1.14.3, < 2) @@ -114,6 +116,7 @@ GEM unf (>= 0.0.5, < 1.0.0) doorkeeper (5.5.1) railties (>= 5) + e2mmap (0.1.0) erubi (1.10.0) eventmachine (1.2.7) execjs (2.7.0) @@ -182,6 +185,7 @@ GEM io-console (0.5.9) irb (1.3.5) reline (>= 0.1.5) + jaro_winkler (1.5.4) jquery-rails (4.4.0) rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) @@ -190,6 +194,10 @@ GEM json-schema (2.8.1) addressable (>= 2.4) jwt (2.2.3) + kramdown (2.3.1) + rexml + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) launchy (2.5.0) addressable (~> 2.7) letter_opener (1.7.0) @@ -321,6 +329,8 @@ GEM mime-types (>= 1.16, < 4.0) netrc (~> 0.8) retriable (3.1.2) + reverse_markdown (2.0.0) + nokogiri rexml (3.2.5) rspec (3.10.0) rspec-core (~> 3.10.0) @@ -403,6 +413,21 @@ GEM rack (~> 2.2) rack-protection (= 2.1.0) tilt (~> 2.0) + solargraph (0.43.0) + backport (~> 1.2) + benchmark + bundler (>= 1.17.2) + diff-lcs (~> 1.4) + e2mmap + jaro_winkler (~> 1.5) + kramdown (~> 2.3) + kramdown-parser-gfm (~> 1.1) + parser (~> 3.0) + reverse_markdown (>= 1.0.5, < 3) + rubocop (>= 0.52) + thor (~> 1.0) + tilt (~> 2.0) + yard (~> 0.9, >= 0.9.24) sprockets (4.0.2) concurrent-ruby (~> 1.0) rack (> 1, < 3) @@ -434,6 +459,7 @@ GEM xml-simple (1.1.8) xpath (3.2.0) nokogiri (~> 1.8) + yard (0.9.26) zeitwerk (2.4.2) PLATFORMS @@ -494,6 +520,7 @@ DEPENDENCIES ruby-prof (~> 1.4) sassc-rails (~> 2.1) simplecov + solargraph sprockets-rails swagger-blocks (~> 3.0) uglifier (~> 4.2) diff --git a/app/controllers/api/v8/base_controller.rb b/app/controllers/api/v8/base_controller.rb index f8e633da1..bf651c36b 100644 --- a/app/controllers/api/v8/base_controller.rb +++ b/app/controllers/api/v8/base_controller.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'json' +require 'rest-client' module Api module V8 @@ -36,9 +37,16 @@ def present(hash) private def authenticate_user! return @current_user if @current_user + if doorkeeper_token @current_user ||= User.find_by(id: doorkeeper_token.resource_owner_id) raise 'Invalid token' unless @current_user + else + moocfi_user = validate_moocfi_user + # raise 'Invalid token' unless moocfi_user + + @current_user ||= User.find_by(id: moocfi_user['upstream_id']) || create_or_update_user_from_moocfi(moocfi_user) + # raise 'Invalid token' unless @current_user end @current_user ||= user_from_session || Guest.new end @@ -125,6 +133,79 @@ def check_client_minimum_version(client_name, client_version) end end end + + def bearer_token + pattern = /^Bearer / + authorization = request.authorization + authorization.gsub(pattern, '') if authorization && authorization.match(pattern) + end + + def validate_moocfi_user + base_url_for_moocfi = SiteSetting.value('base_url_for_moocfi') + + begin + res = RestClient::Request.execute(method: :get, url: "#{base_url_for_moocfi}/auth/validate", headers: { 'Authorization': request.authorization }) + moocfi_response = JSON.parse(res.body) + + moocfi_response['user'] + rescue RestClient::ExceptionWithResponse => e + raise 'Invalid MOOC.fi token' if (400..499).include? e.http_code.to_i + raise 'Internal error' if e.http_code.to_i >= 500 + end + end + + def create_or_update_user_from_moocfi(moocfi_user) + # in case we have a discrepancy, ie. MOOC.fi user and TMC user both exist, but MOOC.fi user doesn't have TMC id + user = User.find_by(email: moocfi_user['email']) + + if user + update_moocfi_user(user) + user + else + ActiveRecord::Base.transaction do + user = User.create!( + login: SecureRandom.uuid, + email: moocfi_user['email'], + password: SecureRandom.base64(12), + administrator: moocfi_user['administrator'] || false, + ) + UserFieldValue.create!(field_name: 'first_name', user_id: user.id, value: moocfi_user['first_name']) + UserFieldValue.create!(field_name: 'last_name', user_id: user.id, value: moocfi_user['last_name']) + UserFieldValue.create!(field_name: 'organizational_id', user_id: user.id, value: moocfi_user['real_student_number'] || '') + + update_moocfi_user(user) + + UserMailer.email_confirmation(user, nil, nil).deliver_now + + user + rescue StandardError, ScriptError + raise ActiveRecord::Rollback + end + end + end + + def update_moocfi_user(user) + base_url_for_moocfi = SiteSetting.value('base_url_for_moocfi') + moocfi_update_secret = SiteSetting.value('moocfi_update_secret') + + begin + res = RestClient::Request.execute( + method: :patch, + url: "#{base_url_for_moocfi}/api/user", + payload: { 'upstream_id': user['id'], 'secret': moocfi_update_secret }.to_json, + headers: { + 'Authorization': request.authorization, + 'Content-Type': 'application/json' + } + ) + moocfi_response = JSON.parse(res.body) + + raise "Error updating MOOC.fi user: #{moocfi_response.message}" unless moocfi_response['success'] + rescue RestClient::ExceptionWithResponse => e + raise 'Error updating MOOC.fi user' if (400..499).include? e.http_code.to_i + raise 'Internal error' if e.http_code.to_i >= 500 + end + end end end end diff --git a/config/site.dev.yml b/config/site.dev.yml index 75e82c79d..02c99abd7 100644 --- a/config/site.dev.yml +++ b/config/site.dev.yml @@ -19,4 +19,6 @@ valid_clients: min_version: 1.4.0 kafka_bridge_url: "test" kafka_bridge_secret: "test" -moocfi_service_id: "test" \ No newline at end of file +moocfi_service_id: "test" +moocfi_update_secret: "test" +base_url_for_moocfi: http://localhost:4000