Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lmsa 7719 library update #11

Merged
merged 23 commits into from
Sep 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
e7d9a53
LMSA-7719 - dep update and start of rivet 2 upgrade
iudsobiera Aug 30, 2023
b0a86b1
LMSA-7719 - dep updates part 2 and rivet fixing
iudsobiera Sep 1, 2023
b7bb306
LMSA-7719 - removed commented out markup
iudsobiera Sep 1, 2023
2c4978a
LMSA-7719 - use latest parent version
iudsobiera Sep 6, 2023
ac3ee43
LMSA-7719 - changes made after code review
iudsobiera Sep 7, 2023
2139786
LMSA-7719 - changed sr-only to rvt-sr-only
iudsobiera Sep 8, 2023
27cce7f
LMSA-7719 rivet2 tweaks
mrw-iu Sep 12, 2023
86f4936
LMSA-7975 - first pass (not done) at feature
iudsobiera Sep 13, 2023
17e6e6e
LMSA-7719 - put Rivet.init() in correct place
iudsobiera Sep 14, 2023
4be2ffc
Merge branch 'LMSA-7719_library_update' into LMSA-7975_lookup_parent_…
iudsobiera Sep 14, 2023
d0a3562
Merge branch 'main' into LMSA-7719_library_update
iudsobiera Sep 15, 2023
b51aa15
Merge branch 'LMSA-7719_library_update' into LMSA-7975_lookup_parent_…
iudsobiera Sep 15, 2023
bd6dda8
LMSA-7975 - next pass (not done) at feature
iudsobiera Sep 19, 2023
bedcd0c
LMSA-7975 - last pass at feature before code review
iudsobiera Sep 19, 2023
5a7efe5
LMSA-7719 a11y and rivet2 tweaks
mrw-iu Sep 20, 2023
bdc0d45
LMSA-7719 merge conflict resolutin
mrw-iu Sep 20, 2023
64eecd8
LMSA-7719 fix src url for rivet icons
mrw-iu Sep 21, 2023
14ccf23
Merge pull request #14 from indiana-university/LMSA-7719-a11y
mrw-iu Sep 21, 2023
78eac09
LMSA-7975 - changes made after code review
iudsobiera Sep 21, 2023
e87d08e
LMSA-7975 - remove alert
iudsobiera Sep 21, 2023
4a5d433
LMSA-7975 - changed secure level and some text changes
iudsobiera Sep 22, 2023
7d1185e
LMSA-7975 - added focus
iudsobiera Sep 22, 2023
ba41718
Merge pull request #13 from indiana-university/LMSA-7975_lookup_paren…
dsobiera Sep 26, 2023
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
37 changes: 36 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ They can be set in a properties file, or overridden as environment variables.
| Property | Default Value | Description |
|----------------------|-----------------------------|-----------------------------------------------------------|
| `canvas.host` | | Hostname of the Canvas instance |
| `canvas.sso.host` | | Hostname of the Canvas OIDC auth domain |
| `canvas.baseUrl` | https://`${canvas.host}` | Base URL of the Canvas instance |
| `canvas.baseApiUrl` | `${canvas.baseUrl}`/api/v1 | Base URL for the Canvas API |
| `canvas.token` | | Token for access to Canvas instance |
Expand Down Expand Up @@ -122,4 +123,38 @@ Once enabled, the ui will be available at `/api/lti/swagger-ui.html`. There are
that need to be accounted for while using this setup.

This is marked as experimental due to the fact that we aren't running with this option at IU. We are running into CORS
issues when trying to talk to our OAuth2 service via swagger, so we can't verify if it really works or not!
issues when trying to talk to our OAuth2 service via swagger, so we can't verify if it really works or not!

# Crosslister Lookup
The Cross-listing Assistant in Canvas at Indiana University has a secondary launch that brings up a user interface to search for a parent
crosslisted course. This is restricted to administrator users only. Configuration is the same as the crosslister with the exceptions listed below.

## Test a local launch
Startup the application with the `LTI_CLIENTREGISTRATION_DEFAULTCLIENT` value set to `saltire`.
Use an LTI tool consumer launcher, like https://saltire.lti.app/platform.
Default values are fine, with the below exceptions...

In the `Message` section, set the following:
<table>
<tr><th>Property</th><th>Value</th></tr>
<tr><td>Custom parameters</td><td>

```
canvas_user_login_id=johnsmith
instructure_membership_roles=http://purl.imsglobal.org/vocab/lis/v2/institution/person#Administrator
```

</td></tr>
</table>

Use an appropriate `canvas_user_login_id`.

From the `Security Model` section, set the following:
<table>
<tr><th>Property</th><th>Value</th></tr>
<tr><td>LTI version</td><td>1.3.0</td></tr>
<tr><td>Message URL</td><td>http://localhost:8080/app/lookup-launch</td></tr>
<tr><td>Client ID</td><td>dev (or whatever is appropriate based on the record inserted in the database table from above)</td></tr>
<tr><td>Initiate login URL</td><td>http://localhost:8080/lti/login_initiation/lms_lti_crosslisting</td></tr>
<tr><td>Redirection URI(s)</td><td>http://localhost:8080/lti/login</td></tr>
</table>
27 changes: 27 additions & 0 deletions examples/crosslisting-lookup.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"title": "Cross-listing Lookup Parent",
"description": "For Looking up a course's parent course if cross-listed in Canvas.",
"oidc_initiation_url": "http://localhost:8080/lti/login_initiation/lms_lti_crosslisting",
"target_link_uri": "http://localhost:8080/app/lookup-launch",
"extensions": [
{
"domain": "localhost",
"platform": "canvas.instructure.com",
"privacy_level": "public",
"settings": {
"placements": [
{
"enabled": true,
"placement": "account_navigation",
"message_type": "LtiResourceLinkRequest"
}
]
}
}
],
"public_jwk_url": "http://localhost:8080/.well-known/jwks.json",
"custom_fields": {
"instructure_membership_roles": "$com.Instructure.membership.roles",
"canvas_user_login_id": "$Canvas.user.loginId"
}
}
2 changes: 1 addition & 1 deletion examples/crosslisting.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"title": "Cross-listing Assistant",
"description": "For cross-listing and de-cross-listing provisioned sections in Canvas.",
"oidc_initiation_url": "http://localhost:8080/lti/login_initiation/lms_lti_crosslisting",
"target_link_uri": "http://localhost:8080/app/loading",
"target_link_uri": "http://localhost:8080/app/launch",
"extensions": [
{
"domain": "localhost",
Expand Down
22 changes: 11 additions & 11 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.8</version>
<version>2.7.15</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>

Expand Down Expand Up @@ -74,20 +74,20 @@
<java.version>17</java.version>
<jdk.source>17</jdk.source>
<jdk.target>17</jdk.target>
<jquery.version>3.5.1</jquery.version>
<lms-canvas-rivet.version>5.1.8.2_1</lms-canvas-rivet.version>
<lms-embedded-services.version>5.2.3</lms-embedded-services.version>
<lms-team-spring-boot-it12>4.7</lms-team-spring-boot-it12>
<spring-cloud.version>2021.0.5</spring-cloud.version>
<webjars-locator.version>0.46</webjars-locator.version>
<jquery.version>3.7.1</jquery.version>
<lms-canvas-rivet.version>5.2.5.2</lms-canvas-rivet.version>
<lms-embedded-services.version>5.2.16</lms-embedded-services.version>
<lms-team-spring-boot-it12>4.8</lms-team-spring-boot-it12>
<spring-cloud.version>2021.0.8</spring-cloud.version>
<webjars-locator.version>0.47</webjars-locator.version>

<plugins.compiler.version>3.10.1</plugins.compiler.version>
<plugins.gpg.version>3.0.1</plugins.gpg.version>
<plugins.compiler.version>3.11.0</plugins.compiler.version>
<plugins.gpg.version>3.1.0</plugins.gpg.version>
<plugins.javadoc.version>3.5.0</plugins.javadoc.version>
<plugins.license.version>2.0.0</plugins.license.version>
<plugins.nexus-staging.version>1.6.13</plugins.nexus-staging.version>
<plugins.release.version>2.5.3</plugins.release.version>
<plugins.source.version>3.2.1</plugins.source.version>
<plugins.release.version>3.0.0</plugins.release.version>
<plugins.source.version>3.3.0</plugins.source.version>
</properties>

<dependencyManagement>
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/edu/iu/uits/lms/crosslist/CrosslistConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,17 @@ public interface CrosslistConstants {
String ACTION_IMPERSONATE = "impersonate";
String ACTION_END_IMPERSONATE = "end_impersonate";

String LOOKUP_SUCCESS_FOUND_MESSAGE = "Parent course is found";
String LOOKUP_FAILURE_COURSE_NOT_CROSSLISTED_MESSAGE = "This course has not been crosslisted";
String LOOKUP_FAILURE_NOT_FOUND_IN_CANVAS_MESSAGE = "Not found in Canvas";
String LOOKUP_FAILURE_NOT_FOUND_IN_SIS_MESSAGE = "Not found in SIS";

String LOOKUP_SUCCESS_CSS = "rvt-color-green rvt-bg-green-100";
String LOOKUP_FAILURE_CSS = "rvt-orange-green rvt-bg-orange-100";

String LOOKUP_SUCCESS_ICON_NAME = "check";
String LOOKUP_FAILURE_ICON_NAME = "close";

String MODE_EDIT = "editMode";

String STATUS_SUCCESS = "rvt-alert--success";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
import java.util.Date;

@SpringBootApplication
@EnableGlobalErrorHandler(accessDeniedViewName="accessDenied")
@EnableGlobalErrorHandler
@Slf4j
@EnableCookieFilter(ignoredRequestPatterns = "/rest/**")
@EnableRedisConfiguration
Expand Down
38 changes: 32 additions & 6 deletions src/main/java/edu/iu/uits/lms/crosslist/config/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
* #L%
*/

import edu.iu.uits.lms.common.it12logging.LmsFilterSecurityInterceptorObjectPostProcessor;
import edu.iu.uits.lms.common.it12logging.RestSecurityLoggingConfig;
import edu.iu.uits.lms.common.oauth.CustomJwtAuthenticationConverter;
import edu.iu.uits.lms.lti.repository.DefaultInstructorRoleRepository;
import org.springframework.beans.factory.annotation.Autowired;
Expand All @@ -44,6 +46,7 @@
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter;
import uk.ac.ox.ctl.lti13.Lti13Configurer;

import static edu.iu.uits.lms.lti.LTIConstants.BASE_USER_ROLE;
Expand Down Expand Up @@ -72,21 +75,33 @@ protected void configure(HttpSecurity http) throws Exception {
.and()
.authorizeRequests()
.antMatchers(WELL_KNOWN_ALL, "/error").permitAll()
.antMatchers("/**").hasRole(BASE_USER_ROLE);
.antMatchers("/**").hasRole(BASE_USER_ROLE)
.withObjectPostProcessor(new LmsFilterSecurityInterceptorObjectPostProcessor())
.and()
.headers()
.contentSecurityPolicy("style-src 'self' 'unsafe-inline'; form-action 'self'; frame-ancestors 'self' https://*.instructure.com")
.and()
.referrerPolicy(referrer -> referrer
.policy(ReferrerPolicyHeaderWriter.ReferrerPolicy.SAME_ORIGIN));

//Setup the LTI handshake
Lti13Configurer lti13Configurer = new Lti13Configurer()
.grantedAuthoritiesMapper(new CustomRoleMapper(defaultInstructorRoleRepository, toolConfig));

http.apply(lti13Configurer);

http.exceptionHandling().accessDeniedPage("/accessDenied");

//Fallback for everything else
http.requestMatchers().antMatchers("/**")
.and()
.authorizeRequests()
.anyRequest().authenticated();
.anyRequest().authenticated()
.withObjectPostProcessor(new LmsFilterSecurityInterceptorObjectPostProcessor())
.and()
.headers()
.contentSecurityPolicy("style-src 'self' 'unsafe-inline'; form-action 'self'; frame-ancestors 'self' https://*.instructure.com")
.and()
.referrerPolicy(referrer -> referrer
.policy(ReferrerPolicyHeaderWriter.ReferrerPolicy.SAME_ORIGIN));
}

@Override
Expand All @@ -103,7 +118,9 @@ public static class CrosslistRestSecurityConfigurationAdapter extends WebSecurit

@Override
public void configure(HttpSecurity http) throws Exception {
http.requestMatchers().antMatchers("/rest/**")
http
.cors().and()
.requestMatchers().antMatchers("/rest/**")
.and()
.authorizeRequests()
.antMatchers("/rest/**")
Expand All @@ -113,6 +130,8 @@ public void configure(HttpSecurity http) throws Exception {
.and()
.oauth2ResourceServer()
.jwt().jwtAuthenticationConverter(new CustomJwtAuthenticationConverter());

http.apply(new RestSecurityLoggingConfig());
}
}

Expand All @@ -125,7 +144,14 @@ public void configure(HttpSecurity http) throws Exception {
http.requestMatchers().antMatchers("/**")
.and()
.authorizeRequests()
.anyRequest().authenticated();
.anyRequest().authenticated()
.withObjectPostProcessor(new LmsFilterSecurityInterceptorObjectPostProcessor())
.and()
.headers()
.contentSecurityPolicy("style-src 'self' 'unsafe-inline'; form-action 'self'; frame-ancestors 'self' https://*.instructure.com")
.and()
.referrerPolicy(referrer -> referrer
.policy(ReferrerPolicyHeaderWriter.ReferrerPolicy.SAME_ORIGIN));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
*/

import com.fasterxml.jackson.databind.ObjectMapper;
import edu.iu.uits.lms.canvas.helpers.CanvasDateFormatUtil;
import edu.iu.uits.lms.canvas.model.CanvasTerm;
import edu.iu.uits.lms.canvas.model.Course;
import edu.iu.uits.lms.canvas.model.Section;
Expand All @@ -43,6 +44,8 @@
import edu.iu.uits.lms.canvas.services.TermService;
import edu.iu.uits.lms.common.session.CourseSessionService;
import edu.iu.uits.lms.crosslist.CrosslistConstants;
import edu.iu.uits.lms.crosslist.model.FindParentModel;
import edu.iu.uits.lms.crosslist.model.FindParentResult;
import edu.iu.uits.lms.crosslist.model.ImpersonationModel;
import edu.iu.uits.lms.crosslist.model.SectionUIDisplay;
import edu.iu.uits.lms.crosslist.model.SectionWrapper;
Expand Down Expand Up @@ -79,6 +82,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
Expand Down Expand Up @@ -127,11 +131,6 @@ public class CrosslistController extends OidcTokenAwareController {
@Autowired
private CourseSessionService courseSessionService;

@RequestMapping(value = "/accessDenied")
public String accessDenied() {
return "accessDenied";
}

private Course getValidatedCourse(OidcAuthenticationToken token, HttpSession session) {
OidcTokenUtils oidcTokenUtils = new OidcTokenUtils(token);
String courseId = oidcTokenUtils.getCourseId();
Expand All @@ -154,7 +153,7 @@ private Course getValidatedCourse(OidcAuthenticationToken token, HttpSession ses
return currentCourse;
}

@RequestMapping("/loading")
@RequestMapping({"/launch", "/loading"})
public String loading(Model model, HttpServletRequest request) {
OidcAuthenticationToken token = getTokenWithoutContext();
OidcTokenUtils oidcTokenUtils = new OidcTokenUtils(token);
Expand Down Expand Up @@ -597,6 +596,7 @@ public String doTermLoad(@PathVariable("courseId") String courseId, @PathVariabl
impersonationModel = new ImpersonationModel();
}
model.addAttribute("impersonationModel", impersonationModel);
model.addAttribute("newestTerm", termId);

String currentUserId = impersonationModel.getUsername() == null ? oidcTokenUtils.getUserLoginId() : impersonationModel.getUsername();

Expand Down Expand Up @@ -880,6 +880,77 @@ public String endSelfImpersonation(@PathVariable("courseId") String courseId, @M
return main(courseId, model, session);
}

@RequestMapping("/lookup-launch")
@Secured({LTIConstants.ADMIN_AUTHORITY})
public String lookupLaunch(@ModelAttribute FindParentModel findParentModel, Model model, HttpSession session) {
getTokenWithoutContext();

Date nowDate = new Date();

List<CanvasTerm> terms = termService.getEnrollmentTerms()
.stream()
.filter(term -> term.getSisTermId().compareTo("4218") >= 0 && term.getSisTermId().charAt(0) == '4')
.filter(term -> CanvasDateFormatUtil.string2DateOnly(term.getStartAt()).compareTo(nowDate) < 0)
.sorted(Comparator.comparing(CanvasTerm::getSisTermId).reversed())
.toList();

if (courseSessionService.getAttributeFromSession(session, "all", "terms", List.class) == null) {
courseSessionService.addAttributeToSession(session, "all", "terms", terms);
}

model.addAttribute("terms", terms);

return "findParentCourse";
}

@PostMapping(value = "/lookup-search-sisid")
@Secured({LTIConstants.ADMIN_AUTHORITY})
public String lookupSearchBySisId(@ModelAttribute FindParentModel findParentModel, Model model, HttpSession session) {
getTokenWithoutContext();

FindParentResult findParentResult = null;

List<CanvasTerm> terms = courseSessionService.getAttributeFromSession(session, "all",
"terms", List.class);

SisCourse sisCourse = sisService.getSisCourseBySiteId(findParentModel.getSisIdSearch().trim().toUpperCase());
findParentResult = crosslistService.processSisLookup(sisCourse);

model.addAttribute("terms", terms);

if (findParentResult != null) {
model.addAttribute("findParentResult", findParentResult);
}

return "findParentCourse";
}

@PostMapping(value = "/lookup-search-termandclassnumber")
@Secured({LTIConstants.ADMIN_AUTHORITY})
public String lookupSearchByTermAndClassNUmber(@ModelAttribute FindParentModel findParentModel, Model model, HttpSession session) {
getTokenWithoutContext();

FindParentResult findParentResult = null;

List<CanvasTerm> terms = courseSessionService.getAttributeFromSession(session, "all",
"terms", List.class);

final String strm = findParentModel.getTermByClassNumberSearch().trim();
final String classNumber = findParentModel.getClassNumberSearch().trim();

final String iuSiteId = sisService.getIuSiteIdFromStrmAndClassNumber(strm, classNumber);

SisCourse sisCourse = sisService.getSisCourseBySiteId(iuSiteId);
findParentResult = crosslistService.processSisLookup(sisCourse);

model.addAttribute("terms", terms);

if (findParentResult != null) {
model.addAttribute("findParentResult", findParentResult);
}

return "findParentCourse";
}

private List<SectionUIDisplay> removeSectionUiDisplayBySectionName(@NonNull List<SectionUIDisplay> oldList, @NonNull String toRemoveSectionName) {
List<SectionUIDisplay> newList = new ArrayList<SectionUIDisplay>();
Expand Down
Loading
Loading