Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
79ffcb7e
Commit
79ffcb7e
authored
Dec 20, 2013
by
Oleg Marshev
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Refactor stub implementation of LTI Provider. BLD-601.
parent
15fc640e
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
17 changed files
with
335 additions
and
251 deletions
+335
-251
CHANGELOG.rst
+2
-0
common/djangoapps/terrain/start_stubs.py
+2
-1
common/djangoapps/terrain/stubs/http.py
+1
-1
common/djangoapps/terrain/stubs/lti.py
+237
-0
common/djangoapps/terrain/stubs/start.py
+2
-0
common/djangoapps/terrain/stubs/tests/test_lti_stub.py
+72
-0
common/lib/xmodule/xmodule/lti_module.py
+6
-2
common/lib/xmodule/xmodule/tests/test_lti_unit.py
+2
-1
lms/djangoapps/courseware/features/lti.py
+4
-9
lms/djangoapps/courseware/features/lti_setup.py
+0
-52
lms/djangoapps/courseware/mock_lti_server/__init__.py
+0
-0
lms/djangoapps/courseware/mock_lti_server/mock_lti_server.py
+0
-0
lms/djangoapps/courseware/mock_lti_server/server_start.py
+0
-30
lms/djangoapps/courseware/mock_lti_server/test_mock_lti_server.py
+0
-154
lms/djangoapps/courseware/tests/test_lti_integration.py
+5
-1
lms/envs/acceptance.py
+1
-0
lms/envs/devstack.py
+1
-0
No files found.
CHANGELOG.rst
View file @
79ffcb7e
...
...
@@ -5,6 +5,8 @@ These are notable changes in edx-platform. This is a rolling list of changes,
in roughly chronological order, most recent first. Add your entries at or near
the top. Include a label indicating the component affected.
Blades: Refactor stub implementation of LTI Provider. BLD-601.
LMS: In left accordion and progress page, due dates are now displayed in time
zone specified by settings.TIME_ZONE, instead of UTC always
...
...
common/djangoapps/terrain/start_stubs.py
View file @
79ffcb7e
...
...
@@ -6,11 +6,12 @@ from lettuce import before, after, world
from
django.conf
import
settings
from
terrain.stubs.youtube
import
StubYouTubeService
from
terrain.stubs.xqueue
import
StubXQueueService
from
terrain.stubs.lti
import
StubLtiService
SERVICES
=
{
"youtube"
:
{
"port"
:
settings
.
YOUTUBE_PORT
,
"class"
:
StubYouTubeService
},
"xqueue"
:
{
"port"
:
settings
.
XQUEUE_PORT
,
"class"
:
StubXQueueService
},
"lti"
:
{
"port"
:
settings
.
LTI_PORT
,
"class"
:
StubLtiService
},
}
...
...
common/djangoapps/terrain/stubs/http.py
View file @
79ffcb7e
...
...
@@ -236,7 +236,7 @@ class StubHttpService(HTTPServer, object):
Configure the server to listen on localhost.
Default is to choose an arbitrary open port.
"""
address
=
(
'
127.0.0.1
'
,
port_num
)
address
=
(
'
0.0.0.0
'
,
port_num
)
HTTPServer
.
__init__
(
self
,
address
,
self
.
HANDLER_CLASS
)
# Create a dict to store configuration values set by the client
...
...
common/djangoapps/terrain/stubs/lti.py
0 → 100644
View file @
79ffcb7e
"""
Stub implementation of LTI Provider.
What is supported:
------------------
1.) This LTI Provider can service only one Tool Consumer at the same time. It is
not possible to have this LTI multiple times on a single page in LMS.
"""
from
uuid
import
uuid4
import
textwrap
import
urllib
import
re
from
oauthlib.oauth1.rfc5849
import
signature
import
oauthlib.oauth1
import
hashlib
import
base64
import
mock
import
requests
from
http
import
StubHttpRequestHandler
,
StubHttpService
class
StubLtiHandler
(
StubHttpRequestHandler
):
"""
A handler for LTI POST and GET requests.
"""
DEFAULT_CLIENT_KEY
=
'test_client_key'
DEFAULT_CLIENT_SECRET
=
'test_client_secret'
DEFAULT_LTI_ENDPOINT
=
'correct_lti_endpoint'
DEFAULT_LTI_ADDRESS
=
'http://127.0.0.1:{port}/'
def
do_GET
(
self
):
"""
Handle a GET request from the client and sends response back.
Used for checking LTI Provider started correctly.
"""
self
.
send_response
(
200
,
'This is LTI Provider.'
,
{
'Content-type'
:
'text/plain'
})
def
do_POST
(
self
):
"""
Handle a POST request from the client and sends response back.
"""
if
'grade'
in
self
.
path
and
self
.
_send_graded_result
()
.
status_code
==
200
:
status_message
=
'LTI consumer (edX) responded with XML content:<br>'
+
self
.
server
.
grade_data
[
'TC answer'
]
content
=
self
.
_create_content
(
status_message
)
self
.
send_response
(
200
,
content
)
# Respond to request with correct lti endpoint
elif
self
.
_is_correct_lti_request
():
params
=
{
k
:
v
for
k
,
v
in
self
.
post_dict
.
items
()
if
k
!=
'oauth_signature'
}
if
self
.
_check_oauth_signature
(
params
,
self
.
post_dict
.
get
(
'oauth_signature'
,
""
)):
status_message
=
"This is LTI tool. Success."
# Set data for grades what need to be stored as server data
if
'lis_outcome_service_url'
in
self
.
post_dict
:
self
.
server
.
grade_data
=
{
'callback_url'
:
self
.
post_dict
.
get
(
'lis_outcome_service_url'
),
'sourcedId'
:
self
.
post_dict
.
get
(
'lis_result_sourcedid'
)
}
submit_url
=
'//{}:{}'
.
format
(
*
self
.
server
.
server_address
)
content
=
self
.
_create_content
(
status_message
,
submit_url
)
self
.
send_response
(
200
,
content
)
else
:
content
=
self
.
_create_content
(
"Wrong LTI signature"
)
self
.
send_response
(
200
,
content
)
else
:
content
=
self
.
_create_content
(
"Invalid request URL"
)
self
.
send_response
(
500
,
content
)
def
_send_graded_result
(
self
):
"""
Send grade request.
"""
values
=
{
'textString'
:
0.5
,
'sourcedId'
:
self
.
server
.
grade_data
[
'sourcedId'
],
'imsx_messageIdentifier'
:
uuid4
()
.
hex
,
}
payload
=
textwrap
.
dedent
(
"""
<?xml version = "1.0" encoding = "UTF-8"?>
<imsx_POXEnvelopeRequest xmlns="http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0">
<imsx_POXHeader>
<imsx_POXRequestHeaderInfo>
<imsx_version>V1.0</imsx_version>
<imsx_messageIdentifier>{imsx_messageIdentifier}</imsx_messageIdentifier> /
</imsx_POXRequestHeaderInfo>
</imsx_POXHeader>
<imsx_POXBody>
<replaceResultRequest>
<resultRecord>
<sourcedGUID>
<sourcedId>{sourcedId}</sourcedId>
</sourcedGUID>
<result>
<resultScore>
<language>en-us</language>
<textString>{textString}</textString>
</resultScore>
</result>
</resultRecord>
</replaceResultRequest>
</imsx_POXBody>
</imsx_POXEnvelopeRequest>
"""
)
data
=
payload
.
format
(
**
values
)
url
=
self
.
server
.
grade_data
[
'callback_url'
]
headers
=
{
'Content-Type'
:
'application/xml'
,
'X-Requested-With'
:
'XMLHttpRequest'
,
'Authorization'
:
self
.
_oauth_sign
(
url
,
data
)
}
# Send request ignoring verifirecation of SSL certificate
response
=
requests
.
post
(
url
,
data
=
data
,
headers
=
headers
,
verify
=
False
)
self
.
server
.
grade_data
[
'TC answer'
]
=
response
.
content
return
response
def
_create_content
(
self
,
response_text
,
submit_url
=
None
):
"""
Return content (str) either for launch, send grade or get result from TC.
"""
if
submit_url
:
submit_form
=
textwrap
.
dedent
(
"""
<form action="{}/grade" method="post">
<input type="submit" name="submit-button" value="Submit">
</form>
"""
)
.
format
(
submit_url
)
else
:
submit_form
=
''
# Show roles only for LTI launch.
if
self
.
post_dict
.
get
(
'roles'
):
role
=
'<h5>Role: {}</h5>'
.
format
(
self
.
post_dict
[
'roles'
])
else
:
role
=
''
response_str
=
textwrap
.
dedent
(
"""
<html>
<head>
<title>TEST TITLE</title>
</head>
<body>
<div>
<h2>IFrame loaded</h2>
<h3>Server response is:</h3>
<h3 class="result">{response}</h3>
{role}
</div>
{submit_form}
</body>
</html>
"""
)
.
format
(
response
=
response_text
,
role
=
role
,
submit_form
=
submit_form
)
# Currently LTI module doublequotes the lis_result_sourcedid parameter.
# Unquote response two times.
return
urllib
.
unquote
(
urllib
.
unquote
(
response_str
))
def
_is_correct_lti_request
(
self
):
"""
Return a boolean indicating whether the URL path is a valid LTI end-point.
"""
lti_endpoint
=
self
.
server
.
config
.
get
(
'lti_endpoint'
,
self
.
DEFAULT_LTI_ENDPOINT
)
return
lti_endpoint
in
self
.
path
def
_oauth_sign
(
self
,
url
,
body
):
"""
Signs request and returns signed body and headers.
"""
client_key
=
self
.
server
.
config
.
get
(
'client_key'
,
self
.
DEFAULT_CLIENT_KEY
)
client_secret
=
self
.
server
.
config
.
get
(
'client_secret'
,
self
.
DEFAULT_CLIENT_SECRET
)
client
=
oauthlib
.
oauth1
.
Client
(
client_key
=
unicode
(
client_key
),
client_secret
=
unicode
(
client_secret
)
)
headers
=
{
# This is needed for body encoding:
'Content-Type'
:
'application/x-www-form-urlencoded'
,
}
# Calculate and encode body hash. See http://oauth.googlecode.com/svn/spec/ext/body_hash/1.0/oauth-bodyhash.html
sha1
=
hashlib
.
sha1
()
sha1
.
update
(
body
)
oauth_body_hash
=
base64
.
b64encode
(
sha1
.
digest
())
__
,
headers
,
__
=
client
.
sign
(
unicode
(
url
.
strip
()),
http_method
=
u'POST'
,
body
=
{
u'oauth_body_hash'
:
oauth_body_hash
},
headers
=
headers
)
headers
=
headers
[
'Authorization'
]
+
', oauth_body_hash="{}"'
.
format
(
oauth_body_hash
)
return
headers
def
_check_oauth_signature
(
self
,
params
,
client_signature
):
"""
Checks oauth signature from client.
`params` are params from post request except signature,
`client_signature` is signature from request.
Builds mocked request and verifies hmac-sha1 signing::
1. builds string to sign from `params`, `url` and `http_method`.
2. signs it with `client_secret` which comes from server settings.
3. obtains signature after sign and then compares it with request.signature
(request signature comes form client in request)
Returns `True` if signatures are correct, otherwise `False`.
"""
client_secret
=
unicode
(
self
.
server
.
config
.
get
(
'client_secret'
,
self
.
DEFAULT_CLIENT_SECRET
))
port
=
self
.
server
.
server_address
[
1
]
lti_base
=
self
.
DEFAULT_LTI_ADDRESS
.
format
(
port
=
port
)
lti_endpoint
=
self
.
server
.
config
.
get
(
'lti_endpoint'
,
self
.
DEFAULT_LTI_ENDPOINT
)
url
=
lti_base
+
lti_endpoint
request
=
mock
.
Mock
()
request
.
params
=
[(
unicode
(
k
),
unicode
(
v
))
for
k
,
v
in
params
.
items
()]
request
.
uri
=
unicode
(
url
)
request
.
http_method
=
u'POST'
request
.
signature
=
unicode
(
client_signature
)
return
signature
.
verify_hmac_sha1
(
request
,
client_secret
)
class
StubLtiService
(
StubHttpService
):
"""
A stub LTI provider server that responds
to POST and GET requests to localhost.
"""
HANDLER_CLASS
=
StubLtiHandler
common/djangoapps/terrain/stubs/start.py
View file @
79ffcb7e
...
...
@@ -8,6 +8,7 @@ from .comments import StubCommentsService
from
.xqueue
import
StubXQueueService
from
.youtube
import
StubYouTubeService
from
.ora
import
StubOraService
from
.lti
import
StubLtiService
USAGE
=
"USAGE: python -m stubs.start SERVICE_NAME PORT_NUM [CONFIG_KEY=CONFIG_VAL, ...]"
...
...
@@ -17,6 +18,7 @@ SERVICES = {
'youtube'
:
StubYouTubeService
,
'ora'
:
StubOraService
,
'comments'
:
StubCommentsService
,
'lti'
:
StubLtiService
,
}
# Log to stdout, including debug messages
...
...
common/djangoapps/terrain/stubs/tests/test_lti_stub.py
0 → 100644
View file @
79ffcb7e
"""
Unit tests for stub LTI implementation.
"""
from
mock
import
Mock
,
patch
import
unittest
import
urllib2
import
requests
from
terrain.stubs.lti
import
StubLtiService
class
StubLtiServiceTest
(
unittest
.
TestCase
):
"""
A stub of the LTI provider that listens on a local
port and responds with pre-defined grade messages.
Used for lettuce BDD tests in lms/courseware/features/lti.feature
"""
def
setUp
(
self
):
self
.
server
=
StubLtiService
()
self
.
uri
=
'http://127.0.0.1:{}/'
.
format
(
self
.
server
.
port
)
self
.
launch_uri
=
self
.
uri
+
'correct_lti_endpoint'
self
.
addCleanup
(
self
.
server
.
shutdown
)
self
.
payload
=
{
'user_id'
:
'default_user_id'
,
'roles'
:
'Student'
,
'oauth_nonce'
:
''
,
'oauth_timestamp'
:
''
,
'oauth_consumer_key'
:
'test_client_key'
,
'lti_version'
:
'LTI-1p0'
,
'oauth_signature_method'
:
'HMAC-SHA1'
,
'oauth_version'
:
'1.0'
,
'oauth_signature'
:
''
,
'lti_message_type'
:
'basic-lti-launch-request'
,
'oauth_callback'
:
'about:blank'
,
'launch_presentation_return_url'
:
''
,
'lis_outcome_service_url'
:
'http://localhost:8001/test_callback'
,
'lis_result_sourcedid'
:
''
,
'resource_link_id'
:
''
,
}
def
test_invalid_request_url
(
self
):
"""
Tests that LTI server processes request with right program path but with wrong header.
"""
self
.
launch_uri
=
self
.
uri
+
'wrong_lti_endpoint'
response
=
requests
.
post
(
self
.
launch_uri
,
data
=
self
.
payload
)
self
.
assertIn
(
'Invalid request URL'
,
response
.
content
)
def
test_wrong_signature
(
self
):
"""
Tests that LTI server processes request with right program
path and responses with incorrect signature.
"""
response
=
requests
.
post
(
self
.
launch_uri
,
data
=
self
.
payload
)
self
.
assertIn
(
'Wrong LTI signature'
,
response
.
content
)
@patch
(
'terrain.stubs.lti.signature.verify_hmac_sha1'
,
return_value
=
True
)
def
test_success_response_launch_lti
(
self
,
check_oauth
):
"""
Success lti launch.
"""
response
=
requests
.
post
(
self
.
launch_uri
,
data
=
self
.
payload
)
self
.
assertIn
(
'This is LTI tool. Success.'
,
response
.
content
)
@patch
(
'terrain.stubs.lti.signature.verify_hmac_sha1'
,
return_value
=
True
)
def
test_send_graded_result
(
self
,
verify_hmac
):
response
=
requests
.
post
(
self
.
launch_uri
,
data
=
self
.
payload
)
self
.
assertIn
(
'This is LTI tool. Success.'
,
response
.
content
)
grade_uri
=
self
.
uri
+
'grade'
with
patch
(
'terrain.stubs.lti.requests.post'
)
as
mocked_post
:
mocked_post
.
return_value
=
Mock
(
content
=
'Test response'
,
status_code
=
200
)
response
=
urllib2
.
urlopen
(
grade_uri
,
data
=
''
)
self
.
assertIn
(
'Test response'
,
response
.
read
())
common/lib/xmodule/xmodule/lti_module.py
View file @
79ffcb7e
...
...
@@ -289,7 +289,7 @@ class LTIModule(LTIFields, XModule):
While testing locally and on Jenkins, mock_lti_server use http.referer
to obtain scheme, so it is ok to have http(s) anyway.
"""
scheme
=
'http'
if
'sandbox'
in
self
.
system
.
hostname
else
'https'
scheme
=
'http'
if
'sandbox'
in
self
.
system
.
hostname
or
self
.
system
.
debug
else
'https'
uri
=
'{scheme}://{host}{path}'
.
format
(
scheme
=
scheme
,
host
=
self
.
system
.
hostname
,
...
...
@@ -325,7 +325,11 @@ class LTIModule(LTIFields, XModule):
the link being launched.
lti_id should be context_id by meaning.
"""
return
u':'
.
join
(
urllib
.
quote
(
i
)
for
i
in
(
self
.
lti_id
,
self
.
get_resource_link_id
(),
self
.
get_user_id
()))
return
"{id}:{resource_link}:{user_id}"
.
format
(
id
=
urllib
.
quote
(
self
.
lti_id
),
resource_link
=
urllib
.
quote
(
self
.
get_resource_link_id
()),
user_id
=
urllib
.
quote
(
self
.
get_user_id
())
)
def
get_course
(
self
):
"""
...
...
common/lib/xmodule/xmodule/tests/test_lti_unit.py
View file @
79ffcb7e
...
...
@@ -246,7 +246,8 @@ class LTIModuleTest(LogicTest):
self
.
assertEqual
(
real_user_id
,
expected_user_id
)
def
test_outcome_service_url
(
self
):
expected_outcome_service_url
=
'https://{host}{path}'
.
format
(
expected_outcome_service_url
=
'{scheme}://{host}{path}'
.
format
(
scheme
=
'http'
if
self
.
xmodule
.
runtime
.
debug
else
'https'
,
host
=
self
.
xmodule
.
runtime
.
hostname
,
path
=
self
.
xmodule
.
runtime
.
handler_url
(
self
.
xmodule
,
'grade_handler'
,
thirdparty
=
True
)
.
rstrip
(
'/?'
)
)
...
...
lms/djangoapps/courseware/features/lti.py
View file @
79ffcb7e
...
...
@@ -9,6 +9,7 @@ from splinter.exceptions import ElementDoesNotExist
from
django.contrib.auth.models
import
User
from
django.core.urlresolvers
import
reverse
from
django.conf
import
settings
from
lettuce
import
world
,
step
from
lettuce.django
import
django_url
...
...
@@ -81,10 +82,7 @@ def incorrect_lti_is_rendered(_step):
def
set_correct_lti_passport
(
_step
,
user
=
'Instructor'
):
coursenum
=
'test_course'
metadata
=
{
'lti_passports'
:
[
"correct_lti_id:{}:{}"
.
format
(
world
.
lti_server
.
oauth_settings
[
'client_key'
],
world
.
lti_server
.
oauth_settings
[
'client_secret'
]
)]
'lti_passports'
:
[
"correct_lti_id:test_client_key:test_client_secret"
]
}
i_am_registered_for_the_course
(
coursenum
,
metadata
,
user
)
...
...
@@ -94,10 +92,7 @@ def set_correct_lti_passport(_step, user='Instructor'):
def
set_incorrect_lti_passport
(
_step
):
coursenum
=
'test_course'
metadata
=
{
'lti_passports'
:
[
"test_lti_id:{}:{}"
.
format
(
world
.
lti_server
.
oauth_settings
[
'client_key'
],
"incorrect_lti_secret_key"
)]
'lti_passports'
:
[
"test_lti_id:test_client_key:incorrect_lti_secret_key"
]
}
i_am_registered_for_the_course
(
coursenum
,
metadata
)
...
...
@@ -108,7 +103,7 @@ def add_correct_lti_to_course(_step, fields):
category
=
'lti'
metadata
=
{
'lti_id'
:
'correct_lti_id'
,
'launch_url'
:
world
.
lti_server
.
oauth_settings
[
'lti_base'
]
+
world
.
lti_server
.
oauth_settings
[
'lti_endpoint'
]
,
'launch_url'
:
'http://127.0.0.1:{}/correct_lti_endpoint'
.
format
(
settings
.
LTI_PORT
)
,
}
if
fields
.
strip
()
==
'incorrect_lti_id'
:
# incorrect fields
...
...
lms/djangoapps/courseware/features/lti_setup.py
deleted
100644 → 0
View file @
15fc640e
#pylint: disable=C0111
#pylint: disable=W0621
from
courseware.mock_lti_server.mock_lti_server
import
MockLTIServer
from
lettuce
import
before
,
after
,
world
from
django.conf
import
settings
import
threading
from
logging
import
getLogger
logger
=
getLogger
(
__name__
)
@before.all
def
setup_mock_lti_server
():
server_host
=
'127.0.0.1'
server_port
=
settings
.
LTI_PORT
address
=
(
server_host
,
server_port
)
# Create the mock server instance
server
=
MockLTIServer
(
address
)
logger
.
debug
(
"LTI server started at {} port"
.
format
(
str
(
server_port
)))
# Start the server running in a separate daemon thread
# Because the thread is a daemon, it will terminate
# when the main thread terminates.
server_thread
=
threading
.
Thread
(
target
=
server
.
serve_forever
)
server_thread
.
daemon
=
True
server_thread
.
start
()
server
.
server_host
=
server_host
server
.
oauth_settings
=
{
'client_key'
:
'test_client_key'
,
'client_secret'
:
'test_client_secret'
,
'lti_base'
:
'http://{}:{}/'
.
format
(
server_host
,
server_port
),
'lti_endpoint'
:
'correct_lti_endpoint'
}
# For testing on localhost make callback url using referer host.
server
.
real_callback_url_on
=
False
# Store the server instance in lettuce's world
# so that other steps can access it
# (and we can shut it down later)
world
.
lti_server
=
server
@after.all
def
teardown_mock_lti_server
(
total
):
# Stop the LTI server and free up the port
world
.
lti_server
.
shutdown
()
lms/djangoapps/courseware/mock_lti_server/__init__.py
deleted
100644 → 0
View file @
15fc640e
lms/djangoapps/courseware/mock_lti_server/mock_lti_server.py
deleted
100644 → 0
View file @
15fc640e
This diff is collapsed.
Click to expand it.
lms/djangoapps/courseware/mock_lti_server/server_start.py
deleted
100644 → 0
View file @
15fc640e
"""
Mock LTI server for manual testing.
Used for manual testing and testing on sandbox.
"""
from
mock_lti_server
import
MockLTIServer
server_port
=
8034
server_host
=
'localhost'
address
=
(
server_host
,
server_port
)
server
=
MockLTIServer
(
address
)
server
.
oauth_settings
=
{
'client_key'
:
'test_client_key'
,
'client_secret'
:
'test_client_secret'
,
'lti_base'
:
'http://{}:{}/'
.
format
(
server_host
,
server_port
),
'lti_endpoint'
:
'correct_lti_endpoint'
}
server
.
server_host
=
server_host
server
.
server_port
=
server_port
# For testing on localhost make callback url using referer host.
server
.
use_real_callback_url
=
False
try
:
server
.
serve_forever
()
except
KeyboardInterrupt
:
print
(
'^C received, shutting down server'
)
server
.
socket
.
close
()
lms/djangoapps/courseware/mock_lti_server/test_mock_lti_server.py
deleted
100644 → 0
View file @
15fc640e
"""
Test for Mock_LTI_Server
"""
from
mock
import
Mock
import
unittest
import
threading
import
requests
from
mock_lti_server
import
MockLTIServer
class
MockLTIServerTest
(
unittest
.
TestCase
):
'''
A mock version of the LTI provider server that listens on a local
port and responds with pre-defined grade messages.
Used for lettuce BDD tests in lms/courseware/features/lti.feature
'''
def
setUp
(
self
):
# Create the server
server_port
=
8034
server_host
=
'localhost'
address
=
(
server_host
,
server_port
)
self
.
server
=
MockLTIServer
(
address
)
self
.
server
.
oauth_settings
=
{
'client_key'
:
'test_client_key'
,
'client_secret'
:
'test_client_secret'
,
'lti_base'
:
'http://{}:{}/'
.
format
(
server_host
,
server_port
),
'lti_endpoint'
:
'correct_lti_endpoint'
}
self
.
server
.
run_inside_unittest_flag
=
True
#flag for creating right callback_url
self
.
server
.
test_mode
=
True
self
.
server
.
server_host
=
server_host
self
.
server
.
server_port
=
server_port
# Start the server in a separate daemon thread
server_thread
=
threading
.
Thread
(
target
=
self
.
server
.
serve_forever
)
server_thread
.
daemon
=
True
server_thread
.
start
()
def
tearDown
(
self
):
# Stop the server, freeing up the port
self
.
server
.
shutdown
()
def
test_wrong_header
(
self
):
"""
Tests that LTI server processes request with right program path but with wrong header.
"""
#wrong number of params and no signature
payload
=
{
'user_id'
:
'default_user_id'
,
'roles'
:
'Student'
,
'oauth_nonce'
:
''
,
'oauth_timestamp'
:
''
,
}
uri
=
self
.
server
.
oauth_settings
[
'lti_base'
]
+
self
.
server
.
oauth_settings
[
'lti_endpoint'
]
headers
=
{
'referer'
:
'http://localhost:8000/'
}
response
=
requests
.
post
(
uri
,
data
=
payload
,
headers
=
headers
)
self
.
assertIn
(
'Wrong LTI signature'
,
response
.
content
)
def
test_wrong_signature
(
self
):
"""
Tests that LTI server processes request with right program
path and responses with incorrect signature.
"""
payload
=
{
'user_id'
:
'default_user_id'
,
'roles'
:
'Student'
,
'oauth_nonce'
:
''
,
'oauth_timestamp'
:
''
,
'oauth_consumer_key'
:
'test_client_key'
,
'lti_version'
:
'LTI-1p0'
,
'oauth_signature_method'
:
'HMAC-SHA1'
,
'oauth_version'
:
'1.0'
,
'oauth_signature'
:
''
,
'lti_message_type'
:
'basic-lti-launch-request'
,
'oauth_callback'
:
'about:blank'
,
'launch_presentation_return_url'
:
''
,
'lis_outcome_service_url'
:
''
,
'lis_result_sourcedid'
:
''
,
'resource_link_id'
:
''
,
}
uri
=
self
.
server
.
oauth_settings
[
'lti_base'
]
+
self
.
server
.
oauth_settings
[
'lti_endpoint'
]
headers
=
{
'referer'
:
'http://localhost:8000/'
}
response
=
requests
.
post
(
uri
,
data
=
payload
,
headers
=
headers
)
self
.
assertIn
(
'Wrong LTI signature'
,
response
.
content
)
def
test_success_response_launch_lti
(
self
):
"""
Success lti launch.
"""
payload
=
{
'user_id'
:
'default_user_id'
,
'roles'
:
'Student'
,
'oauth_nonce'
:
''
,
'oauth_timestamp'
:
''
,
'oauth_consumer_key'
:
'test_client_key'
,
'lti_version'
:
'LTI-1p0'
,
'oauth_signature_method'
:
'HMAC-SHA1'
,
'oauth_version'
:
'1.0'
,
'oauth_signature'
:
''
,
'lti_message_type'
:
'basic-lti-launch-request'
,
'oauth_callback'
:
'about:blank'
,
'launch_presentation_return_url'
:
''
,
'lis_outcome_service_url'
:
''
,
'lis_result_sourcedid'
:
''
,
'resource_link_id'
:
''
,
}
self
.
server
.
check_oauth_signature
=
Mock
(
return_value
=
True
)
uri
=
self
.
server
.
oauth_settings
[
'lti_base'
]
+
self
.
server
.
oauth_settings
[
'lti_endpoint'
]
headers
=
{
'referer'
:
'http://localhost:8000/'
}
response
=
requests
.
post
(
uri
,
data
=
payload
,
headers
=
headers
)
self
.
assertIn
(
'This is LTI tool. Success.'
,
response
.
content
)
def
test_send_graded_result
(
self
):
payload
=
{
'user_id'
:
'default_user_id'
,
'roles'
:
'Student'
,
'oauth_nonce'
:
''
,
'oauth_timestamp'
:
''
,
'oauth_consumer_key'
:
'test_client_key'
,
'lti_version'
:
'LTI-1p0'
,
'oauth_signature_method'
:
'HMAC-SHA1'
,
'oauth_version'
:
'1.0'
,
'oauth_signature'
:
''
,
'lti_message_type'
:
'basic-lti-launch-request'
,
'oauth_callback'
:
'about:blank'
,
'launch_presentation_return_url'
:
''
,
'lis_outcome_service_url'
:
''
,
'lis_result_sourcedid'
:
''
,
'resource_link_id'
:
''
,
}
self
.
server
.
check_oauth_signature
=
Mock
(
return_value
=
True
)
uri
=
self
.
server
.
oauth_settings
[
'lti_base'
]
+
self
.
server
.
oauth_settings
[
'lti_endpoint'
]
#this is the uri for sending grade from lti
headers
=
{
'referer'
:
'http://localhost:8000/'
}
response
=
requests
.
post
(
uri
,
data
=
payload
,
headers
=
headers
)
self
.
assertIn
(
'This is LTI tool. Success.'
,
response
.
content
)
self
.
server
.
grade_data
[
'TC answer'
]
=
"Test response"
graded_response
=
requests
.
post
(
'http://127.0.0.1:8034/grade'
)
self
.
assertIn
(
'Test response'
,
graded_response
.
content
)
lms/djangoapps/courseware/tests/test_lti_integration.py
View file @
79ffcb7e
...
...
@@ -31,7 +31,11 @@ class TestLTI(BaseTestXmodule):
module_id
=
unicode
(
urllib
.
quote
(
self
.
item_module
.
id
))
user_id
=
unicode
(
self
.
item_descriptor
.
xmodule_runtime
.
anonymous_student_id
)
sourcedId
=
u':'
.
join
(
urllib
.
quote
(
i
)
for
i
in
(
lti_id
,
module_id
,
user_id
))
sourcedId
=
"{id}:{resource_link}:{user_id}"
.
format
(
id
=
urllib
.
quote
(
lti_id
),
resource_link
=
urllib
.
quote
(
module_id
),
user_id
=
urllib
.
quote
(
user_id
)
)
lis_outcome_service_url
=
'https://{host}{path}'
.
format
(
host
=
self
.
item_descriptor
.
xmodule_runtime
.
hostname
,
...
...
lms/envs/acceptance.py
View file @
79ffcb7e
...
...
@@ -13,6 +13,7 @@ from .sauce import *
# You need to start the server in debug mode,
# otherwise the browser will not render the pages correctly
DEBUG
=
True
SITE_NAME
=
'localhost:{}'
.
format
(
LETTUCE_SERVER_PORT
)
# Output Django logs to a file
import
logging
...
...
lms/envs/devstack.py
View file @
79ffcb7e
...
...
@@ -7,6 +7,7 @@ from .aws import * # pylint: disable=wildcard-import, unused-wildcard-import
DEBUG
=
True
USE_I18N
=
True
TEMPLATE_DEBUG
=
True
SITE_NAME
=
'localhost:8000'
# By default don't use a worker, execute tasks as if they were local functions
CELERY_ALWAYS_EAGER
=
True
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment