All Downloads are FREE. Search and download functionalities are using the official Maven repository.

edu.iu.uits.lms.provisioning.service.SectionProvisioning Maven / Gradle / Ivy

package edu.iu.uits.lms.provisioning.service;

/*-
 * #%L
 * lms-lti-3rdpartyprovisioning
 * %%
 * Copyright (C) 2015 - 2022 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.canvas.model.Account;
import edu.iu.uits.lms.canvas.model.Course;
import edu.iu.uits.lms.canvas.services.AccountService;
import edu.iu.uits.lms.canvas.services.CourseService;
import edu.iu.uits.lms.iuonly.services.SisServiceImpl;
import edu.iu.uits.lms.provisioning.model.content.FileContent;
import edu.iu.uits.lms.provisioning.model.content.StringArrayFileContent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Slf4j
@Service
public class SectionProvisioning {

   @Autowired
   private AccountService accountService;

   @Autowired
   private CourseService courseService;

   @Autowired
   private CsvService csvService;

   @Autowired
   private SisServiceImpl sisService;

   public List processSections(Collection fileToProcess, List authorizedAccounts, boolean overrideRestrictions) {
      List prs = new ArrayList<>();
      StringBuilder emailMessage = new StringBuilder();
      StringBuilder errorMessage = new StringBuilder();
      List stringArrayList = new ArrayList<>();
      Map sisCourses = new HashMap<>();
      Map sisSections = new HashMap<>();
      Map accountChecksMap = new HashMap<>();
      Map courseAndAccountTrackerMap = new HashMap<>();
      int rejected = 0;
      int success = 0;

      for (FileContent file : fileToProcess) {
         // read individual files line by line
         List  fileContents = ((StringArrayFileContent)file).getContents();
         emailMessage.append(file.getFileName() + ":\r\n");
         int rowCounter = 0;
         int headerLength = 0;

         for (String[] lineContentArray : fileContents) {
            // increment the counter here and not several places
            rowCounter++;

            if (rowCounter == 1) {
               headerLength = lineContentArray.length;
               stringArrayList.add(lineContentArray);
               continue;
            }

            int lineLength = lineContentArray.length;

            if (lineLength != headerLength) {
               errorMessage.append("\tLine " + rowCounter + ": Row did not match the amount of fields specified in the header. Skipping. Double check the amount of commas and try again.\r\n");
               rejected++;
               continue;
            }

            // if the user has override permissions, do not worry about the other lookups and move on!
            if (overrideRestrictions) {
               // Everything is cool, add it in!
               log.debug("Successfully added in row " + rowCounter);
               stringArrayList.add(lineContentArray);
               success++;
               continue;
            }

            // sectionId is always the first entry in the section.csv
            String sectionId = lineContentArray[0];

            // courseId is always the second entry in the section.csv
            String courseId = lineContentArray[1];

            // set SIS checks should happen until proven otherwise
            boolean doSisCheck = true;

            if (sisCourses.containsKey(courseId)) {
               if (sisCourses.get(courseId)) {
                  // confirmed course is 'true' in the map, so skip the sis course lookup later
                  log.debug("Skipped the course " + courseId + " check because we already verified it as good.");
                  doSisCheck = false;
               } else {
                  // confirmed course is 'false', so skip this line since we know it's not ok to use
                  log.debug("Skipped " + rowCounter + " because it is a SIS course and we already checked.");
                  errorMessage.append("\tLine " + rowCounter + ": Course " + courseId + " rejected. Not authorized for SIS changes.\r\n");
                  rejected++;
                  continue;
               }
            }

            if (sisSections.containsKey(sectionId)) {
               if (sisSections.get(sectionId)) {
                  // confirmed section is 'true' in the map, so skip the sis lookup later
                  log.debug("Skipped the sectionId " + sectionId + " check because we already verified it as good.");
                  doSisCheck = false;
               } else {
                  // confirmed section is 'false', so skip this line since we know it's not ok to use
                  log.debug("Skipped " + rowCounter + " because it is a SIS section and we already checked.");
                  errorMessage.append("\tLine " + rowCounter + ": Section " + sectionId + " rejected. Not authorized for SIS changes.\r\n");
                  rejected++;
                  continue;
               }
            }

            if (doSisCheck) {
               // look up for SIS stuff
               if (sisService.isLegitSisCourse(courseId)) {
                  log.debug("Skipped " + rowCounter + " because it is a SIS course and user did not have SIS permission.");
                  errorMessage.append("\tLine " + rowCounter + ": Course " + courseId + " rejected. Not authorized for SIS changes.\r\n");
                  sisCourses.put(courseId, false);
                  rejected++;
                  continue;
               }

               // look up for SIS stuff
               if (sisService.isLegitSisCourse(sectionId)) {
                  log.debug("Skipped " + rowCounter + " because it is a SIS section and user did not have SIS permission.");
                  errorMessage.append("\tLine " + rowCounter + ": Section " + sectionId + " rejected. Not authorized for SIS changes.\r\n");
                  sisSections.put(sectionId, false);
                  rejected++;
                  continue;
               }
            }

            // if we made it here, it passed the SIS checks. Add it to the lists
            sisCourses.put(courseId, true);
            sisSections.put(sectionId, true);

            boolean isAccountAuthorized = false;
            boolean accountPreviouslyAuthorized = false;

            // check existing maps to see if we've looked up this info previously and deal with it as appropriate
            if (accountChecksMap.containsKey(courseId)) {
               if (accountChecksMap.get(courseId)) {
                  // we've checked this course's account before and verified, so let's set accountPreviouslyAuthorized to true to bypass the accounts check
                  accountPreviouslyAuthorized = true;
               } else {
                  // account for course 'false', so skip this line since we know it's not ok to use
                  String accountName = courseAndAccountTrackerMap.get(courseId);
                  log.debug("Skipped " + rowCounter + " because user is not authorized to provision to " + accountName + ".");
                  errorMessage.append("\tLine " + rowCounter + ": Section " + sectionId + " rejected. Not authorized to work in " + accountName + " subaccount.\r\n");
                  rejected++;
                  continue;
               }
            }

            // if we made it here, this is a first time account lookup
            if (overrideRestrictions || accountPreviouslyAuthorized) {
               isAccountAuthorized = true;
            } else if (authorizedAccounts.contains("ALL")) {
               // have authority for all nodes, so set the boolean
               isAccountAuthorized = true;
            } else {
               Course course = courseService.getCourse("sis_course_id:" + courseId);
               if (course == null) {
                  // course does not exist in Canvas yet, so give this a pass and assume it's cool
                  isAccountAuthorized = true;
                  accountChecksMap.put(courseId, true);
               } else {
                  Account courseAccount = accountService.getAccount(course.getAccountId());
                  if (authorizedAccounts.contains(courseAccount.getName())) {
                     isAccountAuthorized = true;
                     accountChecksMap.put(courseId, true);
                  } else {
                     // get the parent account names
                     List parentAccountNames = accountService.getParentAccounts(courseAccount.getId())
                             .stream().map(parentNames -> parentNames.getName()).collect(Collectors.toList());

                     for (String authorizedAccount : authorizedAccounts) {
                        if (parentAccountNames.contains(authorizedAccount)) {
                           isAccountAuthorized = true;
                           accountChecksMap.put(courseId, true);
                           break;
                        }
                     }

                     // if we made it to here, this course does not have an authorized account. Add to map for check in future lines.
                     if (!isAccountAuthorized) {
                        accountChecksMap.put(courseId, false);
                        courseAndAccountTrackerMap.put(courseId, courseAccount.getName());
                     }
                  }
               }
            }

            if (isAccountAuthorized) {
               // Everything is cool, add it in!
               log.debug("Successfully added in row " + rowCounter);
               stringArrayList.add(lineContentArray);
               success++;
               continue;
            } else {
               String accountName = courseAndAccountTrackerMap.get(courseId);
               log.debug("Skipped " + rowCounter + " because user is not authorized to provision to " + accountName + ".");
               errorMessage.append("\tLine " + rowCounter + ": Section " + sectionId + " rejected. Not authorized to work in " + accountName + " subaccount.\r\n");
               rejected++;
               continue;
            }
         }

         StringBuilder finalMessage = new StringBuilder(emailMessage);
         byte[] fileBytes = null;
         boolean fileException = false;
         try {
            // Create csv file to send to Canvas
            fileBytes = csvService.writeCsvToBytes(stringArrayList, null);
            int total = rejected + success;

            if (errorMessage.length() > 0) {
               finalMessage.append(errorMessage);
               finalMessage.append("\tSections rejected: " + rejected + "\r\n");
               finalMessage.append("\tSections sent to Canvas: " + success + "\r\n");
               finalMessage.append("\tTotal sections processed: " + total + "\r\n");
            } else {
               finalMessage.append("\tSections rejected: " + rejected + "\r\n");
               finalMessage.append("\tSections sent to Canvas: " + success + "\r\n");
               finalMessage.append("\tTotal sections processed: " + total + "\r\n");
            }
         } catch (IOException e) {
            log.error("Error generating csv", e);
            finalMessage.append("\tThere were errors when generating the CSV file to send to Canvas\r\n");
            fileException = true;
         }

            ProvisioningResult pr = new ProvisioningResult(finalMessage,
                  new ProvisioningResult.FileObject(file.getFileName(), fileBytes), fileException);
            prs.add(pr);
      }

      return prs;
   }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy