Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .tool-versions
Original file line number Diff line number Diff line change
@@ -1 +1 @@
ruby 3.3.8
ruby 3.3
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ gem 'strong_migrations'
# Logging Customization
gem 'lograge'

# Environment variable management
gem 'dotenv-rails', require: 'dotenv/load'

# Use Active Storage for file uploads [https://guides.rubyonrails.org/active_storage_overview.html]
# gem "activestorage", "~> 7.0.0"

Expand Down
5 changes: 5 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,10 @@ GEM
diff-lcs (1.6.2)
docile (1.4.1)
domain_name (0.6.20240107)
dotenv (3.2.0)
dotenv-rails (3.2.0)
dotenv (= 3.2.0)
railties (>= 6.1)
drb (2.2.3)
dumb_delegator (1.1.0)
erb (6.0.2)
Expand Down Expand Up @@ -632,6 +636,7 @@ DEPENDENCIES
cucumber-rails
database_cleaner-active_record
debug
dotenv-rails
factory_bot_rails
faraday
faraday-cookie_jar
Expand Down
30 changes: 27 additions & 3 deletions app/controllers/session_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,25 @@ def omniauth_callback
'email' => auth.info.email
}
creds = auth.credentials # an OmniAuth::AuthHash

# dev provider doesnt have real credentials so its stubbed
expires_at = creds.expires_at || 30.days.from_now.to_i
refresh_token = creds.refresh_token || 'none'

access_token = OAuth2::AccessToken.new(
OAuth2::Client.new('', ''), # client never used – stub
creds.token,
refresh_token: creds.refresh_token,
expires_at: creds.expires_at
refresh_token: refresh_token,
expires_at: expires_at
)

# Persist / update the user just like `create`
find_or_create_user(user_data, access_token)
user = find_or_create_user(user_data, access_token)

# Auto-enroll developer login users in test courses
if auth.provider == 'developer'
ensure_developer_test_enrollments(user)
end

redirect_to courses_path, notice: "Logged in! Welcome, #{user_data['name']}!"
rescue StandardError => e
Expand All @@ -79,6 +89,18 @@ def destroy

private

def ensure_developer_test_enrollments(user)
# Find the test course
test_course = Course.find_by(course_code: 'DEV101')

# Ensure enrollment in the test course (as student so they can request extensions)
if test_course
UserToCourse.find_or_create_by!(user_id: user.id, course_id: test_course.id) do |utc|
utc.role = 'student'
end
end
end

# TODO: Refactor.
def find_or_create_user(user_data, auth_token)
auth_token.token
Expand All @@ -102,6 +124,8 @@ def find_or_create_user(user_data, auth_token)
# Store user ID in session for authentication
session[:username] = user.name
session[:user_id] = user.canvas_uid

user
end

# TODO: Move this to a Canvas API libarary or user service
Expand Down
4 changes: 3 additions & 1 deletion app/views/home/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
</div>
<div class="col-12 text-center">
<%= link_to 'Login', '/auth/canvas', class: 'btn btn-primary', id: 'login-button-index' %>
<%# <a href="/login" class="btn btn-secondary" id="login-button-index">Login</a> %>
<% if Rails.env.development? %>
<%= link_to 'Developer Login', '/auth/developer', class: 'btn btn-secondary ms-2', id: 'dev-login-button' %>
<% end %>
</div>
</div>
</div>
12 changes: 6 additions & 6 deletions app/views/requests/instructor_index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -31,22 +31,22 @@
id="requests-table">
<thead>
<tr class="table-info">
<th class="text-center align-content-center no-sort" style="min-width: 60px;" data-priority="1">
<th class="text-center align-content-center no-sort" style="min-width: 60px;" data-priority="10">
<input type="checkbox"
id="select-all-requests"
data-requests-target="selectAllCheckbox"
data-action="change->requests#toggleSelectAll"
aria-label="Select all pending requests">
</th>
<th class="text-center align-content-center no-sort" style="min-width: 100px;" data-priority="1">Actions</th>
<th class="text-center align-content-center" id="name" data-priority="5">Name</th>
<th class="text-center align-content-center" id="assignment" style="min-width: 120px; max-width: 200px;" data-priority="2">Assignment</th>
<th class="text-center align-content-center" id="name" data-priority="3">Name</th>
<th class="text-center align-content-center" id="assignment" style="min-width: 120px; max-width: 200px;" data-priority="1">Assignment</th>
<th class="text-center align-content-center" id="student-id" data-priority="9">Student ID</th>
<th class="text-center align-content-center" style="min-width: 198px;" data-priority="7">Requested At</th>
<th class="text-center align-content-center" style="min-width: 198px;" data-priority="8">Original Due Date</th>
<th class="text-center align-content-center" style="min-width: 198px;" data-priority="4">Requested Due Date</th>
<th class="text-center align-content-center" style="min-width: 198px;" data-priority="5">Requested Due Date</th>
<th class="text-center align-content-center" style="min-width: 90px;" data-priority="6"># of Days</th>
<th class="text-center align-content-center" style="min-width: 200px;" data-priority="3">Status</th>
<th class="text-center align-content-center" style="min-width: 200px;" data-priority="10">Status</th>
</tr>
</thead>
<tbody>
Expand Down Expand Up @@ -143,7 +143,7 @@
</tbody>
</table>
</div>
<div class="row mt-2">
<div class="row mt-2 d-none d-md-block">
<div class="col-12 text-start">
<button type="button"
class="btn btn-sm btn-success me-2"
Expand Down
3 changes: 2 additions & 1 deletion config/initializers/omniauth.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ def build_access_token
client_options: {
site: ENV['CANVAS_URL'],
authorize_url: "/login/oauth2/auth?scope=#{encoded_scopes}"
}
},
redirect_uri: "#{ENV['CANVAS_REDIRECT_URI']}/auth/canvas/callback"
end

# OmniAuth.config.before_request_phase do |env|
Expand Down
62 changes: 62 additions & 0 deletions db/seeds.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,65 @@
name: SystemUserService::AUTO_APPROVAL_NAME,
canvas_uid: SystemUserService::AUTO_APPROVAL_UID
)

# Developer test users. you can use teacher or student
if Rails.env.development?
# Teacher user for managing courses
teacher_user = User.find_or_create_by!(email: 'teacher@example.com') do |u|
u.name = 'Test Teacher'
u.admin = false
u.canvas_uid = 'teacher@example.com'
end

# Student user for requesting extensions
student_user = User.find_or_create_by!(email: 'student@example.com') do |u|
u.name = 'Test Student'
u.admin = false
u.canvas_uid = 'student@example.com'
end

# Create a single test course
test_course = Course.find_or_create_by!(course_code: 'DEV101') do |c|
c.course_name = 'Development Test Course'
c.canvas_id = 'dev-course-001'
end

# Link test course to Canvas LMS
CourseToLms.find_or_create_by!(course_id: test_course.id, lms_id: 1) do |ctl|
ctl.external_course_id = 'dev-course-001'
end

# Enroll teacher as instructor
UserToCourse.find_or_create_by!(user_id: teacher_user.id, course_id: test_course.id) do |utc|
utc.role = 'teacher'
end

# Enroll student as student
UserToCourse.find_or_create_by!(user_id: student_user.id, course_id: test_course.id) do |utc|
utc.role = 'student'
end

# Enable extensions for test course
CourseSettings.find_or_create_by!(course_id: test_course.id) do |cs|
cs.enable_extensions = true
end

# Create form settings for test course
FormSetting.find_or_create_by!(course_id: test_course.id) do |fs|
fs.documentation_disp = 'optional'
fs.custom_q1_disp = 'optional'
fs.custom_q2_disp = 'optional'
end

# Create sample assignments for test course
course_lms = CourseToLms.find_by(course_id: test_course.id, lms_id: 1)

if course_lms
Assignment.find_or_create_by!(course_to_lms_id: course_lms.id, external_assignment_id: 'dev-hw-1') do |a|
a.name = 'Homework'
a.due_date = 3.days.from_now
a.late_due_date = 10.days.from_now
a.enabled = true
end
end
end
55 changes: 55 additions & 0 deletions spec/controllers/session_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,61 @@
end
end

describe 'GET #omniauth_callback (developer provider)' do
let(:dev_auth_hash) do
OmniAuth::AuthHash.new(
provider: 'developer',
uid: 'test@example.com',
info: OpenStruct.new(name: 'Test Developer', email: 'test@example.com'),
credentials: {
token: 'dev-token',
refresh_token: nil,
expires_at: nil
}
)
end

before do
request.env['omniauth.auth'] = dev_auth_hash
end

context 'developer provider login with missing credentials' do
it 'handles nil credentials gracefully' do
get :omniauth_callback, params: { provider: 'developer' }

user = User.find_by(canvas_uid: 'test@example.com')
expect(user).to be_present
expect(user.email).to eq('test@example.com')

expect(session[:user_id]).to eq('test@example.com')
expect(response).to redirect_to(courses_path)
end

# test course for dev login
it 'auto-enrolls developer login users in test course' do
test_course = Course.create!(course_code: 'DEV101', course_name: 'Test Course', canvas_id: 'dev-001')

get :omniauth_callback, params: { provider: 'developer' }

user = User.find_by(canvas_uid: 'test@example.com')
enrollment = UserToCourse.find_by(user_id: user.id, course_id: test_course.id)

expect(enrollment).to be_present
expect(enrollment.role).to eq('student')
end

it 'stores fake refresh token for developer provider' do
get :omniauth_callback, params: { provider: 'developer' }

user = User.find_by(canvas_uid: 'test@example.com')
creds = user.lms_credentials.first

expect(creds.refresh_token).to be_present
expect(creds.token).to be_present
end
end
end

describe 'GET #logout' do
before do
session[:user_id] = 'test_user_id'
Expand Down
Loading