<commit_before>package uk.gov.tna.dri.preingest.loader.auth

import com.unboundid.ldap.sdk.{LDAPConnectionOptions, Attribute, SearchScope, LDAPConnection}
import grizzled.slf4j.Logging
import resource._
import uk.gov.tna.dri.preingest.loader.SettingsImpl


trait LDAPUserManager extends AuthManager[String, User] with Logging {

  protected val settings: SettingsImpl

  private val LDAP_OPTS = {
    val opts = new LDAPConnectionOptions()
    opts.setConnectTimeoutMillis(settings.Auth.ldapTimeoutConnection)
    opts.setResponseTimeoutMillis(settings.Auth.ldapTimeoutRequest)
    opts
  }

  /**
   * Attempts to find a user by DN from LDAP
   */
  def find(key: String): Option[User] = {
    ldapOperation(settings.Auth.ldapBindUser, settings.Auth.ldapBindPassword, findUserByDn(_, key)) match {

      case Left(ts) =>
        ts.map(error("Could not find user by DN in LDAP", _))
        None

      case Right(maybeUserAttrs) =>
        maybeUserAttrs.map {
          case (dn, uid, maybeEmail) =>
            User(dn, uid, "UNKNOWN PASSWORD", maybeEmail)
        }
    }
  }

  /**
   * Validates a user account by username/password against LDAP
   *
   * @param userName The username to validate
   * @param password The password to validate
   *
   * @return A User object representing the user
   */
  def validate(userName: String, password: String): Option[User] = {

    ldapOperation(settings.Auth.ldapBindUser, settings.Auth.ldapBindPassword, findUserDN(_, userName)) match {
      case Left(ts) =>
        ts.map(error("Could not find user in LDAP", _))
        None

      case Right(maybeUserDn) =>
        maybeUserDn.flatMap {
          userDn =>
            ldapOperation(userDn, password, getUser(_, userName)) match {

              case Left(ts) =>
                ts.map(error("Could not retrieve user properties from LDAP", _))
                None

              case Right(userCstr) =>
                userCstr.map(_(password))
            }
        }
    }
  }

  private def findUserByDn(ldap: LDAPConnection, dn: String) = ldapSearch(ldap, ldapUserByDnFilter(dn), Seq(settings.Auth.ldapUserAttributeUid, settings.Auth.ldapUserAttributeEmail)).map(attrs => (dn, attrs(settings.Auth.ldapUserAttributeUid).getValue, attrs.get(settings.Auth.ldapUserAttributeEmail).map(_.getValue)))

  /**
   * Finds a User in LDAP
   * and returns their Distinguished Name
   *
   * @param ldap An LDAP connection which is already bound
   * @param userName
   *
   * @return Some(dn) or None if the user cannot be found in LDAP
   */
  private def findUserDN(ldap: LDAPConnection, userName: String): Option[String] = ldapSearch(ldap, ldapUserFilter(userName), Seq(settings.Auth.ldapUserAttributeDN)).flatMap(_.get(settings.Auth.ldapUserAttributeDN).map(_.getValue))

  /**
   * Gets a User from LDAP
   *
   * @param ldap An LDAP connection which is already bound
   * @param userName The username of the user to retrieve from LDAP
   *
   * @return A completion function that when given
   *         the users password returns
   *         a User
   */
  private def getUser(ldap: LDAPConnection, userName: String): Option[String => User] = {
    ldapSearch(ldap, ldapUserFilter(userName), Seq(settings.Auth.ldapUserAttributeDN, settings.Auth.ldapUserAttributeEmail)).map {
      attrs =>
        (password: String) =>
          User(attrs(settings.Auth.ldapUserAttributeDN).getValue, userName, password, attrs.get(settings.Auth.ldapUserAttributeEmail).map(_.getValue))
    }
  }

  /**
   * Perform a managed LDAP operation
   *
   * @param bindUser The username binding for connecting to the LDAP
   * @param bindPassword The password for the user binding when connecting to the LDAP
   * @param op A function which operates on an LDAP connection
   *
   * @return Either a sequence of exceptions or the result of $op
   */
  private def ldapOperation[T](bindUser: String, bindPassword: String, op: (LDAPConnection) => T): Either[Seq[Throwable], T] = {
    managed(new LDAPConnection(LDAP_OPTS, settings.Auth.ldapServer, settings.Auth.ldapPort, bindUser, bindPassword)).map(op).either
  }

  /**
   * Perform an LDAP search on a number of attributes
   *
   * @param ldap Connection to the LDAP
   * @param filter Filter to search
   * @param attributes Attributes to return from the search result classes
   *
   * @return Map of key/value where the keys is the requested attributes, or None if no search results<commit_msg>Fix initialisation problem<commit_after>package uk.gov.tna.dri.preingest.loader.auth

import com.unboundid.ldap.sdk.{LDAPConnectionOptions, Attribute, SearchScope, LDAPConnection}
import grizzled.slf4j.Logging
import resource._
import uk.gov.tna.dri.preingest.loader.SettingsImpl


trait LDAPUserManager extends AuthManager[String, User] with Logging {

  protected val settings: SettingsImpl

  /**
   * Attempts to find a user by DN from LDAP
   */
  def find(key: String): Option[User] = {
    ldapOperation(settings.Auth.ldapBindUser, settings.Auth.ldapBindPassword, findUserByDn(_, key)) match {

      case Left(ts) =>
        ts.map(error("Could not find user by DN in LDAP", _))
        None

      case Right(maybeUserAttrs) =>
        maybeUserAttrs.map {
          case (dn, uid, maybeEmail) =>
            User(dn, uid, "UNKNOWN PASSWORD", maybeEmail)
        }
    }
  }

  /**
   * Validates a user account by username/password against LDAP
   *
   * @param userName The username to validate
   * @param password The password to validate
   *
   * @return A User object representing the user
   */
  def validate(userName: String, password: String): Option[User] = {

    ldapOperation(settings.Auth.ldapBindUser, settings.Auth.ldapBindPassword, findUserDN(_, userName)) match {
      case Left(ts) =>
        ts.map(error("Could not find user in LDAP", _))
        None

      case Right(maybeUserDn) =>
        maybeUserDn.flatMap {
          userDn =>
            ldapOperation(userDn, password, getUser(_, userName)) match {

              case Left(ts) =>
                ts.map(error("Could not retrieve user properties from LDAP", _))
                None

              case Right(userCstr) =>
                userCstr.map(_(password))
            }
        }
    }
  }

  private def findUserByDn(ldap: LDAPConnection, dn: String) = ldapSearch(ldap, ldapUserByDnFilter(dn), Seq(settings.Auth.ldapUserAttributeUid, settings.Auth.ldapUserAttributeEmail)).map(attrs => (dn, attrs(settings.Auth.ldapUserAttributeUid).getValue, attrs.get(settings.Auth.ldapUserAttributeEmail).map(_.getValue)))

  /**
   * Finds a User in LDAP
   * and returns their Distinguished Name
   *
   * @param ldap An LDAP connection which is already bound
   * @param userName
   *
   * @return Some(dn) or None if the user cannot be found in LDAP
   */
  private def findUserDN(ldap: LDAPConnection, userName: String): Option[String] = ldapSearch(ldap, ldapUserFilter(userName), Seq(settings.Auth.ldapUserAttributeDN)).flatMap(_.get(settings.Auth.ldapUserAttributeDN).map(_.getValue))

  /**
   * Gets a User from LDAP
   *
   * @param ldap An LDAP connection which is already bound
   * @param userName The username of the user to retrieve from LDAP
   *
   * @return A completion function that when given
   *         the users password returns
   *         a User
   */
  private def getUser(ldap: LDAPConnection, userName: String): Option[String => User] = {
    ldapSearch(ldap, ldapUserFilter(userName), Seq(settings.Auth.ldapUserAttributeDN, settings.Auth.ldapUserAttributeEmail)).map {
      attrs =>
        (password: String) =>
          User(attrs(settings.Auth.ldapUserAttributeDN).getValue, userName, password, attrs.get(settings.Auth.ldapUserAttributeEmail).map(_.getValue))
    }
  }

  /**
   * Perform a managed LDAP operation
   *
   * @param bindUser The username binding for connecting to the LDAP
   * @param bindPassword The password for the user binding when connecting to the LDAP
   * @param op A function which operates on an LDAP connection
   *
   * @return Either a sequence of exceptions or the result of $op
   */
  private def ldapOperation[T](bindUser: String, bindPassword: String, op: (LDAPConnection) => T): Either[Seq[Throwable], T] = {
    val opts = new LDAPConnectionOptions()
    opts.setConnectTimeoutMillis(settings.Auth.ldapTimeoutConnection)
    opts.setResponseTimeoutMillis(settings.Auth.ldapTimeoutRequest)

    managed(new LDAPConnection(opts, settings.Auth.ldapServer, settings.Auth.ldapPort, bindUser, bindPassword)).map(op).either
  }

  /**
   * Perform an LDAP search on a number of attributes
   *
   * @param ldap Connection to the LDAP
   * @param filter Filter to search
   * @param attributes Attributes to return from the search result classes
   *
   * @return Map of key/value where the keys is the requested attributes, or None if no search results
{'fraction_non_alphanumeric': 0.09684710136738614, 'fraction_numerical': 0.00022601423889705052, 'mean_word_length': 3.2877906976744184, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 29, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '260643', 'n_tokens_mistral': 2653, 'n_tokens_neox': 2618, 'n_words': 739}