Skip to content

Commit

Permalink
Merge pull request #38 from indiana-university/decrosslist
Browse files Browse the repository at this point in the history
LMSA-9397 / LMSA-9398 / LMSA-9399 - decrosslisting tool
  • Loading branch information
gjthomas authored Nov 5, 2024
2 parents 8b09ffd + b45da1f commit d1a8926
Show file tree
Hide file tree
Showing 26 changed files with 998 additions and 269 deletions.
3 changes: 2 additions & 1 deletion examples/crosslisting.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"custom_fields": {
"instructure_membership_roles": "$com.Instructure.membership.roles",
"canvas_course_id": "$Canvas.course.id",
"canvas_user_login_id": "$Canvas.user.loginId"
"canvas_user_login_id": "$Canvas.user.loginId",
"is_crosslist_tool": "true"
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"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_lookup",
"title": "De-Cross-listing Sections",
"description": "For searching for parent courses and de-cross-listing sections quickly.",
"oidc_initiation_url": "http://localhost:8080/lti/login_initiation/lms_lti_decrosslisting",
"target_link_uri": "http://localhost:8080/app/lookup-launch",
"extensions": [
{
Expand All @@ -12,7 +12,7 @@
"placements": [
{
"enabled": true,
"placement": "account_navigation",
"placement": "course_navigation",
"message_type": "LtiResourceLinkRequest"
}
]
Expand Down
11 changes: 11 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
<lms-embedded-services.version>5.2.43</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>
<springdoc-openapi-ui.version>1.8.0</springdoc-openapi-ui.version>
<webjars-locator.version>0.47</webjars-locator.version>

<plugins.compiler.version>3.11.0</plugins.compiler.version>
Expand Down Expand Up @@ -148,6 +149,16 @@
<version>${jsoup.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>${springdoc-openapi-ui.version}</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-data-rest</artifactId>
<version>${springdoc-openapi-ui.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
@Slf4j
@EnableCookieFilter(ignoredRequestPatterns = "/rest/**")
@EnableRedisConfiguration
@EnableLtiClient(toolKeys = {"lms_lti_crosslisting", "lms_lti_crosslisting_lookup"})
@EnableLtiClient(toolKeys = {"lms_lti_crosslisting", "lms_lti_decrosslisting"})
@EnableCanvasClient
@EnableIuOnlyClient
@EnableConfigurationProperties(GitRepositoryState.class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,22 +33,32 @@
* #L%
*/

import edu.iu.uits.lms.crosslist.model.DecrosslistUser;
import edu.iu.uits.lms.crosslist.repository.DecrosslistUserRepository;
import edu.iu.uits.lms.lti.LTIConstants;
import edu.iu.uits.lms.lti.repository.DefaultInstructorRoleRepository;
import edu.iu.uits.lms.lti.service.LmsDefaultGrantedAuthoritiesMapper;
import edu.iu.uits.lms.lti.service.OidcTokenUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

@Slf4j
public class CustomRoleMapper extends LmsDefaultGrantedAuthoritiesMapper {

private ToolConfig toolConfig;

public CustomRoleMapper(DefaultInstructorRoleRepository defaultInstructorRoleRepository, ToolConfig toolConfig) {
private DecrosslistUserRepository decrosslistUserRepository;

public CustomRoleMapper(DefaultInstructorRoleRepository defaultInstructorRoleRepository, ToolConfig toolConfig, DecrosslistUserRepository decrosslistUserRepository) {
super(defaultInstructorRoleRepository);
this.toolConfig = toolConfig;
this.decrosslistUserRepository = decrosslistUserRepository;
}

@Override
Expand All @@ -66,4 +76,42 @@ protected String returnEquivalentAuthority(String[] userRoles, List<String> inst
//Then do normal stuff
return super.returnEquivalentAuthority(userRoles, instructorRoles);
}

@Override
public Collection<? extends GrantedAuthority> mapAuthorities(Collection<? extends GrantedAuthority> authorities) {
List<GrantedAuthority> remappedAuthorities = new ArrayList<>();
remappedAuthorities.addAll(authorities);
for (GrantedAuthority authority : authorities) {
OidcUserAuthority userAuth = (OidcUserAuthority) authority;
OidcTokenUtils oidcTokenUtils = new OidcTokenUtils(userAuth.getAttributes());
log.debug("LTI Claims: {}", userAuth.getAttributes());

if (Boolean.parseBoolean(oidcTokenUtils.getCustomValue("is_crosslist_tool"))) {
log.debug("CustomRoleMapper: Crosslist tool");
// Use the legit roles for crosslisting, which is less restrictive than the decrosslist tool
return super.mapAuthorities(authorities);
} else {
log.debug("CustomRoleMapper: Decrosslist tool");
// decrosslist tool has a restriction of needing to be in a certain table to access the tool
String userId = oidcTokenUtils.getUserLoginId();

String rolesString = "NotAuthorized";

DecrosslistUser user = decrosslistUserRepository.findByUsername(userId);

if (user != null) {
rolesString = LTIConstants.CANVAS_INSTRUCTOR_ROLE;
}

String[] userRoles = rolesString.split(",");

String newAuthString = returnEquivalentAuthority(userRoles, getDefaultInstructorRoles());

OidcUserAuthority newUserAuth = new OidcUserAuthority(newAuthString, userAuth.getIdToken(), userAuth.getUserInfo());
remappedAuthorities.add(newUserAuth);
}
}

return remappedAuthorities;
}
}
57 changes: 57 additions & 0 deletions src/main/java/edu/iu/uits/lms/crosslist/config/JpaRestConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package edu.iu.uits.lms.crosslist.config;

/*-
* #%L
* lms-lti-crosslist
* %%
* Copyright (C) 2015 - 2024 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.model.DecrosslistUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.core.mapping.RepositoryDetectionStrategy;
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurer;
import org.springframework.web.servlet.config.annotation.CorsRegistry;

@Configuration
@Slf4j
public class JpaRestConfig implements RepositoryRestConfigurer {

@Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config, CorsRegistry cors) {
// This is needed to allow the "ids" to be served up via the
// @RepositoryRestResource annotation (by default, it is suppressed)
config.exposeIdsFor(DecrosslistUser.class);

RepositoryRestConfigurer.super.configureRepositoryRestConfiguration(config, cors);
config.setRepositoryDetectionStrategy(RepositoryDetectionStrategy.RepositoryDetectionStrategies.ANNOTATED);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
Expand All @@ -51,6 +52,10 @@
import java.util.Map;

@Configuration("crosslistDbConfig")
@EnableJpaRepositories(
entityManagerFactoryRef = "crosslistEntityMgrFactory",
transactionManagerRef = "crosslistTransactionMgr",
basePackages = {"edu.iu.uits.lms.crosslist.repository"})
@EnableTransactionManagement
public class PostgresDBConfig {

Expand Down
53 changes: 29 additions & 24 deletions src/main/java/edu/iu/uits/lms/crosslist/config/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
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.crosslist.repository.DecrosslistUserRepository;
import edu.iu.uits.lms.lti.repository.DefaultInstructorRoleRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
Expand All @@ -57,6 +58,30 @@
@EnableWebSecurity
public class SecurityConfig {

@Configuration
@Order(SecurityProperties.BASIC_AUTH_ORDER - 5)
public static class CrosslistRestSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {

@Override
public void configure(HttpSecurity http) throws Exception {
http
.cors().and()
.requestMatchers().antMatchers("/rest/**","/api/**")
.and()
.authorizeRequests()
.antMatchers("/api/**").permitAll()
.antMatchers("/rest/**")
.access("hasAuthority('SCOPE_lms:rest') and hasAuthority('ROLE_LMS_REST_ADMINS')")
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.oauth2ResourceServer()
.jwt().jwtAuthenticationConverter(new CustomJwtAuthenticationConverter());

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

@Configuration
@Order(SecurityProperties.BASIC_AUTH_ORDER - 4)
public static class CrosslistWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
Expand All @@ -67,6 +92,9 @@ public static class CrosslistWebSecurityConfigurerAdapter extends WebSecurityCon
@Autowired
private ToolConfig toolConfig;

@Autowired
private DecrosslistUserRepository decrosslistUserRepository;

@Override
protected void configure(HttpSecurity http) throws Exception {
http
Expand All @@ -86,7 +114,7 @@ protected void configure(HttpSecurity http) throws Exception {

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

http.apply(lti13Configurer);

Expand All @@ -112,29 +140,6 @@ public void configure(WebSecurity web) throws Exception {

}

@Configuration
@Order(SecurityProperties.BASIC_AUTH_ORDER - 3)
public static class CrosslistRestSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {

@Override
public void configure(HttpSecurity http) throws Exception {
http
.cors().and()
.requestMatchers().antMatchers("/rest/**")
.and()
.authorizeRequests()
.antMatchers("/rest/**")
.access("hasAuthority('SCOPE_lms:rest') and hasAuthority('ROLE_LMS_REST_ADMINS')")
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.oauth2ResourceServer()
.jwt().jwtAuthenticationConverter(new CustomJwtAuthenticationConverter());

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

@Configuration
@Order(SecurityProperties.BASIC_AUTH_ORDER - 2)
public static class CrosslistCatchAllSecurityConfig extends WebSecurityConfigurerAdapter {
Expand Down
66 changes: 66 additions & 0 deletions src/main/java/edu/iu/uits/lms/crosslist/config/SwaggerConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package edu.iu.uits.lms.crosslist.config;

/*-
* #%L
* lms-lti-crosslist
* %%
* Copyright (C) 2015 - 2024 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 io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
import io.swagger.v3.oas.annotations.info.Info;
import io.swagger.v3.oas.annotations.security.OAuthFlow;
import io.swagger.v3.oas.annotations.security.OAuthFlows;
import io.swagger.v3.oas.annotations.security.OAuthScope;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.security.SecurityScheme;
import org.springdoc.core.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

@Profile("swagger")
@Configuration
@OpenAPIDefinition(info = @Info(title = "Crosslist REST Endpoints", version = "${lms-lti-crosslist.version}"),
security = @SecurityRequirement(name = "security_auth_crosslist"))
@SecurityScheme(name = "security_auth_crosslist", type = SecuritySchemeType.OAUTH2,
flows = @OAuthFlows(authorizationCode = @OAuthFlow(
authorizationUrl = "${springdoc.oAuthFlow.authorizationUrl}",
scopes = {@OAuthScope(name = "lms:rest")},
tokenUrl = "${springdoc.oAuthFlow.tokenUrl}")))
public class SwaggerConfig {
@Bean
public GroupedOpenApi groupedOpenApi() {
return GroupedOpenApi.builder()
.group("crosslist")
.packagesToScan("edu.iu.uits.lms.crosslist.controller.rest")
.build();
}
}
Loading

0 comments on commit d1a8926

Please sign in to comment.