content: package io.aiven.klaw.service;
  
  import static io.aiven.klaw.error.KlawErrorMessages.*;
  import static io.aiven.klaw.helpers.KwConstants.DEFAULT_TENANT_ID;
  import static io.aiven.klaw.model.enums.AuthenticationType.ACTIVE_DIRECTORY;
  import static io.aiven.klaw.model.enums.RolesType.SUPERADMIN;
  import static org.springframework.beans.BeanUtils.copyProperties;
  
  import io.aiven.klaw.config.ManageDatabase;
  import io.aiven.klaw.constants.UriConstants;
  import io.aiven.klaw.dao.RegisterUserInfo;
  import io.aiven.klaw.dao.UserInfo;
  import io.aiven.klaw.helpers.HandleDbRequests;
  import io.aiven.klaw.helpers.KwConstants;
  import io.aiven.klaw.model.enums.ApiResultStatus;
  import io.aiven.klaw.model.enums.NewUserStatus;
  import io.aiven.klaw.model.requests.RegisterUserInfoModel;
  import jakarta.servlet.http.HttpServletRequest;
  import jakarta.servlet.http.HttpServletResponse;
  import java.io.IOException;
  import java.sql.Timestamp;
  import java.util.Collection;
  import java.util.Objects;
  import java.util.Set;
  import java.util.UUID;
  import lombok.extern.slf4j.Slf4j;
  import org.apache.commons.lang3.tuple.Pair;
  import org.springframework.beans.factory.annotation.Autowired;
  import org.springframework.beans.factory.annotation.Value;
  import org.springframework.security.authentication.AbstractAuthenticationToken;
  import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
  import org.springframework.security.core.GrantedAuthority;
  import org.springframework.security.core.context.SecurityContextHolder;
  import org.springframework.security.core.userdetails.UserDetails;
  import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
  import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
  import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
  import org.springframework.stereotype.Service;
  
  @Service
  @Slf4j
  public class UiControllerLoginService {
  
    @Value("${klaw.login.authentication.type}")
    private String authenticationType;
  
    @Value("${spring.ad.domain:#{null}}")
    private String adDomain;
  
    @Value("${klaw.enable.authorization.ad:false}")
    private boolean enableUserAuthorizationFromAD;
  
    @Value("${klaw.ad.name.attribute:name}")
    private String nameAttribute;
  
    @Value("${klaw.enable.sso:false}")
    private String ssoEnabled;
  
    @Value("${klaw.installation.type:onpremise}")
    private String kwInstallationType;
  
    @Autowired ManageDatabase manageDatabase;
  
    @Autowired CommonUtilsService commonUtilsService;
  
    @Autowired(required = false)
    private OAuth2AuthorizedClientService authorizedClientService;
  
    public String getReturningPage(String uri, String userName) {
      try {
        if (userName != null) {
          HandleDbRequests reqsHandle = manageDatabase.getHandleDbRequests();
          UserInfo userInfo = reqsHandle.getUsersInfo(userName);
  
          if (userInfo == null) {
            SecurityContextHolder.getContext().setAuthentication(null);
  
            if (ACTIVE_DIRECTORY.value.equals(authenticationType)) {
              return UriConstants.REGISTER_LDAP_PAGE;
            }
            return UriConstants.REGISTER_PAGE;
          }
  
          log.debug("Authenticated user : {}", userName);
          if (UriConstants.LOGIN_PAGE.equals(uri)
              || UriConstants.HOME_PAGE.equals(uri)
              || UriConstants.REGISTER_PAGE.equals(uri)
              || uri.contains(UriConstants.REGISTRATION_REVIEW)
              || UriConstants.FORGOT_PASSWORD_PAGE.equals(uri)
              || UriConstants.NEW_AD_USER_PAGE.equals(uri)) {
            return UriConstants.INDEX_PAGE;
          }
          return uri;
        }
  
        return UriConstants.DEFAULT_PAGE;
  
      } catch (Exception e) {
        log.error("Exception:", e);
        if (UriConstants.LOGIN_PAGE.equals(uri)
            || UriConstants.REGISTER_PAGE.equals(uri)
            || UriConstants.REGISTER_LDAP_PAGE.equals(uri)
            || uri.contains(UriConstants.REGISTRATION_REVIEW)
            || UriConstants.FORGOT_PASSWORD_PAGE.equals(uri)
            || UriConstants.NEW_AD_USER_PAGE.equals(uri)
            || UriConstants.TERMS_PAGE.equals(uri)
            || UriConstants.FEEDBACK_PAGE.equals(uri)) return uri;
  
        return UriConstants.DEFAULT_PAGE;
      }
    }
  
    public String checkAnonymousLogin(
        String uri,
        AbstractAuthenticationToken abstractAuthenticationToken,
        HttpServletResponse response,
        String userName) {
      DefaultOAuth2User defaultOAuth2User = null;
      String nameAttribute = "name";
      String emailAttribute = "email";
      String emailAttributeVal = null;
      Collection<? extends GrantedAuthority> authorities = null;
  
      Object principal = abstractAuthenticationToken.getPrincipal();
      // Extract attributes for user verification/registration
      if (abstractAuthenticationToken instanceof OAuth2AuthenticationToken) {
        if (principal instanceof DefaultOAuth2User) {
          defaultOAuth2User = (DefaultOAuth2User) principal;
          nameAttribute = (String) defaultOAuth2User.getAttributes().get(nameAttribute);
          emailAttributeVal = (String) defaultOAuth2User.getAttributes().get(emailAttribute);
          authorities = defaultOAuth2User.getAuthorities();
        }
      } else if (abstractAuthenticationToken instanceof UsernamePasswordAuthenticationToken) {
        nameAttribute = abstractAuthenticationToken.getName();
        authorities = abstractAuthenticationToken.getAuthorities();
      }
  
      // if user does not exist in db
      if (manageDatabase.getHandleDbRequests().getUsersInfo(userName) == null) {
        Pair<Boolean, String> roleValidationPair = Pair.of(null, null);
        // If enableUserAuthorizationFromAD is true, retrieve roles from AD token and match
        // with klaw metadata
        if (enableUserAuthorizationFromAD) {
          Set<String> klawRoles =
              manageDatabase.getRolesPermissionsPerTenant(DEFAULT_TENANT_ID).keySet();
  
          // extract role from AD token authorities
          if ("true".equals(ssoEnabled) && defaultOAuth2User != null) {
            roleValidationPair =
                getRoleFromTokenAuthorities(authorities, userName, klawRoles, response);
            if (!roleValidationPair.getLeft()) {
              sendResponse(response, roleValidationPair);
              return UriConstants.OAUTH_LOGIN;
            }
          }
        }
        return registerStagingUser(
            userName, nameAttribute, roleValidationPair.getRight(), emailAttributeVal);
      }
  
      if (abstractAuthenticationToken.isAuthenticated()) {
        return uri;
      } else {
        return UriConstants.OAUTH_LOGIN;
      }
    }
  
    // redirect the user to login page with error display
    private static void sendResponse(
        HttpServletResponse response, Pair<Boolean, String> validationPair) {
      try {
        // Display error to the user based on error code
        response.sendRedirect(UriConstants.LOGIN_ERROR_CODE + validationPair.getRight());
      } catch (IOException ex) {
        log.error("Ignore error from response redirect !");
      }
    }
  
    private Pair<Boolean, String> getRoleFromTokenAuthorities(
        Collection<? extends GrantedAuthority> authorities,
        String userName,
        Set<String> klawRoles,
        HttpServletResponse response) {
      String roleFromClaim = null;
      int claimMatched = 0;
      for (GrantedAuthority authority : authorities) {
        String authorityStr = authority.getAuthority();
        int indexOfRoleSeparator = authorityStr.indexOf("_");
        if (indexOfRoleSeparator > 0) {
          String authorityRole = authorityStr.substring(indexOfRoleSeparator + 1); // ex : ROLE_USER
          if (klawRoles.stream().anyMatch(authorityRole::equalsIgnoreCase)) {
            claimMatched++;
            roleFromClaim = authorityRole;
          }
        }
      }
  
      Pair<Boolean, String> claimValidationPair = validateClaims(claimMatched, userName, "roles");
      if (claimValidationPair.getLeft()) { // valid claim
        return Pair.of(Boolean.TRUE, roleFromClaim);
      } else {
        return claimValidationPair;
      }
    }
  
    // if no claims matched, or multiple claims matched, deny registering/login to the user. claimType
    // can be 'roles' or 'teams'.
    private Pair<Boolean, String> validateClaims(
        int claimMatched, String userName, String claimType) {
      String errorCode = "";
      Boolean validClaim = Boolean.TRUE;
      if (claimMatched == 0) {
        if (claimType.equals("roles")) {
          errorCode = ACTIVE_DIRECTORY_ERR_CODE_101;
          log.error(AD_ERROR_101_NO_MATCHING_ROLE + "{}", userName);
        }
        validClaim = Boolean.FALSE;
      } else if (claimMatched > 1) {
        if (claimType.equals("roles")) {
          errorCode = ACTIVE_DIRECTORY_ERR_CODE_103;
          log.error(AD_ERROR_103_MULTIPLE_MATCHING_ROLE + "{}", userName);
        }
        validClaim = Boolean.FALSE;
      }
      return Pair.of(validClaim, errorCode);
    }
  
    public String checkAuth(
        String uri,
        HttpServletRequest request,
        HttpServletResponse response,
        AbstractAuthenticationToken abstractAuthenticationToken) {
      String userName = null;
      if (abstractAuthenticationToken instanceof OAuth2AuthenticationToken) {
        DefaultOAuth2User defaultOAuth2User =
            (DefaultOAuth2User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        userName = commonUtilsService.getUserName(defaultOAuth2User);
      } else if (abstractAuthenticationToken instanceof UsernamePasswordAuthenticationToken) {
        userName =
            ((UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal())
                .getUsername();
      }
  
      if (UriConstants.TENANTS_PAGE.equals(uri)) {
        if (SUPERADMIN
                .name()
                .equals(manageDatabase.getHandleDbRequests().getUsersInfo(userName).getRole())
            && commonUtilsService.getTenantId(userName) == DEFAULT_TENANT_ID) {
          return getReturningPage(uri, userName);
        } else {
          uri = UriConstants.INDEX;
        }
      }
  
      if ("true".equals(ssoEnabled)) {
        if (uri.contains(UriConstants.REGISTER)
            || uri.equals(UriConstants.REGISTRATION_REVIEW_PAGE)
            || uri.equals(UriConstants.FORGOT_PASSWORD)
            || uri.equals(UriConstants.FORGOT_PASSWORD_PAGE)) {
          return uri;
        } else {
          if (abstractAuthenticationToken instanceof OAuth2AuthenticationToken) {
            return checkAnonymousLogin(uri, abstractAuthenticationToken, response, userName);
          } else {
            return UriConstants.OAUTH_LOGIN;
          }
        }
      } else {
        if (uri.contains(UriConstants.REGISTER)
            || uri.equals(UriConstants.REGISTRATION_REVIEW_PAGE)
            || uri.equals(UriConstants.FORGOT_PASSWORD)
            || uri.equals(UriConstants.FORGOT_PASSWORD_PAGE)) {
          return uri;
        } else {
          if (ACTIVE_DIRECTORY.value.equals(authenticationType)) {
            return checkAnonymousLogin(uri, abstractAuthenticationToken, response, userName);
          }
          return getReturningPage(uri, userName);
        }
      }
    }
  
    // register user with staging status, and forward to signup
    public String registerStagingUser(
        String userName, Object fullName, String roleFromAD, String emailClaim) {
      try {
        log.info("User found in SSO/AD and not in Klaw db :{}", userName);
        String existingRegistrationId =
            manageDatabase.getHandleDbRequests().getRegistrationId(userName);
  
        if (existingRegistrationId != null) {
          if ("PENDING_ACTIVATION".equals(existingRegistrationId)) {
            return UriConstants.REDIRECT + UriConstants.REGISTRATION_REVIEW;
          } else {
            return UriConstants.REDIRECT
                + UriConstants.REGISTER_USER_REGISTRATION_ID
                + existingRegistrationId;
          }
        } else {
          String randomId = UUID.randomUUID().toString();
  
          RegisterUserInfoModel registerUserInfoModel = new RegisterUserInfoModel();
          registerUserInfoModel.setRegistrationId(randomId);
          registerUserInfoModel.setStatus(NewUserStatus.STAGING.value);
          registerUserInfoModel.setTeam(KwConstants.STAGINGTEAM);
          registerUserInfoModel.setRole(
              Objects.requireNonNullElse(roleFromAD, KwConstants.USER_ROLE));
          registerUserInfoModel.setRegisteredTime(new Timestamp(System.currentTimeMillis()));
  
          if (emailClaim != null) {
            registerUserInfoModel.setMailid(emailClaim);
          }
  
          registerUserInfoModel.setUsername(userName);
          registerUserInfoModel.setPwd("");
          if (fullName != null) {
            String fullNameStr =
                ((String) fullName).replace(",", ""); // Look for other characters in names
            registerUserInfoModel.setFullname(fullNameStr);
          }
  
          RegisterUserInfo registerUserInfo = new RegisterUserInfo();
          copyProperties(registerUserInfoModel, registerUserInfo);
          registerUserInfo.setTeamId(
              manageDatabase.getTeamIdFromTeamName(DEFAULT_TENANT_ID, registerUserInfo.getTeam()));
          registerUserInfo.setTenantId(DEFAULT_TENANT_ID);
  
          String result = manageDatabase.getHandleDbRequests().registerUserForAD(registerUserInfo);
          if (result.equals(ApiResultStatus.SUCCESS.value))
            return UriConstants.REDIRECT + UriConstants.REGISTER_USER_REGISTRATION_ID + randomId;
          else return "";
        }
      } catch (Exception e) {
        log.error("Unable to find mail/name fields.", e);
        return UriConstants.REGISTER_LDAP_PAGE;
      }
    }
  }
  