gt:         if (!isUserLoggedIn && (isGoogleLoginRequired || !hasRegkey)) {
context: package teammates.ui.controller;
  
  import java.util.ArrayList;
  import java.util.List;
  import java.util.Map;
  
  import javax.servlet.http.HttpServletRequest;
  import javax.servlet.http.HttpSession;
  
  import teammates.common.datatransfer.UserType;
  import teammates.common.datatransfer.attributes.AccountAttributes;
  import teammates.common.datatransfer.attributes.StudentAttributes;
  import teammates.common.exception.EntityDoesNotExistException;
  import teammates.common.exception.EntityNotFoundException;
  import teammates.common.exception.InvalidOriginException;
  import teammates.common.exception.UnauthorizedAccessException;
  import teammates.common.util.Assumption;
  import teammates.common.util.Config;
  import teammates.common.util.Const;
  import teammates.common.util.CryptoHelper;
  import teammates.common.util.HttpRequestHelper;
  import teammates.common.util.LogMessageGenerator;
  import teammates.common.util.SanitizationHelper;
  import teammates.common.util.StatusMessage;
  import teammates.common.util.StatusMessageColor;
  import teammates.common.util.StringHelper;
  import teammates.common.util.Url;
  import teammates.logic.api.EmailSender;
  import teammates.logic.api.GateKeeper;
  import teammates.logic.api.Logic;
  import teammates.logic.api.TaskQueuer;
  import teammates.ui.pagedata.PageData;
  
  /** An 'action' to be performed by the system. If the logged in user is allowed
   * to perform the requested action, this object can talk to the back end to
   * perform that action.
   */
  public abstract class Action {
  
      /** This is used to ensure unregistered users don't access certain pages in the system. */
      public String regkey;
  
      /** The regkey may also contain a next url parameter as well. */
      public String nextUrlFromRegkey;
  
      /** This will be the admin user if the application is running under the masquerade mode. */
      public AccountAttributes loggedInUser;
  
      /** This is the 'nominal' user. Need not be the logged in user */
      public AccountAttributes account;
  
      /** This is the unregistered and not loggedin student's attributes. */
      public StudentAttributes student;
  
      protected Logic logic;
      protected GateKeeper gateKeeper;
      protected TaskQueuer taskQueuer;
      protected EmailSender emailSender;
  
      /** The full request URL e.g., {@code /page/instructorHome?user=abc&course=c1}. */
      protected String requestUrl;
  
      /** Parameters received with the request. */
      protected Map<String, String[]> requestParameters;
  
      /** Execution status info to be shown to he admin (in 'activity log'). */
      protected String statusToAdmin; // TODO: make this a list?
  
      /** Execution status info to be shown to the user. */
      protected List<StatusMessage> statusToUser = new ArrayList<>();
  
      /**
       * Whether the execution completed without any errors or
       * when we are unable to perform the requested action(s).
       **/
      protected boolean isError;
  
      /** Session that contains status message information. */
      protected HttpSession session;
  
      /** Session token used in forms/links to actions requiring origin validation. */
      protected String sessionToken;
  
      /** This is to get the blobInfo for any file upload from prev pages. */
      protected HttpServletRequest request;
  
      /** This is for authentication at Action Level. */
      private String authenticationRedirectUrl = "";
  
      /** Initializes variables.
       * Aborts with an {@link UnauthorizedAccessException} if the user is not
       * logged in or if a non-admin tried to masquerade as another user.
       *
       */
      public void init(HttpServletRequest req) {
          initialiseAttributes(req);
          validateOriginIfRequired();
          authenticateUser();
      }
  
      @SuppressWarnings("unchecked")
      protected void initialiseAttributes(HttpServletRequest req) {
          request = req;
          requestUrl = HttpRequestHelper.getRequestedUrl(request);
          logic = new Logic();
          gateKeeper = new GateKeeper();
          setTaskQueuer(new TaskQueuer());
          setEmailSender(new EmailSender());
          requestParameters = request.getParameterMap();
          session = request.getSession();
          sessionToken = CryptoHelper.computeSessionToken(session.getId());
          parseAndInitializeRegkeyFromRequest();
          // Set error status forwarded from the previous action
          isError = getRequestParamAsBoolean(Const.ParamsNames.ERROR);
      }
  
      /**
       * Parses and initializes the regkey from the http request.
       */
      private void parseAndInitializeRegkeyFromRequest() {
          String regkeyFromRequest = getRegkeyFromRequest();
          boolean isNextParamInRegkey = regkeyFromRequest != null
                                        && regkeyFromRequest.contains("${amp}" + Const.ParamsNames.NEXT_URL + "=");
          if (isNextParamInRegkey) {
              /*
               * Here regkey may contain the nextUrl as well. This is due to
               * a workaround which replaces "&" with a placeholder "${amp}", thus the
               * next parameter, nextUrl, is treated as part of the "regkey".
               */
              String[] split = regkeyFromRequest.split("\\$\\{amp\\}" + Const.ParamsNames.NEXT_URL + "=");
              regkey = split[0];
              nextUrlFromRegkey = SanitizationHelper.desanitizeFromNextUrl(split[1]);
          } else {
              regkey = regkeyFromRequest;
              nextUrlFromRegkey = null;
          }
      }
  
      public TaskQueuer getTaskQueuer() {
          return taskQueuer;
      }
  
      public void setTaskQueuer(TaskQueuer taskQueuer) {
          this.taskQueuer = taskQueuer;
      }
  
      public EmailSender getEmailSender() {
          return emailSender;
      }
  
      public void setEmailSender(EmailSender emailSender) {
          this.emailSender = emailSender;
      }
  
      // These methods are used for Cross-Site Request Forgery (CSRF) prevention
  
      private void validateOriginIfRequired() {
          if (!Const.SystemParams.PAGES_REQUIRING_ORIGIN_VALIDATION.contains(request.getRequestURI())) {
              return;
          }
  
          String referrer = request.getHeader("referer");
          if (referrer == null) {
              // Requests with missing referrer information are given the benefit of the doubt to
              // accommodate users who choose to disable the HTTP referrer setting in their browser
              // for privacy reasons
          } else if (!isHttpReferrerValid(referrer)) {
              throw new InvalidOriginException("Invalid HTTP referrer");
          }
  
          String sessionToken = getRequestParamValue(Const.ParamsNames.SESSION_TOKEN);
          if (sessionToken == null) {
              throw new InvalidOriginException("Missing session token");
          }
  
          if (!isSessionTokenValid(sessionToken)) {
              throw new InvalidOriginException("Invalid session token");
          }
      }
  
      /**
       * Validates the HTTP referrer against the request URL. The origin is the
       * base URL of the HTTP referrer, which includes the protocol and authority
       * (host name + port number if specified). Similarly, the target is the base
       * URL of the requested action URL. For the referrer to be considered valid,
       * origin and target must match exactly. Otherwise, the request is likely to
       * be a CSRF attack, and is considered invalid.
       *
       * <p>Example of malicious request originating from embedded image in email:
       * <pre>
       * Request URL: https://teammatesv4.appspot.com/page/instructorCourseDelete?courseid=abcdef
       * Referrer:    https://mail.google.com/mail/u/0/
       *
       * Target: https://teammatesv4.appspot.com
       * Origin: https://mail.google.com
       * </pre>
       * Origin does not match target. This request is invalid.</p>
       *
       * <p>Example of legitimate request originating from instructor courses page:
       * <pre>
       * Request URL: https://teammatesv4.appspot.com/page/instructorCourseDelete?courseid=abcdef
       * Referrer:    https://teammatesv4.appspot.com/page/instructorCoursesPage
       *
       * Target: https://teammatesv4.appspot.com
       * Origin: https://teammatesv4.appspot.com
       * </pre>
       * Origin matches target. This request is valid.</p>
       */
      private boolean isHttpReferrerValid(String referrer) {
          String origin;
          try {
              origin = new Url(referrer).getBaseUrl();
          } catch (AssertionError e) { // due to MalformedURLException
              return false;
          }
  
          String requestUrl = request.getRequestURL().toString();
          String target = new Url(requestUrl).getBaseUrl();
  
          return origin.equals(target);
      }
  
      private boolean isSessionTokenValid(String actualToken) {
          String sessionId = request.getRequestedSessionId();
          if (sessionId == null) {
              // Newly-created session
              sessionId = session.getId();
          }
  
          String expectedToken = CryptoHelper.computeSessionToken(sessionId);
  
          return actualToken.equals(expectedToken);
      }
  
      // These methods are used for user authentication
  
      protected void authenticateUser() {
          UserType currentUser = gateKeeper.getCurrentUser();
          loggedInUser = authenticateAndGetActualUser(currentUser);
          if (isValidUser()) {
              account = authenticateAndGetNominalUser(currentUser);
          }
      }
  
      protected AccountAttributes authenticateAndGetActualUser(UserType currentUser) {
          if (doesUserNeedToLogin(currentUser)) {
              return null;
          }
  
          AccountAttributes loggedInUser = null;
  
          String email = getRequestParamValue(Const.ParamsNames.STUDENT_EMAIL);
          String courseId = getRequestParamValue(Const.ParamsNames.COURSE_ID);
  
          if (currentUser == null) {
              Assumption.assertPostParamNotNull(Const.ParamsNames.REGKEY, regkey);
              loggedInUser = authenticateNotLoggedInUser(email, courseId);
          } else {
              loggedInUser = logic.getAccount(currentUser.id);
              if (doesRegkeyMatchLoggedInUserGoogleId(currentUser.id)) {
                  loggedInUser = createDummyAccountIfUserIsUnregistered(currentUser, loggedInUser);
              }
          }
  
          return loggedInUser;
      }
  
      /**
       * Retrieves registration key from the HTTP request.
       *
       * @return Registration key or null if key not in HTTP request
       */
      protected String getRegkeyFromRequest() {
          return getRequestParamValue(Const.ParamsNames.REGKEY);
      }
  
      protected AccountAttributes createDummyAccountIfUserIsUnregistered(UserType currentUser,
              AccountAttributes loggedInUser) {
          if (loggedInUser == null) { // Unregistered but loggedin user
              return AccountAttributes.builder()
                      .withGoogleId(currentUser.id)
                      .build();
          }
          return loggedInUser;
      }
  
      protected boolean doesRegkeyMatchLoggedInUserGoogleId(
              String loggedInUserId) {
          if (regkey != null && loggedInUserId != null) {
              student = logic.getStudentForRegistrationKey(regkey);
              boolean isKnownKey = student != null;
              if (isKnownKey && student.isRegistered() && !loggedInUserId.equals(student.googleId)) {
                  String expectedId = StringHelper.obscure(student.googleId);
                  expectedId = StringHelper.encrypt(expectedId);
                  String redirectUrl = Config.getAppUrl(Const.ActionURIs.LOGOUT)
                                            .withUserId(StringHelper.encrypt(loggedInUserId))
                                            .withParam(Const.ParamsNames.NEXT_URL, gateKeeper.getLoginUrl(requestUrl))
                                            .withParam(Const.ParamsNames.HINT, expectedId)
                                            .toString();
  
                  setRedirectPage(redirectUrl);
                  return false;
              }
          }
          return true;
      }
  
      protected AccountAttributes authenticateNotLoggedInUser(String email, String courseId) {
          student = logic.getStudentForRegistrationKey(regkey);
          boolean isUnknownKey = student == null;
          boolean isARegisteredUser = !isUnknownKey && student.googleId != null && !student.googleId.isEmpty();
          boolean isMissingAdditionalAuthenticationInfo = email == null || courseId == null;
          boolean isAuthenticationFailure = !isUnknownKey
                                            && (!student.email.equals(email) || !student.course.equals(courseId));
  
          AccountAttributes loggedInUser = null;
  
          if (isUnknownKey) {
              throw new UnauthorizedAccessException("Unknown Registration Key " + regkey);
          } else if (isARegisteredUser) {
              setRedirectPage(gateKeeper.getLoginUrl(requestUrl));
              return null;
          } else if (isNotLegacyLink() && isMissingAdditionalAuthenticationInfo) {
              throw new UnauthorizedAccessException("Insufficient information to authenticate user");
          } else if (isNotLegacyLink() && isAuthenticationFailure) {
              throw new UnauthorizedAccessException("Invalid email/course for given Registration Key");
          } else {
              // Unregistered and not logged in access given to page
              loggedInUser = AccountAttributes.builder()
                      .withEmail(student.email)
                      .build();
          }
  
          return loggedInUser;
      }
  
      private boolean isNotLegacyLink() {
          return !Const.SystemParams.LEGACY_PAGES_WITH_REDUCED_SECURITY.contains(request.getRequestURI());
      }
  
      private boolean doesUserNeedToLogin(UserType currentUser) {
          boolean isGoogleLoginRequired =
                  !Const.SystemParams.PAGES_ACCESSIBLE_WITHOUT_GOOGLE_LOGIN.contains(request.getRequestURI());
          boolean isUserLoggedIn = currentUser != null;
          boolean hasRegkey = getRegkeyFromRequest() != null;
  
  