diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8777124c0..2f359a147 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v2 - with: + with: submodules: false - name: Setup git submodules run: | @@ -29,7 +29,7 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v2 - with: + with: submodules: false - name: Setup git submodules run: | @@ -47,7 +47,7 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v2 - with: + with: submodules: false - name: Setup git submodules run: | @@ -65,7 +65,7 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v2 - with: + with: submodules: false - name: Setup git submodules run: | @@ -83,7 +83,7 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v2 - with: + with: submodules: false - name: Setup git submodules run: | @@ -101,7 +101,7 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v2 - with: + with: submodules: false - name: Setup git submodules run: | @@ -112,4 +112,4 @@ jobs: - name: Setup database & test env run: docker compose run web bin/rake db:create db:schema:load RAILS_ENV=test - name: Run spec_integration tests - run: docker compose run web bin/spec_integration \ No newline at end of file + run: docker compose run web bin/spec_integration diff --git a/app/controllers/api/v8/users_controller.rb b/app/controllers/api/v8/users_controller.rb index 88c129160..d0fc74467 100644 --- a/app/controllers/api/v8/users_controller.rb +++ b/app/controllers/api/v8/users_controller.rb @@ -56,6 +56,28 @@ class UsersController < Api::V8::BaseController end end + swagger_path '/api/v8/users/{user_id}/set_password_managed_by_courses_mooc_fi' do + operation :post do + key :description, 'Sets the boolean password_managed_by_courses_mooc_fi for the user with the given id to true.' + key :operationId, 'setPasswordManagedByCoursesMoocFi' + key :produces, ['application/json'] + key :tags, ['user'] + parameter '$ref': '#/parameters/user_id' + response 403, '$ref': '#/responses/error' + response 404, '$ref': '#/responses/error' + response 200 do + key :description, "status 'ok' and sets the boolean password_managed_by_courses_mooc_fi to true" + schema do + key :title, :status + key :required, [:status] + property :status, type: :string, example: 'Password managed by courses.mooc.fi set to true and password deleted.' + end + end + end + end + + skip_authorization_check only: %i[set_password_managed_by_courses_mooc_fi] + def show unauthorize_guest! if current_user.guest? user = current_user @@ -149,6 +171,26 @@ def update }, status: :bad_request end + def set_password_managed_by_courses_mooc_fi + only_admins! + + User.transaction do + user = User.find_by!(id: params[:id]) + user.password_managed_by_courses_mooc_fi = true + user.password_hash = nil + user.salt = nil + user.argon_hash = nil + user.courses_mooc_fi_user_id = params[:courses_mooc_fi_user_id] + raise ActiveRecord::Rollback if !user.errors.empty? || !user.save + return render json: { + status: 'Password managed by courses.mooc.fi set to true and password deleted.' + } + end + render json: { + errors: @user.errors + }, status: :bad_request + end + private def set_email user_params = params[:user] @@ -189,6 +231,10 @@ def set_password end def maybe_update_password + if @user.password_managed_by_courses_mooc_fi && @user.courses_mooc_fi_user_id.present? + return @user.update_password_via_courses_mooc_fi(@user.courses_mooc_fi_user_id, user_params[:old_password], user_params[:password]) + end + if params[:old_password].present? && params[:password].present? if !@user.has_password?(params[:old_password]) @user.errors.add(:old_password, 'incorrect') diff --git a/app/controllers/settings_controller.rb b/app/controllers/settings_controller.rb index 1915ac4c9..21e8f2801 100644 --- a/app/controllers/settings_controller.rb +++ b/app/controllers/settings_controller.rb @@ -86,6 +86,10 @@ def set_email end def maybe_update_password(user, user_params) + if user.password_managed_by_courses_mooc_fi && user.courses_mooc_fi_user_id.present? + return user.update_password_via_courses_mooc_fi(user.courses_mooc_fi_user_id, user_params[:old_password], user_params[:password]) + end + if user_params[:old_password].present? || user_params[:password].present? if !user.has_password?(user_params[:old_password]) user.errors.add(:old_password, 'incorrect') diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index d7e2246d4..be012b747 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -179,6 +179,10 @@ def set_password end def maybe_update_password(user, user_params) + if user.password_managed_by_courses_mooc_fi && user.courses_mooc_fi_user_id.present? + return user.update_password_via_courses_mooc_fi(user.courses_mooc_fi_user_id, user_params[:old_password], user_params[:password]) + end + if user_params[:old_password].present? || user_params[:password].present? if !user.has_password?(user_params[:old_password]) user.errors.add(:old_password, 'incorrect') diff --git a/app/models/user.rb b/app/models/user.rb index 721e5c041..46e45802d 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require 'rest-client' + class User < ApplicationRecord include Comparable include Gravtastic @@ -146,9 +148,72 @@ def self.authenticate(login, submitted_password) user = find_by(login: login) user ||= find_by('lower(email) = ?', login.downcase) return nil if user.nil? + user if user.password_managed_by_courses_mooc_fi && user.courses_mooc_fi_user_id.present? && authenticate_via_courses_mooc_fi(user.courses_mooc_fi_user_id, submitted_password) user if user.has_password?(submitted_password) end + def authenticate_via_courses_mooc_fi(courses_mooc_fi_user_id, submitted_password) + auth_url = SiteSetting.value('courses_mooc_fi_auth_url') + response = RestClient.post( + auth_url, + { + user_id: courses_mooc_fi_user_id, + password: submitted_password }.to_json, + { + content_type: :json, + accept: :json, + Authorization: Rails.application.secrets.tmc_server_secret_for_communicating_to_secret_project, + } + ) + + data = JSON.parse(response.body) + unless data['authenticated'] == true + raise "Authentication via courses.mooc.fi failed for #{email}" + end + + true + rescue RestClient::Unauthorized, RestClient::Forbidden + raise "Authentication rejected by courses.mooc.fi for #{email}" + rescue RestClient::ExceptionWithResponse => e + Rails.logger.error("Authentication via courses.mooc.fi error: #{e.response}") + raise "Authentication via courses.mooc.fi failed: #{e.message}" + rescue => e + Rails.logger.error("Unexpected error during authentication via courses.mooc.fi: #{e.message}") + raise "Unexpected error while authenticating via courses.mooc.fi: #{e.message}" + end + + def update_password_via_courses_mooc_fi(courses_mooc_fi_user_id, old_password, new_password) + update_url = SiteSetting.value('courses_mooc_fi_update_password_url') + + response = RestClient.put( + update_url, + { + user_id: courses_mooc_fi_user_id, + old_password: old_password, + new_password: new_password, + }.to_json, + { + content_type: :json, + accept: :json, + Authorization: Rails.application.secrets.tmc_server_secret_for_communicating_to_secret_project, + } + ) + + data = JSON.parse(response.body) + + unless data['updated'] == true + raise "Updating password via courses.mooc.fi failed for user with courses.mooc.fi-user-id #{courses_mooc_fi_user_id}" + end + + true + rescue RestClient::ExceptionWithResponse => e + Rails.logger.error("Updating password via courses.mooc.fi failed for user with courses.mooc.fi-user-id #{courses_mooc_fi_user_id}: #{e.response}") + false + rescue => e + Rails.logger.error("Unexpected error updating password via courses.mooc.fi for user with courses.mooc.fi-user-id #{courses_mooc_fi_user_id}: #{e.message}") + false + end + def password_reset_key action_tokens.find { |t| t.action == 'reset_password' } end diff --git a/config/routes.rb b/config/routes.rb index f3c7c23b1..04fc52c93 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -59,6 +59,7 @@ resources :request_deletion, only: [:create], module: :users resources :assistantships, module: :users, only: :index resources :teacherships, module: :users, only: :index + post :set_password_managed_by_courses_mooc_fi, on: :member end resources :user_app_datum, only: [:index] diff --git a/db/migrate/20250711214850_add_password_managed_by_courses_mooc_fi_to_users.rb b/db/migrate/20250711214850_add_password_managed_by_courses_mooc_fi_to_users.rb new file mode 100644 index 000000000..0f70f37af --- /dev/null +++ b/db/migrate/20250711214850_add_password_managed_by_courses_mooc_fi_to_users.rb @@ -0,0 +1,5 @@ +class AddPasswordManagedByCoursesMoocFiToUsers < ActiveRecord::Migration[7.1] + def change + add_column :users, :password_managed_by_courses_mooc_fi, :boolean, default: false, null: false + end +end diff --git a/db/migrate/20250723122117_add_courses_mooc_fi_user_id_to_users.rb b/db/migrate/20250723122117_add_courses_mooc_fi_user_id_to_users.rb new file mode 100644 index 000000000..8c478a373 --- /dev/null +++ b/db/migrate/20250723122117_add_courses_mooc_fi_user_id_to_users.rb @@ -0,0 +1,6 @@ +class AddCoursesMoocFiUserIdToUsers < ActiveRecord::Migration[7.1] + def change + add_column :users, :courses_mooc_fi_user_id, :string + add_index :users, :courses_mooc_fi_user_id, unique: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 32be56cfd..2bef330e0 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,18 +10,17 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2024_03_04_085436) do - +ActiveRecord::Schema[7.1].define(version: 2025_07_23_122117) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" create_table "action_tokens", id: :serial, force: :cascade do |t| t.integer "user_id", null: false t.string "token", null: false - t.datetime "created_at", null: false + t.datetime "created_at", precision: nil, null: false t.integer "action", null: false - t.datetime "expires_at" - t.datetime "updated_at" + t.datetime "expires_at", precision: nil + t.datetime "updated_at", precision: nil t.index ["user_id"], name: "index_action_tokens_on_user_id" end @@ -30,7 +29,7 @@ t.string "record_type", null: false t.bigint "record_id", null: false t.bigint "blob_id", null: false - t.datetime "created_at", null: false + t.datetime "created_at", precision: nil, null: false t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id" t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true end @@ -43,7 +42,7 @@ t.string "service_name", null: false t.bigint "byte_size", null: false t.string "checksum", null: false - t.datetime "created_at", null: false + t.datetime "created_at", precision: nil, null: false t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true end @@ -56,8 +55,8 @@ create_table "assistantships", id: :serial, force: :cascade do |t| t.integer "user_id" t.integer "course_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil t.index ["user_id", "course_id"], name: "index_assistantships_on_user_id_and_course_id", unique: true end @@ -73,7 +72,7 @@ t.integer "user_id", null: false t.integer "submission_id" t.string "name", null: false - t.datetime "created_at" + t.datetime "created_at", precision: nil t.boolean "awarded_after_soft_deadline", default: false, null: false t.index ["course_id", "user_id", "name"], name: "index_awarded_points_on_course_id_and_user_id_and_name", unique: true t.index ["course_id", "user_id", "submission_id"], name: "index_awarded_points_on_course_id_and_user_id_and_submission_id" @@ -83,8 +82,8 @@ create_table "banned_emails", force: :cascade do |t| t.string "email", null: false - t.datetime "created_at", precision: 6, null: false - t.datetime "updated_at", precision: 6, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end create_table "certificates", id: :serial, force: :cascade do |t| @@ -92,8 +91,8 @@ t.binary "pdf" t.integer "user_id" t.integer "course_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil t.index ["course_id"], name: "index_certificates_on_course_id" t.index ["user_id"], name: "index_certificates_on_user_id" end @@ -103,8 +102,8 @@ t.string "message" t.integer "sender_id" t.integer "course_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil t.index ["course_id"], name: "index_course_notifications_on_course_id" end @@ -125,8 +124,8 @@ end create_table "course_template_refreshes", id: :serial, force: :cascade do |t| - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil t.integer "status", default: 0, null: false t.decimal "percent_done", precision: 10, scale: 4, default: "0.0", null: false t.jsonb "langs_refresh_output" @@ -147,21 +146,21 @@ t.integer "cached_version", default: 0, null: false t.string "source_backend", default: "git", null: false t.string "git_branch", default: "master", null: false - t.datetime "expires_at" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "expires_at", precision: nil + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil end create_table "courses", id: :serial, force: :cascade do |t| t.string "name" - t.datetime "created_at" - t.datetime "updated_at" - t.datetime "hide_after" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil + t.datetime "hide_after", precision: nil t.boolean "hidden", default: false, null: false t.integer "cached_version", default: 0, null: false t.string "spreadsheet_key" - t.datetime "hidden_if_registered_after" - t.datetime "refreshed_at" + t.datetime "hidden_if_registered_after", precision: nil + t.datetime "refreshed_at", precision: nil t.boolean "locked_exercise_points_visible", default: true, null: false t.text "description" t.integer "paste_visibility" @@ -191,15 +190,15 @@ create_table "exercises", id: :serial, force: :cascade do |t| t.string "name" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil t.integer "course_id" - t.datetime "publish_time" + t.datetime "publish_time", precision: nil t.string "gdocs_sheet" t.boolean "hidden", default: false, null: false t.boolean "returnable_forced" t.string "checksum", default: "", null: false - t.datetime "solution_visible_after" + t.datetime "solution_visible_after", precision: nil t.boolean "has_tests", default: false, null: false t.text "deadline_spec" t.text "unlock_spec" @@ -225,8 +224,8 @@ t.string "exercise_name", null: false t.integer "submission_id" t.text "answer", null: false - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil t.index ["feedback_question_id", "course_id", "exercise_name"], name: "index_feedback_answers_question_course_exercise" t.index ["submission_id"], name: "index_feedback_answers_question" end @@ -235,8 +234,8 @@ t.integer "course_id", null: false t.text "question", null: false t.string "kind", null: false - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil t.integer "position", null: false t.text "title" t.index ["course_id"], name: "index_feedback_questions_on_course_id" @@ -244,8 +243,8 @@ create_table "kafka_batch_update_points", id: :serial, force: :cascade do |t| t.integer "course_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil t.integer "user_id" t.integer "exercise_id" t.string "task_type", default: "progress", null: false @@ -258,8 +257,8 @@ t.integer "to_course_id" t.integer "original_submission_id" t.integer "new_submission_id" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false t.index ["from_course_id", "to_course_id", "original_submission_id", "new_submission_id"], name: "unique_values", unique: true end @@ -267,8 +266,8 @@ t.integer "user_id", null: false t.integer "course_id", null: false t.string "exercise_name", null: false - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil t.index ["course_id"], name: "index_model_solution_access_logs_on_course_id" t.index ["user_id"], name: "index_model_solution_access_logs_on_user_id" end @@ -277,8 +276,8 @@ t.integer "user_id" t.integer "course_id" t.string "exercise_name" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false t.integer "cost", default: 1, null: false t.index ["course_id"], name: "index_model_solution_token_useds_on_course_id" t.index ["user_id"], name: "index_model_solution_token_useds_on_user_id" @@ -290,8 +289,8 @@ t.string "token", null: false t.integer "expires_in", null: false t.text "redirect_uri", null: false - t.datetime "created_at", null: false - t.datetime "revoked_at" + t.datetime "created_at", precision: nil, null: false + t.datetime "revoked_at", precision: nil t.string "scopes" t.index ["application_id"], name: "index_oauth_access_grants_on_application_id" t.index ["token"], name: "index_oauth_access_grants_on_token", unique: true @@ -303,8 +302,8 @@ t.string "token", null: false t.string "refresh_token" t.integer "expires_in" - t.datetime "revoked_at" - t.datetime "created_at", null: false + t.datetime "revoked_at", precision: nil + t.datetime "created_at", precision: nil, null: false t.string "scopes" t.index ["application_id"], name: "index_oauth_access_tokens_on_application_id" t.index ["refresh_token"], name: "index_oauth_access_tokens_on_refresh_token", unique: true @@ -318,8 +317,8 @@ t.string "secret", null: false t.text "redirect_uri", null: false t.string "scopes", default: "", null: false - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil t.boolean "confidential", default: true, null: false t.index ["uid"], name: "index_oauth_applications_on_uid", unique: true end @@ -333,8 +332,8 @@ create_table "organization_memberships", force: :cascade do |t| t.bigint "user_id" t.bigint "organization_id" - t.datetime "created_at", precision: 6, null: false - t.datetime "updated_at", precision: 6, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.index ["organization_id"], name: "index_organization_memberships_on_organization_id" t.index ["user_id", "organization_id"], name: "index_organization_memberships_on_user_id_and_organization_id", unique: true t.index ["user_id"], name: "index_organization_memberships_on_user_id" @@ -344,18 +343,18 @@ t.string "name" t.string "information" t.string "slug" - t.datetime "verified_at" + t.datetime "verified_at", precision: nil t.boolean "verified" t.boolean "disabled", default: false, null: false t.string "disabled_reason" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil t.boolean "hidden", default: false t.integer "creator_id" t.string "logo_file_name" t.string "logo_content_type" t.integer "logo_file_size" - t.datetime "logo_updated_at" + t.datetime "logo_updated_at", precision: nil t.string "phone" t.text "contact_information" t.string "email" @@ -367,16 +366,16 @@ create_table "points_upload_queues", id: :serial, force: :cascade do |t| t.integer "point_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil end create_table "recently_changed_user_details", id: :serial, force: :cascade do |t| t.integer "change_type", null: false t.string "old_value" t.string "new_value", null: false - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil t.string "username" t.string "email" t.integer "user_id" @@ -387,8 +386,8 @@ t.integer "feedback_answer_id" t.text "body" t.string "from" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil t.index ["feedback_answer_id"], name: "index_reply_to_feedback_answers_on_feedback_answer_id" end @@ -396,8 +395,8 @@ t.integer "submission_id", null: false t.integer "reviewer_id" t.text "review_body", null: false - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil t.text "points" t.boolean "marked_as_read", default: false, null: false t.index ["reviewer_id"], name: "index_reviews_on_reviewer_id" @@ -407,8 +406,8 @@ create_table "sessions", id: :serial, force: :cascade do |t| t.string "session_id", null: false t.text "data" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil t.index ["session_id"], name: "index_sessions_on_session_id" t.index ["updated_at"], name: "index_sessions_on_updated_at" end @@ -425,19 +424,19 @@ create_table "submissions", id: :serial, force: :cascade do |t| t.integer "user_id" t.text "pretest_error" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil t.string "exercise_name", null: false t.integer "course_id", null: false t.boolean "processed", default: false, null: false t.string "secret_token" t.boolean "all_tests_passed", default: false, null: false t.text "points" - t.datetime "processing_tried_at" - t.datetime "processing_began_at" - t.datetime "processing_completed_at" + t.datetime "processing_tried_at", precision: nil + t.datetime "processing_began_at", precision: nil + t.datetime "processing_completed_at", precision: nil t.integer "times_sent_to_sandbox", default: 0, null: false - t.datetime "processing_attempts_started_at" + t.datetime "processing_attempts_started_at", precision: nil t.integer "processing_priority", default: 0, null: false t.text "params_json" t.boolean "requires_review", default: false, null: false @@ -449,7 +448,7 @@ t.boolean "paste_available", default: false, null: false t.text "message_for_paste" t.string "paste_key" - t.datetime "client_time" + t.datetime "client_time", precision: nil t.bigint "client_nanotime" t.text "client_ip" t.string "sandbox" @@ -466,8 +465,8 @@ create_table "teacherships", id: :serial, force: :cascade do |t| t.integer "user_id" t.integer "organization_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil t.index ["user_id", "organization_id"], name: "index_teacherships_on_user_id_and_organization_id", unique: true end @@ -476,8 +475,8 @@ t.text "test_case_name" t.text "message" t.boolean "successful" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil t.text "exception" t.text "detailed_message" t.index ["submission_id"], name: "index_test_case_runs_on_submission_id" @@ -488,15 +487,15 @@ t.string "exercise_name" t.string "files_hash" t.text "value" - t.datetime "created_at" + t.datetime "created_at", precision: nil t.index ["course_id", "exercise_name"], name: "index_test_scanner_cache_entries_on_course_id_and_exercise_name", unique: true end create_table "uncomputed_unlocks", id: :serial, force: :cascade do |t| t.integer "course_id", null: false t.integer "user_id", null: false - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil t.index ["course_id", "user_id"], name: "index_uncomputed_unlocks_on_course_id_and_user_id" end @@ -504,8 +503,8 @@ t.integer "user_id", null: false t.integer "course_id", null: false t.string "exercise_name", null: false - t.datetime "valid_after" - t.datetime "created_at", null: false + t.datetime "valid_after", precision: nil + t.datetime "created_at", precision: nil, null: false t.index ["user_id", "course_id", "exercise_name"], name: "index_unlocks_on_user_id_and_course_id_and_exercise_name", unique: true end @@ -514,8 +513,8 @@ t.text "value" t.string "namespace" t.integer "user_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil t.index ["user_id", "field_name", "namespace"], name: "index_user_app_data_on_user_id_and_field_name_and_namespace", unique: true end @@ -523,23 +522,26 @@ t.integer "user_id", null: false t.string "field_name", null: false t.text "value", null: false - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil t.index ["user_id", "field_name"], name: "index_user_field_values_on_user_id_and_field_name", unique: true end create_table "users", id: :serial, force: :cascade do |t| t.string "login", null: false t.text "password_hash" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil t.string "salt" t.boolean "administrator", default: false, null: false t.text "email", default: "", null: false t.boolean "legitimate_student", default: true, null: false t.boolean "email_verified", default: false, null: false t.string "argon_hash" + t.boolean "password_managed_by_courses_mooc_fi", default: false, null: false + t.string "courses_mooc_fi_user_id" t.index "lower(email)", name: "index_user_email_lowercase", unique: true + t.index ["courses_mooc_fi_user_id"], name: "index_users_on_courses_mooc_fi_user_id", unique: true t.index ["login"], name: "index_users_on_login", unique: true end @@ -547,8 +549,8 @@ t.string "token", null: false t.integer "type", null: false t.integer "user_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil t.index ["user_id"], name: "index_verification_tokens_on_user_id" end diff --git a/spec/factories.rb b/spec/factories.rb index 3d0872957..8f0c4c416 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -31,6 +31,7 @@ def make_repo_for_course_template sequence(:email) { |n| "user#{n}@example.com" } sequence(:email_verified) { |n| 'false' } administrator { false } + password_managed_by_courses_mooc_fi { false } end factory :verified_user, class: User do @@ -39,6 +40,7 @@ def make_repo_for_course_template sequence(:email) { |n| "ver_user#{n}@example.com" } sequence(:email_verified) { |n| 'true' } administrator { false } + password_managed_by_courses_mooc_fi { false } end factory :admin, class: User do @@ -47,6 +49,7 @@ def make_repo_for_course_template sequence(:email) { |n| "admin#{n}@example.com" } sequence(:email_verified) { |n| 'true' } administrator { true } + password_managed_by_courses_mooc_fi { false } end factory :course, class: Course do