Skip to content
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
b25a155
Add api endpoint for setting user password to be handled by moocfi
Redande Jul 10, 2025
28d495e
Validate request payload
Redande Jul 10, 2025
7a0ee92
Lessen code complexity for codeclimate
Redande Jul 10, 2025
17105ed
Further lessen code complexity
Redande Jul 10, 2025
e8e9296
Fix model spec
Redande Jul 10, 2025
c700924
Prune docker before spec_models
Redande Jul 11, 2025
a016ffd
Revert docker pruning before spec_models
Redande Jul 11, 2025
4940b77
Specify courses.mooc.fi, delete password details from db
Redande Jul 12, 2025
7f192b6
Add migration file, fix rubocop error
Redande Jul 12, 2025
d71bdd8
Update factorybot user for specs
Redande Jul 12, 2025
968b875
Don't call @user.save twice
Redande Jul 13, 2025
ce1f10a
Authenticate via courses.mooc.fi if boolean true
Redande Jul 15, 2025
e0cdbae
Use rest-client for authentication request
Redande Jul 22, 2025
a900bc5
Update password through courses if boolean is set
Redande Jul 22, 2025
0286f75
Add courses.mooc.fi user id to users-table
Redande Jul 23, 2025
cc333a4
User courses.mooc.fi user id instead of email
Redande Jul 23, 2025
0794e94
Add courses.mooc.fi user id when moving password management
Redande Jul 23, 2025
9f486e5
Authorize requests to courses.mooc.fi
Redande Jul 28, 2025
a38d633
Add enpoint for getting user details by email
Redande Aug 26, 2025
3e86cc4
Remove unnecessary logging
Redande Aug 26, 2025
fcf8bef
Add api v8 endpoint for deleting users
Redande Sep 23, 2025
51f0ae8
Use Faraday instead of RestClient
Redande Dec 3, 2025
730cab7
Rubocop fixes
Redande Dec 3, 2025
7eb65dd
Remove rest-client import
Redande Dec 4, 2025
da13414
Remove unused google drive gem and related logic
Redande Dec 4, 2025
85002db
Use transaction in new destroy user endpoint
Redande Dec 4, 2025
9fb251e
Add back ancient lost point routes
Redande Dec 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
with:
submodules: false
- name: Setup git submodules
run: |
Expand All @@ -29,7 +29,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
with:
submodules: false
- name: Setup git submodules
run: |
Expand All @@ -47,7 +47,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
with:
submodules: false
- name: Setup git submodules
run: |
Expand All @@ -65,7 +65,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
with:
submodules: false
- name: Setup git submodules
run: |
Expand All @@ -83,7 +83,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
with:
submodules: false
- name: Setup git submodules
run: |
Expand All @@ -101,7 +101,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
with:
submodules: false
- name: Setup git submodules
run: |
Expand All @@ -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
run: docker compose run web bin/spec_integration
7 changes: 7 additions & 0 deletions app/controllers/api/v8/apidocs_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,13 @@ class ApidocsController < ActionController::Base
key :required, true
key :type, :integer
end
parameter :path_user_email do
key :name, :user_email
key :in, :path
key :description, "User's email"
key :required, true
key :type, :string
end
parameter :path_exercise_id do
key :name, :exercise_id
key :in, :path
Expand Down
132 changes: 124 additions & 8 deletions app/controllers/api/v8/users_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,54 @@ 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

swagger_path '/api/v8/users/get_user_with_email?email={email}' do
operation :get do
key :description, "Returns the user's id as upstream_id, user's courses.mooc.fi-id as id, email, first name and last name by user email"
key :operationId, 'getUserInformationByEmail'
key :produces, ['application/json']
key :tags, ['user']
parameter '$ref': '#/parameters/user_email'
response 403, '$ref': '#/responses/error'
response 404, '$ref': '#/responses/error'
response 200 do
key :description, "User's courses.mooc.fi-id as id, email, first name, last name and id as upstream_id as json"
key :content, 'application/json'
schema do
key :title, :user
key :required, [:user]
key :type, :object
property :id, type: :string, description: "User's courses.mooc.fi user id", example: 'ABCD1234-5678-EFGH-IJ90-ABC123DEF456'
property :email, type: :string, description: 'User email', example: 'user@example.com'
property :first_name, type: :string, description: 'User first name', example: 'John'
property :last_name, type: :string, description: 'User last name', example: 'Doe'
property :upstream_id, type: :integer, description: "User's user id in TMC database", example: 123
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
Expand Down Expand Up @@ -103,19 +151,13 @@ def create
set_extra_data

if BannedEmail.banned?(@user.email)
return render json: {
success: true,
message: 'User created.'
}
return render json: build_success_response(params[:include_id])
end

if @user.errors.empty? && @user.save
# TODO: Whitelist origins
UserMailer.email_confirmation(@user, params[:origin], params[:language]).deliver_now
render json: {
success: true,
message: 'User created.'
}
render json: build_success_response(params[:include_id])
else
errors = @user.errors
errors[:username] = errors.delete(:login) if errors.key?(:login)
Expand Down Expand Up @@ -149,6 +191,65 @@ def update
}, status: :bad_request
end

def destroy
unauthorize_guest! if current_user.guest?

user = User.find(params[:id])
authorize! :destroy, user

if user.destroy
RecentlyChangedUserDetail.where(username: user.login).delete_all
RecentlyChangedUserDetail.deleted.create!(
new_value: true,
email: user.email,
username: user.login,
user_id: user.id
)
Doorkeeper::AccessToken.where(resource_owner_id: user.id).delete_all

render json: { success: true, message: 'User deleted.' }
else
render json: { success: false, errors: user.errors }, status: :bad_request
end
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

def get_user_with_email
unauthorize_guest! if current_user.guest?

user = User.find_by!(email: params[:email])
authorize! :read, user

name = UserFieldValue.where(user_id: user.id, field_name: ['first_name', 'last_name']).pluck(:field_name, :value).to_h

render json: {
id: user.courses_mooc_fi_user_id,
email: user.email,
first_name: name['first_name'],
last_name: name['last_name'],
upstream_id: user.id,
}
end

private
def set_email
user_params = params[:user]
Expand Down Expand Up @@ -189,6 +290,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')
Expand Down Expand Up @@ -229,6 +334,17 @@ def set_extra_data(eager_save = false)
datum.save! if eager_save
end
end

def build_success_response(include_id = false)
response = {
success: true,
message: 'User created.',
}
if include_id
response[:id] = @user.id
end
response
end
end
end
end
29 changes: 20 additions & 9 deletions app/controllers/password_reset_keys_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,29 @@ def destroy
return render action: :show, status: :forbidden
end

@user.password = params[:password]
if @user.save
@key.destroy
flash[:success] = 'Your password has been reset.'
redirect_to root_path
if @user.password_managed_by_courses_mooc_fi
success = @user.update_password_via_courses_mooc_fi(nil, params[:password])
if success
@key.destroy
flash[:success] = 'Your password has been reset.'
redirect_to root_path
else
'Failed to reset password.'
end
else
flash.now[:alert] = if @user.errors[:password]
'Password ' + @user.errors[:password].join(', ')
@user.password = params[:password]
if @user.save
@key.destroy
flash[:success] = 'Your password has been reset.'
redirect_to root_path
else
'Failed to set password'
flash.now[:alert] = if @user.errors[:password]
'Password ' + @user.errors[:password].join(', ')
else
'Failed to set password'
end
render action: :show, status: :forbidden
end
render action: :show, status: :forbidden
end
end

Expand Down
4 changes: 4 additions & 0 deletions app/controllers/settings_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
4 changes: 4 additions & 0 deletions app/controllers/users_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
Loading