diff --git a/README.md b/README.md index 5d49eef..785a471 100644 --- a/README.md +++ b/README.md @@ -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 | @@ -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! \ No newline at end of file +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: + + + +
PropertyValue
Custom parameters + +``` +canvas_user_login_id=johnsmith +instructure_membership_roles=http://purl.imsglobal.org/vocab/lis/v2/institution/person#Administrator +``` + +
+ +Use an appropriate `canvas_user_login_id`. + +From the `Security Model` section, set the following: + + + + + + + +
PropertyValue
LTI version1.3.0
Message URLhttp://localhost:8080/app/lookup-launch
Client IDdev (or whatever is appropriate based on the record inserted in the database table from above)
Initiate login URLhttp://localhost:8080/lti/login_initiation/lms_lti_crosslisting
Redirection URI(s)http://localhost:8080/lti/login
diff --git a/examples/crosslisting-lookup.json b/examples/crosslisting-lookup.json new file mode 100644 index 0000000..68ee67a --- /dev/null +++ b/examples/crosslisting-lookup.json @@ -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" + } +} \ No newline at end of file diff --git a/examples/crosslisting.json b/examples/crosslisting.json index 9feaf69..4b6a795 100644 --- a/examples/crosslisting.json +++ b/examples/crosslisting.json @@ -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", diff --git a/pom.xml b/pom.xml index c6433c4..35020be 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ org.springframework.boot spring-boot-starter-parent - 2.7.8 + 2.7.15 @@ -74,20 +74,20 @@ 17 17 17 - 3.5.1 - 5.1.8.2_1 - 5.2.3 - 4.7 - 2021.0.5 - 0.46 + 3.7.1 + 5.2.5.2 + 5.2.16 + 4.8 + 2021.0.8 + 0.47 - 3.10.1 - 3.0.1 + 3.11.0 + 3.1.0 3.5.0 2.0.0 1.6.13 - 2.5.3 - 3.2.1 + 3.0.0 + 3.3.0 diff --git a/src/main/java/edu/iu/uits/lms/crosslist/CrosslistConstants.java b/src/main/java/edu/iu/uits/lms/crosslist/CrosslistConstants.java index 861ecbb..3ab4b93 100644 --- a/src/main/java/edu/iu/uits/lms/crosslist/CrosslistConstants.java +++ b/src/main/java/edu/iu/uits/lms/crosslist/CrosslistConstants.java @@ -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"; diff --git a/src/main/java/edu/iu/uits/lms/crosslist/WebApplication.java b/src/main/java/edu/iu/uits/lms/crosslist/WebApplication.java index 18510b2..fc75881 100644 --- a/src/main/java/edu/iu/uits/lms/crosslist/WebApplication.java +++ b/src/main/java/edu/iu/uits/lms/crosslist/WebApplication.java @@ -55,7 +55,7 @@ import java.util.Date; @SpringBootApplication -@EnableGlobalErrorHandler(accessDeniedViewName="accessDenied") +@EnableGlobalErrorHandler @Slf4j @EnableCookieFilter(ignoredRequestPatterns = "/rest/**") @EnableRedisConfiguration diff --git a/src/main/java/edu/iu/uits/lms/crosslist/config/SecurityConfig.java b/src/main/java/edu/iu/uits/lms/crosslist/config/SecurityConfig.java index 89058e9..cb29aff 100644 --- a/src/main/java/edu/iu/uits/lms/crosslist/config/SecurityConfig.java +++ b/src/main/java/edu/iu/uits/lms/crosslist/config/SecurityConfig.java @@ -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; @@ -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; @@ -72,7 +75,14 @@ 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() @@ -80,13 +90,18 @@ protected void configure(HttpSecurity http) throws Exception { 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 @@ -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/**") @@ -113,6 +130,8 @@ public void configure(HttpSecurity http) throws Exception { .and() .oauth2ResourceServer() .jwt().jwtAuthenticationConverter(new CustomJwtAuthenticationConverter()); + + http.apply(new RestSecurityLoggingConfig()); } } @@ -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)); } } } diff --git a/src/main/java/edu/iu/uits/lms/crosslist/controller/CrosslistController.java b/src/main/java/edu/iu/uits/lms/crosslist/controller/CrosslistController.java index db5d6e6..64ecb8f 100644 --- a/src/main/java/edu/iu/uits/lms/crosslist/controller/CrosslistController.java +++ b/src/main/java/edu/iu/uits/lms/crosslist/controller/CrosslistController.java @@ -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; @@ -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; @@ -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; @@ -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(); @@ -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); @@ -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(); @@ -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 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 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 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 removeSectionUiDisplayBySectionName(@NonNull List oldList, @NonNull String toRemoveSectionName) { List newList = new ArrayList(); diff --git a/src/main/java/edu/iu/uits/lms/crosslist/model/FindParentModel.java b/src/main/java/edu/iu/uits/lms/crosslist/model/FindParentModel.java new file mode 100644 index 0000000..21201be --- /dev/null +++ b/src/main/java/edu/iu/uits/lms/crosslist/model/FindParentModel.java @@ -0,0 +1,44 @@ +package edu.iu.uits.lms.crosslist.model; + +/*- + * #%L + * lms-lti-crosslist + * %% + * Copyright (C) 2015 - 2023 Indiana University + * %% + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the Indiana University nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +import lombok.Data; + +@Data +public class FindParentModel { + private String radioSearch; + private String sisIdSearch; + private String termByClassNumberSearch; + private String classNumberSearch; +} \ No newline at end of file diff --git a/src/main/java/edu/iu/uits/lms/crosslist/model/FindParentResult.java b/src/main/java/edu/iu/uits/lms/crosslist/model/FindParentResult.java new file mode 100644 index 0000000..ca1a627 --- /dev/null +++ b/src/main/java/edu/iu/uits/lms/crosslist/model/FindParentResult.java @@ -0,0 +1,48 @@ +package edu.iu.uits.lms.crosslist.model; + +/*- + * #%L + * lms-lti-crosslist + * %% + * Copyright (C) 2015 - 2023 Indiana University + * %% + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the Indiana University nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +import edu.iu.uits.lms.crosslist.CrosslistConstants; +import lombok.Data; + +@Data +public class FindParentResult { + private String url; + private String name; + private String sisCourseId; + private boolean showCourseInfo; + private String statusMessage; + private String statusIconCssClasses; + private String statusIconName; +} diff --git a/src/main/java/edu/iu/uits/lms/crosslist/service/CrosslistService.java b/src/main/java/edu/iu/uits/lms/crosslist/service/CrosslistService.java index 0e5102f..1b0d67a 100644 --- a/src/main/java/edu/iu/uits/lms/crosslist/service/CrosslistService.java +++ b/src/main/java/edu/iu/uits/lms/crosslist/service/CrosslistService.java @@ -33,6 +33,7 @@ * #L% */ +import edu.iu.uits.lms.canvas.config.CanvasConfiguration; import edu.iu.uits.lms.canvas.helpers.CanvasDateFormatUtil; import edu.iu.uits.lms.canvas.model.Account; import edu.iu.uits.lms.canvas.model.CanvasTerm; @@ -40,8 +41,10 @@ import edu.iu.uits.lms.canvas.model.Section; import edu.iu.uits.lms.canvas.services.AccountService; import edu.iu.uits.lms.canvas.services.CourseService; +import edu.iu.uits.lms.canvas.services.SectionService; import edu.iu.uits.lms.common.session.CourseSessionService; import edu.iu.uits.lms.crosslist.CrosslistConstants; +import edu.iu.uits.lms.crosslist.model.FindParentResult; import edu.iu.uits.lms.crosslist.model.SectionUIDisplay; import edu.iu.uits.lms.iuonly.model.SisCourse; import edu.iu.uits.lms.iuonly.services.FeatureAccessServiceImpl; @@ -88,6 +91,13 @@ public class CrosslistService { @Autowired private SisServiceImpl sisService; + @Autowired + private SectionService sectionService; + + @Autowired + private CanvasConfiguration canvasConfiguration; + + // self reference so can use the cache for getCourseSections() from within this service @Lazy @Autowired @@ -380,6 +390,61 @@ public boolean canCoursesBeCrosslistedBasedOnEtexts(String sourceSisCourseSiteId return sourceCourseEtextIsbns.equals(destinationCourseEtextIsbns); } + public FindParentResult processSisLookup(SisCourse sisCourse) { + FindParentResult findParentResult = new FindParentResult(); + + if (sisCourse == null || sisCourse.getIuSiteId() == null) { + findParentResult.setShowCourseInfo(false); + findParentResult.setStatusMessage(CrosslistConstants.LOOKUP_FAILURE_NOT_FOUND_IN_SIS_MESSAGE); + findParentResult.setStatusIconCssClasses(CrosslistConstants.LOOKUP_FAILURE_CSS); + findParentResult.setStatusIconName(CrosslistConstants.LOOKUP_FAILURE_ICON_NAME); + return findParentResult; + } + + Section section = sectionService.getSection(String.format("sis_section_id:%s", sisCourse.getIuSiteId())); + + if (section == null || section.getSis_course_id() == null || section.getSis_section_id() == null) { + findParentResult.setShowCourseInfo(false); + findParentResult.setStatusMessage(CrosslistConstants.LOOKUP_FAILURE_NOT_FOUND_IN_CANVAS_MESSAGE); + findParentResult.setStatusIconCssClasses(CrosslistConstants.LOOKUP_FAILURE_CSS); + findParentResult.setStatusIconName(CrosslistConstants.LOOKUP_FAILURE_ICON_NAME); + return findParentResult; + } + + Course course = courseService.getCourse(section.getCourse_id()); + + if (course == null) { + findParentResult.setShowCourseInfo(false); + findParentResult.setStatusMessage(CrosslistConstants.LOOKUP_FAILURE_NOT_FOUND_IN_CANVAS_MESSAGE); + findParentResult.setStatusIconCssClasses(CrosslistConstants.LOOKUP_FAILURE_CSS); + findParentResult.setStatusIconName(CrosslistConstants.LOOKUP_FAILURE_ICON_NAME); + return findParentResult; + } + + if (section.getSis_course_id().equals(section.getSis_section_id())) { + findParentResult.setShowCourseInfo(true); + findParentResult.setStatusMessage(CrosslistConstants.LOOKUP_FAILURE_COURSE_NOT_CROSSLISTED_MESSAGE); + findParentResult.setStatusIconCssClasses(CrosslistConstants.LOOKUP_FAILURE_CSS); + findParentResult.setStatusIconName(CrosslistConstants.LOOKUP_FAILURE_ICON_NAME); + findParentResult.setName(course.getName()); + findParentResult.setSisCourseId(course.getSisCourseId()); + findParentResult.setUrl(String.format("%s/courses/%s", + canvasConfiguration.getBaseUrl(), course.getId())); + return findParentResult; + } + + findParentResult.setShowCourseInfo(true); + findParentResult.setStatusMessage(CrosslistConstants.LOOKUP_SUCCESS_FOUND_MESSAGE); + findParentResult.setStatusIconCssClasses(CrosslistConstants.LOOKUP_SUCCESS_CSS); + findParentResult.setStatusIconName(CrosslistConstants.LOOKUP_SUCCESS_ICON_NAME); + findParentResult.setName(course.getName()); + findParentResult.setSisCourseId(course.getSisCourseId()); + findParentResult.setUrl(String.format("%s/courses/%s", + canvasConfiguration.getBaseUrl(), course.getId())); + + return findParentResult; + } + @Data private class CourseSisNaturalAndAlien { String courseId; diff --git a/src/main/resources/static/css/crosslisting.css b/src/main/resources/static/css/crosslisting.css index 3bbb09d..114ca77 100644 --- a/src/main/resources/static/css/crosslisting.css +++ b/src/main/resources/static/css/crosslisting.css @@ -89,6 +89,16 @@ margin: 1.0rem 0 1.2rem; } -.toggler { - margin-bottom: 0.5rem; +/* override the rivet style */ +.rvt-disclosure__content { + box-shadow: none; } + +/* override rivet to make the toggle black instead of blue */ +.rvt-disclosure__toggle::before { + filter: brightness(0%); +} + +.resultsWidth { + width: max-content; +} \ No newline at end of file diff --git a/src/main/resources/static/js/crosslisting.js b/src/main/resources/static/js/crosslisting.js index 9d96da6..84c48ff 100644 --- a/src/main/resources/static/js/crosslisting.js +++ b/src/main/resources/static/js/crosslisting.js @@ -117,9 +117,6 @@ $(document).ready(function(){ loadUnavailableSections(); - // move focus to the newly added section - $("button[aria-controls=" + termId + "]").focus(); - }); // remove the term option from the map since there won't be a need to select it again $("#addTerm option[value=" + termId + "]").remove(); @@ -137,24 +134,6 @@ $(document).ready(function(){ $("#loading").show(); }); - // this controls the toggle dropdowns - $(document).on('click', '.toggleGroup', function() { - - $(this).parent().find('.toggler:first').slideToggle("slow"); - - if ($(this).find("use:first").attr("xlink:href").includes("rvt-icon-chevron-down")) { - $(this).find("button:first").attr("aria-expanded","false"); - $(this).find("use:first").attr("xlink:href", function(index, old) { - return old.replace("rvt-icon-chevron-down", "rvt-icon-chevron-right"); - }); - } else { - $(this).find("button:first").attr("aria-expanded","true"); - $(this).find("use:first").attr("xlink:href", function(index, old) { - return old.replace("rvt-icon-chevron-right", "rvt-icon-chevron-down"); - }); - } - }); - $(document).ajaxComplete(function(event, xhr, settings) { var unavailableSectionsUrl = $('#unavailable-sections-load').data('urlbase'); @@ -168,6 +147,12 @@ $(document).ready(function(){ // Canvas has a message listener to resize the iframe parent.postMessage(JSON.stringify({subject: 'lti.frameResize', height: $(document).height()}), '*'); } + + // move focus to the newly added term + var focusElement = $(".newTerm").first(); + if (focusElement) { + focusElement.focus(); + } }); }); @@ -245,7 +230,7 @@ function checkboxEventRegistration() { event.preventDefault(); var currentBox = $(this); - var li = currentBox.parent(); + var li = currentBox.parent().parent(); if (currentBox.is(":checked")) { var newLi = $("
  • ", { diff --git a/src/main/resources/templates/accessDenied.html b/src/main/resources/templates/accessDenied.html deleted file mode 100644 index 9508978..0000000 --- a/src/main/resources/templates/accessDenied.html +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - Access Denied - - - -

    Access Denied

    -

    You are not authorized to use this tool. Access is limited to specific Canvas roles.

    - - diff --git a/src/main/resources/templates/confirmation.html b/src/main/resources/templates/confirmation.html index b539efa..b1b58dd 100644 --- a/src/main/resources/templates/confirmation.html +++ b/src/main/resources/templates/confirmation.html @@ -42,99 +42,101 @@
    -
    -
    -
    -
    -

    SP16-BL-MUS-A112-15890 - A112 (Online):15890

    -
    -

    Cross-listing Information

    -

    - Review the summary on this page to confirm that the Final List of Cross-listed Sections - is correct. If you are satisfied, click Submit to complete the request. To add or remove courses, - click Edit to return to the previous screen. -

    -

    - Note: Cross-listing and de-cross-listing should occur before students submit assignments or - receive grades (and ideally before the affected courses are published). If you cross-list or - de-cross-list a section after the term begins, student work and grades will remain in the course - with which the section was originally associated and will no longer be visible to you. -

    -
    -
    -

    Cross-listing Error

    -

    - The following section(s) could not be cross-listed because their eText order(s) do not match the - order for the primary section in this site. -

      -
      -
    • course_code1
    • -
      -
    -

    -
    +
    +
    + +

    SP16-BL-MUS-A112-15890 - A112 (Online):15890

    + + + -
    -
    -
    -
    -

    Final List of Cross-listed Sections

    -

    -

      -
    • None
    • -
    • - - -
    • -
    -

    -
    -
    -

    Summary of Actions

    -

    -

    Added

    -
      -
    • None
    • -
    • - - -
    • -
    -

    -

    -

    Removed

    -
      -
    • None
    • -
    • - - -
    • -
    -

    -
    -
    +
    +
    +
    +
    +

    Final List of Cross-listed Sections

    +

    +

      +
    • None
    • +
    • + + +
    • +
    +

    +
    +
    +

    Summary of Actions

    +

    +

    Added

    +
      +
    • None
    • +
    • + + +
    • +
    +

    +

    +

    Removed

    +
      +
    • None
    • +
    • + + +
    • +
    +

    -
    -

    Review Changes

    -

    Make sure you understand how the changes will affect grades, assignments, and other areas of your site. Information is provided on this page.

    -
    -
    -
    - - - - -
    - +
    + +
    + +
    +
    + + + + +
    +
    diff --git a/src/main/resources/templates/findParentCourse.html b/src/main/resources/templates/findParentCourse.html new file mode 100644 index 0000000..6c20a38 --- /dev/null +++ b/src/main/resources/templates/findParentCourse.html @@ -0,0 +1,253 @@ + + + + + + + Find parent course + + +
    +
    +

    Find parent course

    +
    + +
    + This tool will only return exact matches. +
    + +
    + Find by +
      +
    • +
      + + +
      +
    • +
    • +
      + + +
      +
    • +
    +
    + +
    +
    + +
    Example: SP22-BL-FOLK-E295-4441
    +
    + +
    +
    + + + + + + + SIS ID needs to be supplied. +
    +
    + + +
    + +
    +
    + +
    +
    + + +
    +
    + +
    Example: 4441
    +
    + +
    +
    + + + + + + + Class Number needs to be supplied. +
    +
    + + +
    + +
    +
    + +
    +
    + +

    Parent course found

    +
    + +
    +
    + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/fragments/enterImpersonationMode.html b/src/main/resources/templates/fragments/enterImpersonationMode.html index ed8f7a9..83aa7cb 100644 --- a/src/main/resources/templates/fragments/enterImpersonationMode.html +++ b/src/main/resources/templates/fragments/enterImpersonationMode.html @@ -40,65 +40,75 @@ -
    -
    -

    Cross-listing Assistant

    - -
    - +
    +
    +

    Cross-listing Assistant

    + +
    + +
    -
    -