content: /*
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.
   * The ASF licenses this file to You under the Apache License, Version 2.0
   * (the "License"); you may not use this file except in compliance with
   * the License.  You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package org.apache.atlas.web.dao;
  
  import com.google.common.annotations.VisibleForTesting;
  import org.apache.atlas.ApplicationProperties;
  import org.apache.atlas.AtlasException;
  import org.apache.atlas.web.model.User;
  import org.apache.atlas.web.security.AtlasAuthenticationException;
  import org.apache.commons.configuration.Configuration;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  import org.springframework.security.core.AuthenticationException;
  import org.springframework.security.core.GrantedAuthority;
  import org.springframework.security.core.authority.SimpleGrantedAuthority;
  import org.springframework.security.core.userdetails.UsernameNotFoundException;
  import org.springframework.security.crypto.bcrypt.BCrypt;
  import org.springframework.security.crypto.codec.Hex;
  import org.springframework.security.crypto.codec.Utf8;
  import org.springframework.stereotype.Repository;
  import org.springframework.util.StringUtils;
  
  import javax.annotation.PostConstruct;
  
  import java.io.IOException;
  import java.io.InputStream;
  import java.nio.charset.StandardCharsets;
  import java.security.MessageDigest;
  import java.security.NoSuchAlgorithmException;
  import java.util.ArrayList;
  import java.util.List;
  import java.util.Properties;
  
  @Repository
  public class UserDao {
      private static final Logger LOG = LoggerFactory.getLogger(UserDao.class);
  
      private static final String  DEFAULT_USER_CREDENTIALS_PROPERTIES = "users-credentials.properties";
      private static       boolean v1ValidationEnabled                 = true;
      private static       boolean v2ValidationEnabled                 = true;
  
      private Properties userLogins = new Properties();
  
      public static String encrypt(String password) {
          String ret = null;
  
          try {
              ret = BCrypt.hashpw(password, BCrypt.gensalt());
          } catch (Throwable excp) {
              LOG.warn("UserDao.encrypt(): failed", excp);
          }
  
          return ret;
      }
  
      public static boolean checkEncrypted(String password, String encryptedPwd, String userName) {
          boolean ret = checkPasswordBCrypt(password, encryptedPwd);
  
          if (!ret && v2ValidationEnabled) {
              ret = checkPasswordSHA256WithSalt(password, encryptedPwd, userName);
          }
  
          if (!ret && v1ValidationEnabled) {
              ret = checkPasswordSHA256(password, encryptedPwd);
          }
  
          return ret;
      }
  
      public static String encodePassword(String rawPass, Object salt) {
          String        saltedPass    = mergePasswordAndSalt(rawPass, salt, false);
          MessageDigest messageDigest = getMessageDigest();
          byte[]        digest        = messageDigest.digest(Utf8.encode(saltedPass));
  
          return new String(Hex.encode(digest));
      }
  
      protected static MessageDigest getMessageDigest() throws IllegalArgumentException {
          try {
              return MessageDigest.getInstance("SHA-256");
          } catch (NoSuchAlgorithmException var2) {
              throw new IllegalArgumentException("No such algorithm [SHA-256 ]");
          }
      }
  
      protected static String mergePasswordAndSalt(String password, Object salt, boolean strict) {
          if (!StringUtils.hasText(password)) {
              password = "";
          }
  
          if (strict && salt != null && (salt.toString().lastIndexOf("{") != -1 || salt.toString().lastIndexOf("}") != -1)) {
              throw new IllegalArgumentException("Cannot use { or } in salt.toString()");
          } else {
              return StringUtils.hasText(salt.toString()) ? password + "{" + salt + "}" : password;
          }
      }
  
      @PostConstruct
      public void init() {
          loadFileLoginsDetails();
      }
  
      public User loadUserByUsername(final String username) throws AuthenticationException {
          String userdetailsStr = userLogins.getProperty(username);
  
          if (userdetailsStr == null || userdetailsStr.isEmpty()) {
              throw new UsernameNotFoundException("Username not found." + username);
          }
  
          String   password;
          String   role;
          String[] dataArr = userdetailsStr.split("::");
  
          if (dataArr.length == 2) {
              role     = dataArr[0];
              password = dataArr[1];
          } else {
              LOG.error("User role credentials is not set properly for {}", username);
  
              throw new AtlasAuthenticationException("User role credentials is not set properly for " + username);
          }
  
          List<GrantedAuthority> grantedAuths = new ArrayList<>();
  
          if (StringUtils.hasText(role)) {
              grantedAuths.add(new SimpleGrantedAuthority(role));
          } else {
              LOG.error("User role credentials is not set properly for {}", username);
  
              throw new AtlasAuthenticationException("User role credentials is not set properly for " + username);
          }
  
          return new User(username, password, grantedAuths);
      }
  
      @VisibleForTesting
      public void setUserLogins(Properties userLogins) {
          this.userLogins = userLogins;
      }
  
      void loadFileLoginsDetails() {
          userLogins.clear();
  
          InputStream inStr = null;
  
          try {
              Configuration configuration = ApplicationProperties.get();
  
              v1ValidationEnabled = configuration.getBoolean("atlas.authentication.method.file.v1-validation.enabled", true);
              v2ValidationEnabled = configuration.getBoolean("atlas.authentication.method.file.v2-validation.enabled", true);
  
              inStr = ApplicationProperties.getFileAsInputStream(configuration, "atlas.authentication.method.file.filename", DEFAULT_USER_CREDENTIALS_PROPERTIES);
  
              userLogins.load(inStr);
          } catch (IOException | AtlasException e) {
              LOG.error("Error while reading user.properties file", e);
  
              throw new RuntimeException(e);
          } finally {
              if (inStr != null) {
                  try {
                      inStr.close();
                  } catch (Exception excp) {
                      // ignore
                  }
              }
          }
      }
  
      private static boolean checkPasswordBCrypt(String password, String encryptedPwd) {
          LOG.debug("checkPasswordBCrypt()");
  
          boolean ret = false;
  
          try {
              ret = BCrypt.checkpw(password, encryptedPwd);
          } catch (Throwable excp) {
              LOG.debug("checkPasswordBCrypt(): failed", excp);
          }
  
          return ret;
      }
  
      private static boolean checkPasswordSHA256WithSalt(String password, String encryptedPwd, String salt) {
          LOG.debug("checkPasswordSHA256WithSalt()");
  
          boolean ret = false;
  
          try {
              String hash = encodePassword(password, salt);
  
              ret = hash != null && hash.equals(encryptedPwd);
          } catch (Throwable excp) {
              LOG.debug("checkPasswordSHA256WithSalt(): failed", excp);
          }
  
          return ret;
      }
  
      private static boolean checkPasswordSHA256(String password, String encryptedPwd) {
          LOG.debug("checkPasswordSHA256()");
  
          boolean ret = false;
  
          try {
              String hash = getSha256Hash(password);
  
              ret = hash != null && hash.equals(encryptedPwd);
          } catch (Throwable excp) {
              LOG.debug("checkPasswordSHA256(): failed", excp);
          }
  
          return ret;
      }
  
      private static String getSha256Hash(String base) throws AtlasAuthenticationException {
          try {
              MessageDigest digest    = MessageDigest.getInstance("SHA-256");
              byte[]        hash      = digest.digest(base.getBytes(StandardCharsets.UTF_8));
              StringBuilder hexString = new StringBuilder();
  
              for (byte aHash : hash) {
                  String hex = Integer.toHexString(0xff & aHash);
  
                  if (hex.length() == 1) {
                      hexString.append('0');
                  }
  
                  hexString.append(hex);
              }
  
              return hexString.toString();
          } catch (Exception ex) {
              throw new AtlasAuthenticationException("Exception while encoding password.", ex);
          }
      }
  }
  