closes FOO-2368
TEST PLAN:
1) have a user-merge occur x-shard
2) visit an api-gateway mediated pathway
like account notifications
3) correct shard-local user should always be loaded
as auth context
Change-Id: I2dfb86ec35499e9a00ebb8498c4eab9c6c95297e
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/273451
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Reviewed-by: Rob Orton <rob@instructure.com>
QA-Review: Ethan Vizitei <evizitei@instructure.com>
Product-Review: Ethan Vizitei <evizitei@instructure.com>
where suspending means they still show up everywhere, but the user is no longer
allowed to login
closes FOO-2039
test plan:
* have a regular user with an access token, and an active session
* (via a separate session or access token) suspend a pseudonym
via the API as an admin (logins API, set workflow_state to
suspended)
* ensure the original user gets logged out when they refresh, and
that their access token doesn't work
* but as the admin, you can still see the user
Change-Id: Idc0c61bcc244697e3c89b9beb2edfbe2a504b00e
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/269878
Reviewed-by: Simon Williams <simon@instructure.com>
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
QA-Review: Cody Cutrer <cody@instructure.com>
Product-Review: Cody Cutrer <cody@instructure.com>
so that everyone gets the benefits, not just instfs
also include a new circuit breaker so that if consul is unresponsive for more than the
retry interval, we just let failures through quickly for a while
Change-Id: I9ba757c8529c1011ca771612f592f289c6a844b6
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/270789
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Reviewed-by: Simon Williams <simon@instructure.com>
QA-Review: Cody Cutrer <cody@instructure.com>
Product-Review: Cody Cutrer <cody@instructure.com>
fixes INTEROP-6931
most of these changes are based on the guide at
https://github.com/Gusto/apollo-federation-ruby#getting-started
there should be no functional difference to the existing graphql API
endpoint.
test plan:
- regression smoke test on the `/api/graphql` endpoint
- testing the new `/api/graphql/subgraph` endpoint would involve
spinning up an Apollo Gateway server and registering that subgraph
with it, then ensuring you can issue queries, especially ones that
involve extension of the "Course" entity.
Change-Id: Ib4266941d28c5a8dc7c279a2909257d0a330fa7a
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/270041
Reviewed-by: Ethan Vizitei <evizitei@instructure.com>
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
QA-Review: Michael Ziwisky <mziwisky@instructure.com>
Product-Review: Michael Ziwisky <mziwisky@instructure.com>
flag = none
so as to avoid confusing people who actually know what an Identity Token
is, e.g. in the OpenID Connect sense.
test plan:
i think it's sufficient to see tests pass, since this is just a renaming
and slight refactoring. but for a concrete smoke test, follow the same
test plan described in the commit message of commit 4826df723d, only
instead of hitting the endpoint `/api/v1/inst_ids?unencrypted=1`, hit
`/api/v1/inst_access_tokens?unencrypted=1`
Change-Id: Ie7b646ff80129c094aa44ed46999321bfbcf2851
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/269690
Reviewed-by: Ethan Vizitei <evizitei@instructure.com>
Reviewed-by: Weston Dransfield <wdransfield@instructure.com>
Reviewed-by: Ryan Hawkins <ryan.hawkins@instructure.com>
QA-Review: Ryan Hawkins <ryan.hawkins@instructure.com>
Product-Review: Michael Ziwisky <mziwisky@instructure.com>
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
fixes INTEROP-6911
also fixes some specs in jwts_controller_spec -- the intention was to
use a services JWT to try to generate a services JWT and see it fail.
it did fail, but for the wrong reason -- the spec didn't actually pass a
services JWT in the auth header, it passed something unrecognizable.
the fix here is to make it a proper JWT, and add a more specific
assertion so we're confident that we're exercising the right code path.
test plan:
- see test plan for commit c44b1844f7 for how to generate RSA keypairs
and configure InstID to use them
- get an API access token by following
https://canvas.instructure.com/doc/api/file.oauth.html#manual-token-generation
- use that token to generate an unencrypted InstID token like:
$ curl 'http://localhost:3000/api/v1/inst_ids?unencrypted=1' \
-X POST \
-H 'Authorization: Bearer <access_token>'
- now use the returned token to hit some other endpoint, e.g.:
$ curl http://localhost:3000/api/v1/users/self \
-X GET \
-H 'Authorization: Bearer <InstID_token>'
Change-Id: Ie6c01dd12d98f68c138da1960b87f91e42b8b04d
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/269345
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Reviewed-by: Ethan Vizitei <evizitei@instructure.com>
QA-Review: Michael Ziwisky <mziwisky@instructure.com>
Product-Review: Michael Ziwisky <mziwisky@instructure.com>
when trying to masquerade as them
Change-Id: I245808ff2665c695ec66ab3301d59b62b803a9fd
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/263658
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Reviewed-by: Simon Williams <simon@instructure.com>
QA-Review: Simon Williams <simon@instructure.com>
Product-Review: Simon Williams <simon@instructure.com>
This reverts commit e09ffa5a1a.
it _was_ a good idea
Change-Id: I39d62cbdae53dde76bd8982b76fdc336b64f9a4d
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/259711
Reviewed-by: Keith Garner <kgarner@instructure.com>
Reviewed-by: Ben Rinaca <brinaca@instructure.com>
QA-Review: Rob Orton <rob@instructure.com>
Product-Review: Ben Rinaca <brinaca@instructure.com>
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
test plan
- open canvas
- use query string ?courseAmin=true
- it should display a warning
Change-Id: Iaf59942c3a81e3f234dc5c2694c22330105cbb87
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/259963
QA-Review: Rob Orton <rob@instructure.com>
Product-Review: Rob Orton <rob@instructure.com>
Reviewed-by: Gary Mei <gmei@instructure.com>
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
refs FOO-1649
Pull out middleware for request
context to a gem so that other
engines in canvas can use the
generator to look at the current
request for standard attributes
in the same way.
TEST PLAN:
1) requests should keep on getting context ids
2) sessions should keep getting added to the cookie jar
Change-Id: I9245491f722ac29c9544623ee14e0771ae248cd4
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/259609
Reviewed-by: Cody Cutrer <cody@instructure.com>
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
QA-Review: Ethan Vizitei <evizitei@instructure.com>
Product-Review: Ethan Vizitei <evizitei@instructure.com>
refs FOO-1125
flag=non
TEST PLAN:
1) stats for things like ImperiumTimeouts should still
end up in datadog
2) sentry errors for the target error types should disappear
Change-Id: I6e97c04e3f6fcc3545b10418511934c89f20a419
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/251536
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Reviewed-by: Simon Williams <simon@instructure.com>
QA-Review: Simon Williams <simon@instructure.com>
Product-Review: Simon Williams <simon@instructure.com>
The two occurrences I found were `prepend_around_action`,
which prepends a callback around actions. The two actions
associated with this callback are no longer being used. Therefore
there was no evidence pointing to a callsite for
AuthenticationMethods#load_pseudonym_from_policy.
The files controller appears to call a new action `api_create`
vs the old :create. This handles the policy via its new code-path.
The content imports controller referenced a "migrate_content_upload"
action that is no longer in commission.
It appears the original intent behind the usage was for flash file
uploads. Usage existed in local_storage.rb & s3_storage.rb
[skip-cache]
refs FOO-1085
refs https://gerrit.instructure.com/c/migration_tool/+/251170
flag = none
test plan:
- specs pass
- bulk course migrations still work
Change-Id: Iedd0ff846e13840ce1c33f4da27b29ddd30f87ae
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/250591
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Reviewed-by: Simon Williams <simon@instructure.com>
QA-Review: August Thornton <august@instructure.com>
Product-Review: August Thornton <august@instructure.com>
closes FOO-1085
flag = none
test plan:
1. $specs_pass == true
Change-Id: I4c01e9f073d0d23030ac8703a0822081cc2bd414
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/250905
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Reviewed-by: Simon Williams <simon@instructure.com>
QA-Review: August Thornton <august@instructure.com>
Product-Review: August Thornton <august@instructure.com>
Access tokens now support baking in a masquerading user, which interacts
strangely with trying to manually pass a masquerade user_id param in an
api request.
Clarify that if the users match, things work, if they don't match, we
error.
fixes FOO-1069
flag=none
test plan:
- while masquerading, create an access token
- it should work
- make an api request including as_user_id as the user you are
masquerading as
- it should work
- make an api request including as_user_id as a different user
- it should error
Change-Id: Ia1d61a977d467d1fc0eca3db4931fdfda2d05618
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/249955
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Reviewed-by: Cody Cutrer <cody@instructure.com>
QA-Review: August Thornton <august@instructure.com>
Product-Review: August Thornton <august@instructure.com>
refs FOO-853
TEST PLAN:
1) login to canvas
2) it should not fail to log you in.
3) click around
4) you should not be abruptbly logged out
Change-Id: I8e71fec8831f8978e011e7d86d81650e01c15ac4
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/245421
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Reviewed-by: Simon Williams <simon@instructure.com>
QA-Review: Ethan Vizitei <evizitei@instructure.com>
Product-Review: Ethan Vizitei <evizitei@instructure.com>
access tokens cannot be created when masquerading a user on purpose,
because this would allow a user to get the real token and use it when
permissions for the user could change in the future. A commit was made
ee50eec4bd to create the access tokens
used when doing an lti launch on the real_user instead of the user, but
this breaks some tools that are not handling all the masquerade data.
c94b34348a reverted that change to create
them on the user again.
This commit is adding a column to access_token so we can audit usage of
the tokens created from an LTI launch. When a token is created while
masquerading we add the real_user_id to the token and make the token
expire in one hour.
test plan
- masquerade as a user
- launch an lti_tool that creates an access token
- the tool should see the end users token
- in a console verify the token is set to expire in an hour
- verify that real_user_id is used on the token
- the token should expire within an hour
fixes KNO-464
flag=none
Change-Id: I1f8913fc536f4e2c8539551efed69b27fbdb6b1a
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/236443
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Reviewed-by: Davis Hyer <dhyer@instructure.com>
QA-Review: Davis Hyer <dhyer@instructure.com>
Product-Review: Davis Hyer <dhyer@instructure.com>
closes PLAT-5154
flag = developer_key_support_includes
Test Plan:
- Create or Edit an API Developer Key
- set the flag on the Developer Key to allow includes 'allow_includes'
- Use an API tool to authenticate and hit an API endpoint and include
'include' parameters (see Clint or Weston on how to authenticate)
ex: /api/v1/courses/:id?include[]=permissions&include[]=concluded
- verify that in the response json, the included data exists
Change-Id: I268f59287505151b9e3cb0c0024a4f837e33412e
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/222143
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Product-Review: Clint Furse <cfurse@instructure.com>
Reviewed-by: Weston Dransfield <wdransfield@instructure.com>
QA-Review: Weston Dransfield <wdransfield@instructure.com>
closes GQL-84
since scopes can't be configured for graphql yet, this effectively
disables the graphql api for scoped tokens
Test plan:
* have a scoped token
* make sure graphql requests fail
Change-Id: Ief07db829b19c2942521689a33655c982ae0c614
Reviewed-on: https://gerrit.instructure.com/207292
Tested-by: Jenkins
Reviewed-by: Brent Burgoyne <bburgoyne@instructure.com>
QA-Review: Brent Burgoyne <bburgoyne@instructure.com>
Product-Review: Brent Burgoyne <bburgoyne@instructure.com>
Fixes: CORE-2959
Test plan:
* Follow the repro steps in the linked jira ticket
* you should not be able to reproduce it
Change-Id: I60799db6f2e573411ef5c7daa9b4620e58a2523e
Reviewed-on: https://gerrit.instructure.com/195411
Tested-by: Jenkins
Reviewed-by: Brent Burgoyne <bburgoyne@instructure.com>
QA-Review: Brent Burgoyne <bburgoyne@instructure.com>
Product-Review: Brent Burgoyne <bburgoyne@instructure.com>
fixes PLAT-4388
test plan:
create a Dev key that requires scopes
create an access token with that dev key
attempt to destroy that dev key using the DELETE:login/oauth2/token
endpoint
it should let you destroy it
Change-Id: Ie8039072513eb2264ca88e83656decfeab64be30
Reviewed-on: https://gerrit.instructure.com/190513
Tested-by: Jenkins
Reviewed-by: Marc Phillips <mphillips@instructure.com>
QA-Review: Weston Dransfield <wdransfield@instructure.com>
Product-Review: Nathan Mills <nathanm@instructure.com>
test plan:
* configure the root account in rails console with:
Account.default.tap do |a|
a.settings[:require_confirmed_email] = true
a.save!
end
* create a new user and add them to a course but don't
confirm their email - just register them in console:
User.find(X).register
* confirm that the new user cannot do the following
(and receives a message to finish registering):
- launch LTI tools
- publish a course
- access eportfolios
closes #CORE-2774 #CORE-2775 #CORE-2776
Change-Id: I97ad90e714f7d191e84674924ceb4c9a8b87a00b
Reviewed-on: https://gerrit.instructure.com/189946
Tested-by: Jenkins
Reviewed-by: Rob Orton <rob@instructure.com>
QA-Review: Rob Orton <rob@instructure.com>
Product-Review: Rob Orton <rob@instructure.com>
test plan:
- add a file to a course that is not published
- link to the file from a page that is published
- masquerade as a student (or test student) and view the page
- click the link to the file
- you will see a page that says the file is locked
- click in the footer to stop masquerading
- the masquerading/student view banner should disappear
and a link to the file should appear
fixes ADMIN-2326
Change-Id: I59c0bc1291837aa6196f8a04f5242d495f872ca8
Reviewed-on: https://gerrit.instructure.com/177205
Tested-by: Jenkins
Reviewed-by: Mysti Sadler <mysti@instructure.com>
QA-Review: Anju Reddy <areddy@instructure.com>
Product-Review: Jeremy Stanley <jeremy@instructure.com>
This does not remove the old dead code, just all the places
that checked to see if it was enabled.
closes PLAT-3752
Test Plan:
- Regression test on the developer keys page
- Also need to check that creating an oauth 2 token is
not broken (using client credentials)
Change-Id: I89983922a894ff7f20e86c034728d55284c8c668
Reviewed-on: https://gerrit.instructure.com/168271
Tested-by: Jenkins
Reviewed-by: Weston Dransfield <wdransfield@instructure.com>
QA-Review: Weston Dransfield <wdransfield@instructure.com>
Product-Review: Marc Phillips <mphillips@instructure.com>
- fix id transposition for unfavoriting
- don't keep the favorite courses cache after saving
- reload the user if they're a shadow so we get the primary
updated_at
closes #ADMIN-1467
Change-Id: I97ff4937bdb009e2576e2201a8a01f40a8668bf4
Reviewed-on: https://gerrit.instructure.com/165076
Reviewed-by: Cody Cutrer <cody@instructure.com>
Tested-by: Jenkins
QA-Review: Jeremy Putnam <jeremyp@instructure.com>
Product-Review: James Williams <jamesw@instructure.com>
scores are shown, not grades.
closes: GRADE-1457
Test Plan:
Note: the new endpoint is of the form:
/submissions/:submssion_id/comments.pdf
- given a submission with many submission comments
- given SpeedGrader page
- when the link for Export Submission Comments is clicked
- then a pdf of submission comments is displayed
- given a user that cannot Read Grades
- when accessing a pdf url
- then the response is unauthorized
- given a user that cannot Read Comments
- when accessing a pdf url
- then the response is unauthorized
- given an assignment that is anonymous
- when on the SpeedGrader page
- then the link for Export Submission Comments is not displayed
- then hitting the pdf url returns unauthorized
- also test with group assignments
Change-Id: I529fe170a3de6f8a1c8f28ae20a3346c5676a97e
Reviewed-on: https://gerrit.instructure.com/158888
Reviewed-by: Keith T. Garner <kgarner@instructure.com>
QA-Review: Derek Bender <djbender@instructure.com>
Product-Review: Sidharth Oberoi <soberoi@instructure.com>
Tested-by: Jenkins
yes, eager load. this query is executed on _every_ _single_
_request_, so if we can do one query instead of two, it can
have a potentially decent impact on queries-per-second. and it's
a belong_to, so we're not bring duplicated data off the wire,
either
Change-Id: I1f0b0fffe762c1c5523e78a16a36cc31150279ea
Reviewed-on: https://gerrit.instructure.com/161078
Reviewed-by: James Williams <jamesw@instructure.com>
Tested-by: Jenkins
Product-Review: Cody Cutrer <cody@instructure.com>
QA-Review: Cody Cutrer <cody@instructure.com>
Closes PLAT-3466
Test Plan:
- Enable the single "Developer key management and scoping"
feature flag in a root account.
- Verify dev key management and scoping work in that account.
- Disable the "Developer key management and scoping" feature
flag.
- Verify developer key scoping and management do not apply
in that account.
Change-Id: Ic9a1a639191b754a3b0fc808739dd4f3a9cadc43
Reviewed-on: https://gerrit.instructure.com/153141
Tested-by: Jenkins
Reviewed-by: Marc Alan Phillips <mphillips@instructure.com>
QA-Review: August Thornton <august@instructure.com>
Product-Review: Jesse Poulos <jpoulos@instructure.com>
fixes PLAT-3176
fixes PLAT-3179
fixes PLAT-3181
fixes PLAT-3177
Test plan:
* Create a DeveloperKey
* Create an AccessToken
* Ensure that everything can be accessed as normal
* Set require_scopes to true on the DeveloperKey
* Ensure that nothing can be accessed
* Add some scopes to the AccessToken from the list of available scopes
TokenScopes::SCOPES
* Ensure that the endpoints associated with those requests work but that
others don't
* Ensure that HEAD requests work for GET endpoints
* Ensure all api endpoints behave normally when scopes are not turned on
for developer key
Change-Id: I0e7c1758ae2d51743490f243cfa21714255c8109
Reviewed-on: https://gerrit.instructure.com/143026
Tested-by: Jenkins
Reviewed-by: Simon Williams <simon@instructure.com>
Reviewed-by: Nathan Mills <nathanm@instructure.com>
QA-Review: August Thornton <august@instructure.com>
Reviewed-by: Rob Orton <rob@instructure.com>
Product-Review: Karl Lloyd <karl@instructure.com>
fixes PLAT-3018
test plan:
- create a developer key at a sub account
- use it to get an access token for a user
- use the access token to access resources in the sub account, i.e
a course via the api
+ it should allow you to get access resources in the sub account
- use it to access a user via the api
+ it shouldn't allow you to access a user
- use it to access a resource in a different sub account
+ it shouldn't allow you to access resources in other sub accounts
Change-Id: Ie6e18399770e2dd3590be2c8407cdd5c3a230e69
Reviewed-on: https://gerrit.instructure.com/139268
Tested-by: Jenkins
Reviewed-by: Andrew Butterfield <abutterfield@instructure.com>
QA-Review: August Thornton <august@instructure.com>
Product-Review: Nathan Mills <nathanm@instructure.com>
just pretend it's empty. the caller should be responsible for dealing
with missing consul data as appropriate
Change-Id: I2c37d33481b55776b14c6c17e109005a75dd600b
Reviewed-on: https://gerrit.instructure.com/125567
Reviewed-by: Tyler Pickett <tpickett@instructure.com>
Reviewed-by: Simon Williams <simon@instructure.com>
Tested-by: Jenkins
Product-Review: Cody Cutrer <cody@instructure.com>
QA-Review: Cody Cutrer <cody@instructure.com>
closes CNVS-35834
* allow specifying tree, service, and cluster for consul stuff
* check multiple consul keys for each setting (cluster, env, region, global)
test plan:
* an existing consul environment still works
Change-Id: I48e8fadeac2e140973bfc4b41c1cfb386532d15c
Reviewed-on: https://gerrit.instructure.com/125271
Tested-by: Jenkins
Reviewed-by: Rob Orton <rob@instructure.com>
Reviewed-by: Simon Williams <simon@instructure.com>
QA-Review: Tucker McKnight <tmcknight@instructure.com>
Product-Review: Cody Cutrer <cody@instructure.com>
Change-Id: Ic6e4f64874021639f5e8950e2fe42f714ae31250
Reviewed-on: https://gerrit.instructure.com/120225
Tested-by: Jenkins
Reviewed-by: Cody Cutrer <cody@instructure.com>
Product-Review: James Williams <jamesw@instructure.com>
QA-Review: James Williams <jamesw@instructure.com>
fixes CNVS-37391
test plan:
* generate an access token
* hit /login/session_token with your access token
* in an incognito window, paste the returned URL in (quickly)
* you should be logged in
Change-Id: Ic12f98156f070e9932d0ff3e12c07b2de9e02db5
Reviewed-on: https://gerrit.instructure.com/115271
Tested-by: Jenkins
Reviewed-by: Tyler Pickett <tpickett@instructure.com>
QA-Review: Jeremy Putnam <jeremyp@instructure.com>
Product-Review: Cody Cutrer <cody@instructure.com>
fixes CNVS-35919
also, prefer SIS pseudonyms over non-SIS pseudonyms from any given
account
test plan:
* have a non-SIS pseudonym and a SIS pseudonym on a user
* do an LTI launch
* the LTI tool should get the info from the SIS pseudonym
Change-Id: I60a3c48a32eae94db93b0e72f1f0f6c5b6a5f5c2
Reviewed-on: https://gerrit.instructure.com/107785
Reviewed-by: Nathan Mills <nathanm@instructure.com>
Reviewed-by: Tyler Pickett <tpickett@instructure.com>
QA-Review: Jeremy Putnam <jeremyp@instructure.com>
Tested-by: Jenkins
Product-Review: Cody Cutrer <cody@instructure.com>
Also, make Consul container accessible from the host.
Fixes: CNVS-35831
Refs: CNVS-34341, CNVS-32864
Test Plan:
- Smoke test RCS and Canvas running together to make sure they still
play nice.
Change-Id: I418d54a176677b1df8ec42a009752807908a847c
Reviewed-on: https://gerrit.instructure.com/99443
Tested-by: Jenkins
Reviewed-by: Simon Williams <simon@instructure.com>
QA-Review: Tucker McKnight <tmcknight@instructure.com>
Product-Review: Tyler Pickett <tpickett@instructure.com>
refs #CNVS-32574
Change-Id: I4e255b989f8ad3fc6ec2f2699d4950dc0e5a419a
Reviewed-on: https://gerrit.instructure.com/99483
Reviewed-by: Cody Cutrer <cody@instructure.com>
Tested-by: Jenkins
Product-Review: James Williams <jamesw@instructure.com>
QA-Review: James Williams <jamesw@instructure.com>