Skip to content

Commit 9a20994

Browse files
committed
Merge branch '8.0.x' of https://github.com/macite/doubtfire-api into 8.0.x
2 parents ae54a1c + 3ff0c5d commit 9a20994

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+2195
-173
lines changed

CHANGELOG.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,87 @@
22

33
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
44

5+
### [8.0.38](https://github.com/macite/doubtfire-deploy/compare/v8.0.37...v8.0.38) (2024-11-06)
6+
7+
8+
### Bug Fixes
9+
10+
* ensure task definitions can be created without scorm details ([b45f0fa](https://github.com/macite/doubtfire-deploy/commit/b45f0fa15d790396f0ba3dbfebb1c059bffe622d))
11+
12+
### [8.0.37](https://github.com/macite/doubtfire-deploy/compare/v8.0.36...v8.0.37) (2024-10-25)
13+
14+
15+
### Bug Fixes
16+
17+
* enhance substitutions for ipynb ([c19e149](https://github.com/macite/doubtfire-deploy/commit/c19e14992fe81cef1a17ab1dceda4e81888a8979))
18+
* ensure broken aux file does not kill future pdf generation ([715ccaf](https://github.com/macite/doubtfire-deploy/commit/715ccaf101655e63e9d081ef9b364cfccae62d13))
19+
* improve ipynb processing ([3d24fb2](https://github.com/macite/doubtfire-deploy/commit/3d24fb25d613515ee86c625c840dcf9db2636ca9))
20+
* revert notebook replacements from file helper ([e41cad3](https://github.com/macite/doubtfire-deploy/commit/e41cad3d9b27733cb3c79dd61449f9922f6f07ab))
21+
22+
### [8.0.25](https://github.com/macite/doubtfire-deploy/compare/v8.0.24...v8.0.25) (2024-08-09)
23+
24+
25+
### Bug Fixes
26+
27+
* ensure schema has index for auth token type ([7d3e4d3](https://github.com/macite/doubtfire-deploy/commit/7d3e4d369e66815b422faf46f8924397600266f1))
28+
* ensure test attempt review exception is handled ([bb3590c](https://github.com/macite/doubtfire-deploy/commit/bb3590c14c5c66191833fa98ee6c6eeebc2a3d78))
29+
* remove default from cmi_datamodel in test attempt ([ccb20dc](https://github.com/macite/doubtfire-deploy/commit/ccb20dc5c1efea2e5d0331026bc17d39dda3db11))
30+
31+
### [8.0.24](https://github.com/macite/doubtfire-deploy/compare/v8.0.23...v8.0.24) (2024-08-09)
32+
33+
34+
### Features
35+
36+
* add attribute to allow file upload before scorm is passed ([fce7e75](https://github.com/macite/doubtfire-deploy/commit/fce7e7519bb9171726a030b409aee23de65f44fd))
37+
* add Numbas config options to task def backend ([d53610a](https://github.com/macite/doubtfire-deploy/commit/d53610a3f4b0c8077aea34cbfa2924e301914e1f))
38+
* add numbas task comment on test completion ([3f5aa2b](https://github.com/macite/doubtfire-deploy/commit/3f5aa2be6bd69441730375b689751fe881d7617a))
39+
* add test attempt auth ([7d31f7c](https://github.com/macite/doubtfire-deploy/commit/7d31f7caaae6dc1efa24f78842873e9f55796279))
40+
* change Numbas time delay config to enable incremental delays ([54c27ce](https://github.com/macite/doubtfire-deploy/commit/54c27cef2b8ff57fd8ac972728ec3d249e2862b8))
41+
* create unique token for scorm asset retrieval ([fc8134a](https://github.com/macite/doubtfire-deploy/commit/fc8134ab6b734b7daf064a67ad15f3cefba1d7d6))
42+
* enable reviewing, passing, and deleting test attempts ([8c9a68b](https://github.com/macite/doubtfire-deploy/commit/8c9a68ba6b3914da24ba33ee62f6a5a00e101c76))
43+
* enable students to request extra scorm attempt ([c5055b8](https://github.com/macite/doubtfire-deploy/commit/c5055b858c30ba693c535590e1ccff0e8e0b42da))
44+
* restrict test attempts by limit and comments to when test is completed ([26d75f5](https://github.com/macite/doubtfire-deploy/commit/26d75f51b7fcf11dac0834ddc5a46f40c07407de))
45+
46+
47+
### Bug Fixes
48+
49+
* add allow review property to task def related files ([3539d95](https://github.com/macite/doubtfire-deploy/commit/3539d957022f0c6310a2939dd6eccad946cb6610))
50+
* add missing numbas config fields to fix unit tests ([89a6615](https://github.com/macite/doubtfire-deploy/commit/89a66157b4fde887a19912ca40261243b4961e2f))
51+
* add scorm bypass to excel file ([4139690](https://github.com/macite/doubtfire-deploy/commit/413969069969316f6ea9c515e4ec9da6b332be0a))
52+
* calculate attempt number and limit instead of using stored int ([28f3279](https://github.com/macite/doubtfire-deploy/commit/28f327964edb0c9326b487a674d19b7da7da8c89))
53+
* change scorm comment text ([69053ee](https://github.com/macite/doubtfire-deploy/commit/69053ee147503e7916e929aac5c834903c0087ba))
54+
* check for attempts before accessing properties ([4255347](https://github.com/macite/doubtfire-deploy/commit/42553479eb2a018a9273931e033084e26b3d18d5))
55+
* check if no old scorm tokens exist ([6108b52](https://github.com/macite/doubtfire-deploy/commit/6108b52bc04d7866548c8738b39d37c30d24f602))
56+
* consolidate numbas api endpoints ([27253bd](https://github.com/macite/doubtfire-deploy/commit/27253bd1b1d5640d00098f692160dd4b50675640))
57+
* enforce attempt limit ([d71ea14](https://github.com/macite/doubtfire-deploy/commit/d71ea14d319a59ba1e96bbd5bf34c85a21f0c0f6))
58+
* expose enable Numbas test config to all users ([20d5265](https://github.com/macite/doubtfire-deploy/commit/20d526533a2ecab592d7d22f3330d37cee7e0f45))
59+
* expose scorm configs to student ([910eecd](https://github.com/macite/doubtfire-deploy/commit/910eecdc218f52e572d39059e64a0b28acb44dce))
60+
* grant same number of extra attempts as scorm limit ([3d44ef2](https://github.com/macite/doubtfire-deploy/commit/3d44ef2ea57829131cc3c70d1655ccd996154ee2))
61+
* post scorm comment after test attempt termination ([0812e20](https://github.com/macite/doubtfire-deploy/commit/0812e206a9dadcfe7d575feec04e49b15b412556))
62+
* preload unit in test attempt and ensure limit flexibility in validation ([8059213](https://github.com/macite/doubtfire-deploy/commit/80592130bfb33bbb74322c5950e62e2663223af1))
63+
* prevent new attempt if last is incomplete or passed ([1240b3f](https://github.com/macite/doubtfire-deploy/commit/1240b3fa853d3a3f3fd1ad061f9cc6f6635c2c37))
64+
* prevent scorm extensions if no attempt limit ([1ae0347](https://github.com/macite/doubtfire-deploy/commit/1ae03478bb2c55b5e281a876bae37f730206ac3e))
65+
* refactor numbas config reset logic ([ff5ff62](https://github.com/macite/doubtfire-deploy/commit/ff5ff62061c05e509f15af3048fe047b0d69dc68))
66+
* rename entity file and add update fields in task spreadsheet ([b498924](https://github.com/macite/doubtfire-deploy/commit/b4989242e37ccd046651bfc8db32934ee94e190a))
67+
* reorder columns for csv export ([5db5f35](https://github.com/macite/doubtfire-deploy/commit/5db5f35dc6cc1874c50f5891ca7bbd752ea32b55))
68+
* reset Numbas configs if no zip file has been uploaded ([3f19ffa](https://github.com/macite/doubtfire-deploy/commit/3f19ffa6f4f465ed0691582b5012cf997ec62852))
69+
* temporarily disable auth and fix test attempt lookup ([b4d3f9d](https://github.com/macite/doubtfire-deploy/commit/b4d3f9dc1661b733eaf704c551ceb5836789db22))
70+
* update auth token to work with scorm and general ([e7a6eed](https://github.com/macite/doubtfire-deploy/commit/e7a6eed53d8e7049b6144e2b07b8018725be01fb))
71+
* use correct endpoint url and include exam result for numbas test attempts ([ee992f4](https://github.com/macite/doubtfire-deploy/commit/ee992f4218b8ca07c9259d6569c9c946af7701ef))
72+
* use correct Numbas data path in Numbas api ([5d80830](https://github.com/macite/doubtfire-deploy/commit/5d80830d3564bb7137db3c4adb3b1d906342e851))
73+
* use custom endpoint for Numbas ([0cc4915](https://github.com/macite/doubtfire-deploy/commit/0cc4915c85d7d55b48ca6832f6779e49362a7870))
74+
* use project and task def to fix issue where task is undefined on launching scorm test ([2a04a06](https://github.com/macite/doubtfire-deploy/commit/2a04a068282f69b11a6243a590bb25edcdd5c2c1))
75+
* use test attempt entity in file instead ([a7c4006](https://github.com/macite/doubtfire-deploy/commit/a7c400669bf199f30b627b54c4ed49157ff88222))
76+
* use unique perms for scorm test retrieval ([08a0090](https://github.com/macite/doubtfire-deploy/commit/08a00906019ce0c2706c34cf053a511b6e5ddca2))
77+
* validate attempt id ([c5240d8](https://github.com/macite/doubtfire-deploy/commit/c5240d8da378b84deb3ac64e1584808b07d5e671))
78+
79+
### [8.0.36](https://github.com/macite/doubtfire-deploy/compare/v8.0.35...v8.0.36) (2024-09-24)
80+
81+
82+
### Bug Fixes
83+
84+
* markdown in ipynb ([4fd4ed3](https://github.com/macite/doubtfire-deploy/commit/4fd4ed3c633e7aa4ee801e5d274c82aa840f31ac))
85+
586
### [8.0.35](https://github.com/doubtfire-lms/doubtfire-deploy/compare/v8.0.34...v8.0.35) (2024-09-23)
687

788

app/api/api_root.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ class ApiRoot < Grape::API
6060
mount BreaksApi
6161
mount DiscussionCommentApi
6262
mount ExtensionCommentsApi
63+
mount ScormExtensionCommentsApi
6364
mount GroupSetsApi
6465
mount LearningOutcomesApi
6566
mount LearningAlignmentApi
@@ -81,6 +82,8 @@ class ApiRoot < Grape::API
8182
mount Tii::TiiGroupAttachmentApi
8283
mount Tii::TiiActionApi
8384

85+
mount ScormApi
86+
mount TestAttemptsApi
8487
mount CampusesPublicApi
8588
mount CampusesAuthenticatedApi
8689
mount TutorialsApi
@@ -101,6 +104,7 @@ class ApiRoot < Grape::API
101104
AuthenticationHelpers.add_auth_to BreaksApi
102105
AuthenticationHelpers.add_auth_to DiscussionCommentApi
103106
AuthenticationHelpers.add_auth_to ExtensionCommentsApi
107+
AuthenticationHelpers.add_auth_to ScormExtensionCommentsApi
104108
AuthenticationHelpers.add_auth_to GroupSetsApi
105109
AuthenticationHelpers.add_auth_to LearningOutcomesApi
106110
AuthenticationHelpers.add_auth_to LearningAlignmentApi
@@ -127,6 +131,8 @@ class ApiRoot < Grape::API
127131
AuthenticationHelpers.add_auth_to UnitRolesApi
128132
AuthenticationHelpers.add_auth_to UnitsApi
129133
AuthenticationHelpers.add_auth_to WebcalApi
134+
AuthenticationHelpers.add_auth_to ScormApi
135+
AuthenticationHelpers.add_auth_to TestAttemptsApi
130136

131137
add_swagger_documentation \
132138
base_path: nil,

app/api/authentication_api.rb

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
class AuthenticationApi < Grape::API
1212
helpers LogHelper
1313
helpers AuthenticationHelpers
14+
helpers AuthorisationHelpers
1415

1516
#
1617
# Sign in - only mounted if AAF auth is NOT used
@@ -71,7 +72,7 @@ class AuthenticationApi < Grape::API
7172

7273
# Return user details
7374
present :user, user, with: Entities::UserEntity
74-
present :auth_token, user.generate_authentication_token!(remember).authentication_token
75+
present :auth_token, user.generate_authentication_token!(remember: remember).authentication_token
7576
end
7677
end
7778

@@ -237,18 +238,18 @@ class AuthenticationApi < Grape::API
237238
requires :auth_token, type: String, desc: 'The user\'s temporary auth token'
238239
end
239240
post '/auth' do
240-
error!({ error: 'Invalid token.' }, 404) if params[:auth_token].nil?
241-
logger.info "Get user via auth_token from #{request.ip}"
241+
error!({ error: 'Invalid authentication details.' }, 404) if params[:auth_token].blank? || params[:username].blank?
242+
logger.info "Get user via auth_token from #{request.ip} - #{params[:username]}"
242243

243244
# Authenticate that the token is okay
244-
if authenticated?
245+
if authenticated?(:login)
245246
user = User.find_by(username: params[:username])
246-
token = user.token_for_text?(params[:auth_token]) unless user.nil?
247-
error!({ error: 'Invalid token.' }, 404) if token.nil?
247+
token = user.token_for_text?(params[:auth_token], :login) unless user.nil?
248+
error!({ error: 'Invalid authentication details.' }, 404) if token.nil?
248249

249250
# Invalidate the token and regenrate a new one
250251
token.destroy!
251-
token = user.generate_authentication_token! true
252+
token = user.generate_authentication_token!
252253

253254
logger.info "Login #{params[:username]} from #{request.ip}"
254255

@@ -324,7 +325,7 @@ class AuthenticationApi < Grape::API
324325

325326
# Find user
326327
user = User.find_by(username: user_param)
327-
token = user.token_for_text?(token_param) unless user.nil?
328+
token = user.token_for_text?(token_param, :general) unless user.nil?
328329
remember = params[:remember] || false
329330

330331
# Token does not match user
@@ -359,7 +360,7 @@ class AuthenticationApi < Grape::API
359360
}
360361
delete '/auth' do
361362
user = User.find_by(username: headers['username'] || headers['Username'])
362-
token = user.token_for_text?(headers['auth-token'] || headers['Auth-Token']) unless user.nil?
363+
token = user.token_for_text?(headers['auth-token'] || headers['Auth-Token'], :general) unless user.nil?
363364

364365
if token.present?
365366
logger.info "Sign out #{user.username} from #{request.ip}"
@@ -368,4 +369,21 @@ class AuthenticationApi < Grape::API
368369

369370
present nil
370371
end
372+
373+
desc 'Get SCORM authentication token'
374+
get '/auth/scorm' do
375+
if authenticated?(:general)
376+
unless authorise? current_user, User, :get_scorm_token
377+
error!({ error: 'You cannot get SCORM tokens' }, 403)
378+
end
379+
380+
token = current_user.auth_tokens.find_by(token_type: :scorm)
381+
if token.nil? || token.auth_token_expiry <= Time.zone.now
382+
token&.destroy
383+
token = current_user.generate_scorm_authentication_token!
384+
end
385+
386+
present :scorm_auth_token, token.authentication_token
387+
end
388+
end
371389
end

app/api/entities/task_definition_entity.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ def staff?(my_role)
3939
expose :has_task_sheet?, as: :has_task_sheet
4040
expose :has_task_resources?, as: :has_task_resources
4141
expose :has_task_assessment_resources?, as: :has_task_assessment_resources, if: ->(unit, options) { staff?(options[:my_role]) }
42+
expose :has_scorm_data?, as: :has_scorm_data
43+
expose :scorm_enabled
44+
expose :scorm_allow_review
45+
expose :scorm_bypass_test
46+
expose :scorm_time_delay_enabled
47+
expose :scorm_attempt_limit
4248
expose :is_graded
4349
expose :max_quality_pts
4450
expose :overseer_image_id, if: ->(unit, options) { staff?(options[:my_role]) }, expose_nil: false

app/api/entities/task_entity.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class TaskEntity < Grape::Entity
1717
end
1818

1919
expose :extensions
20+
expose :scorm_extensions
2021

2122
expose :times_assessed
2223
expose :grade, expose_nil: false
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
module Entities
2+
class TestAttemptEntity < Grape::Entity
3+
expose :id
4+
expose :task_id
5+
expose :attempted_time
6+
expose :terminated
7+
expose :success_status
8+
expose :score_scaled
9+
expose :completion_status
10+
expose :cmi_datamodel
11+
end
12+
end

app/api/scorm_api.rb

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
require 'grape'
2+
require 'zip'
3+
require 'mime/types'
4+
class ScormApi < Grape::API
5+
# Include the AuthenticationHelpers for authentication functionality
6+
helpers AuthenticationHelpers
7+
helpers AuthorisationHelpers
8+
9+
before do
10+
authenticated? :scorm
11+
end
12+
13+
helpers do
14+
# Method to stream a file from a zip archive at the specified path
15+
# @param zip_path [String] the path to the zip archive
16+
# @param file_path [String] the path of the file within the zip archive
17+
def stream_file_from_zip(zip_path, file_path)
18+
file_stream = nil
19+
20+
logger.debug "Streaming zip file at #{zip_path}"
21+
# Get an input stream for the requested file within the ZIP archive
22+
Zip::File.open(zip_path) do |zip_file|
23+
zip_file.each do |entry|
24+
next unless entry.name == file_path
25+
logger.debug "Found file #{file_path} from SCORM container"
26+
file_stream = entry.get_input_stream
27+
break
28+
end
29+
end
30+
31+
# If the file was not found in the ZIP archive, return a 404 response
32+
unless file_stream
33+
error!({ error: 'File not found' }, 404)
34+
end
35+
36+
# Set the content type based on the file extension
37+
content_type = MIME::Types.type_for(file_path).first.content_type
38+
logger.debug "Content type: #{content_type}"
39+
40+
# Set the content type header
41+
header 'Content-Type', content_type
42+
43+
# Set cache control header to prevent caching
44+
header 'Cache-Control', 'no-cache, no-store, must-revalidate'
45+
46+
# Set the body to the contents of the file_stream and return the response
47+
body file_stream.read
48+
end
49+
end
50+
51+
desc 'Serve SCORM content'
52+
params do
53+
requires :task_def_id, type: Integer, desc: 'Task Definition ID to get SCORM test data for'
54+
end
55+
get '/scorm/:task_def_id/:username/:auth_token/*file_path' do
56+
task_def = TaskDefinition.find(params[:task_def_id])
57+
58+
unless authorise? current_user, task_def.unit, :get_unit
59+
error!({ error: 'You cannot access SCORM tests of unit' }, 403)
60+
end
61+
62+
env['api.format'] = :txt
63+
if task_def.has_scorm_data?
64+
zip_path = task_def.task_scorm_data
65+
content_type 'application/octet-stream'
66+
stream_file_from_zip(zip_path, params[:file_path])
67+
else
68+
error!({ error: 'SCORM data does not exist.' }, 404)
69+
end
70+
end
71+
end
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
require 'grape'
2+
3+
class ScormExtensionCommentsApi < Grape::API
4+
helpers AuthenticationHelpers
5+
helpers AuthorisationHelpers
6+
7+
desc 'Request a scorm extension for a task'
8+
params do
9+
requires :comment, type: String, desc: 'The details of the request'
10+
end
11+
post '/projects/:project_id/task_def_id/:task_definition_id/request_scorm_extension' do
12+
project = Project.find(params[:project_id])
13+
task_definition = project.unit.task_definitions.find(params[:task_definition_id])
14+
task = project.task_for_task_definition(task_definition)
15+
16+
# check permissions using specific permission has with addition of request extension if allowed in unit
17+
unless authorise? current_user, task, :request_scorm_extension
18+
error!({ error: 'Not authorised to request a scorm extension for this task' }, 403)
19+
end
20+
21+
if task_definition.scorm_attempt_limit == 0
22+
error!({ message: 'This task allows unlimited attempts to complete the test' }, 400)
23+
return
24+
end
25+
26+
result = task.apply_for_scorm_extension(current_user, params[:comment])
27+
present result.serialize(current_user), Grape::Presenters::Presenter
28+
end
29+
30+
desc 'Assess a scorm extension for a task'
31+
params do
32+
requires :granted, type: Boolean, desc: 'Assess a scorm extension'
33+
end
34+
put '/projects/:project_id/task_def_id/:task_definition_id/assess_scorm_extension/:task_comment_id' do
35+
project = Project.find(params[:project_id])
36+
task_definition = project.unit.task_definitions.find(params[:task_definition_id])
37+
task = project.task_for_task_definition(task_definition)
38+
39+
unless authorise? current_user, task, :assess_scorm_extension
40+
error!({ error: 'Not authorised to assess a scorm extension for this task' }, 403)
41+
end
42+
43+
task_comment = task.all_comments.find(params[:task_comment_id]).becomes(ScormExtensionComment)
44+
45+
unless task_comment.assess_scorm_extension(current_user, params[:granted])
46+
if task_comment.errors.count >= 1
47+
error!({ error: task_comment.errors.full_messages.first }, 403)
48+
else
49+
error!({ error: 'Error saving scorm extension' }, 403)
50+
end
51+
end
52+
present task_comment.serialize(current_user), Grape::Presenters::Presenter
53+
end
54+
end

0 commit comments

Comments
 (0)