@Override
  public List<Identity> checkForSimilarUsers(
      PerunSession sess, List<ApplicationFormItemData> formItems) throws PerunException {

    if (sess.getPerunPrincipal().getUser() != null || formItems == null) {
      return new ArrayList<Identity>();
    }

    Set<RichUser> res = new HashSet<RichUser>();
    List<String> attrNames = new ArrayList<String>();
    attrNames.add("urn:perun:user:attribute-def:def:preferredMail");
    attrNames.add("urn:perun:user:attribute-def:def:organization");

    for (ApplicationFormItemData item : formItems) {

      String value = item.getValue();

      if (item.getFormItem().getType().equals(ApplicationFormItem.Type.VALIDATED_EMAIL)) {
        // search by email
        if (value != null && !value.isEmpty())
          res.addAll(
              perun
                  .getUsersManager()
                  .findRichUsersWithAttributesByExactMatch(registrarSession, value, attrNames));
      }
      if (Objects.equals(
          item.getFormItem().getPerunDestinationAttribute(),
          "urn:perun:user:attribute-def:core:displayName")) {
        // search by name
        if (value != null && !value.isEmpty())
          res.addAll(
              perun
                  .getUsersManager()
                  .findRichUsersWithAttributesByExactMatch(registrarSession, value, attrNames));
      }
    }

    return convertToIdentities(new ArrayList<RichUser>(res));
  }
  @Override
  public List<Identity> checkForSimilarUsers(PerunSession sess) throws PerunException {

    // if user known, doesn't actually search and offer joining.
    if (sess.getPerunPrincipal().getUser() != null) {
      return new ArrayList<Identity>();
    }

    // if user known, doesn't actually search and offer joining.
    try {
      perun
          .getUsersManager()
          .getUserByExtSourceNameAndExtLogin(
              registrarSession,
              sess.getPerunPrincipal().getExtSourceName(),
              sess.getPerunPrincipal().getActor());
      return new ArrayList<Identity>();
    } catch (Exception ex) {
      // we don't care, that search failed. That is actually OK case.
    }

    String name = "";
    String mail = "";

    Set<RichUser> res = new HashSet<RichUser>();

    List<String> attrNames = new ArrayList<String>();
    attrNames.add("urn:perun:user:attribute-def:def:preferredMail");
    attrNames.add("urn:perun:user:attribute-def:def:organization");

    mail = sess.getPerunPrincipal().getAdditionalInformations().get("mail");

    if (mail != null) {
      if (mail.contains(";")) {
        String mailSearch[] = mail.split(";");
        for (String m : mailSearch) {
          if (m != null && !m.isEmpty())
            res.addAll(
                perun
                    .getUsersManager()
                    .findRichUsersWithAttributesByExactMatch(registrarSession, m, attrNames));
        }
      } else {
        res.addAll(
            perun
                .getUsersManager()
                .findRichUsersWithAttributesByExactMatch(registrarSession, mail, attrNames));
      }
    }

    // check by mail is more precise, so check by name only if nothing is found.
    if (res.isEmpty()) {

      name = sess.getPerunPrincipal().getAdditionalInformations().get("cn");

      if (name != null && !name.isEmpty())
        res.addAll(
            perun
                .getUsersManager()
                .findRichUsersWithAttributesByExactMatch(registrarSession, name, attrNames));

      name = sess.getPerunPrincipal().getAdditionalInformations().get("displayName");

      if (name != null && !name.isEmpty())
        res.addAll(
            perun
                .getUsersManager()
                .findRichUsersWithAttributesByExactMatch(registrarSession, name, attrNames));
    }

    return convertToIdentities(new ArrayList<RichUser>(res));
  }
  /**
   * Convert RichUsers to Identity objects with obfuscated email address and limited set of ext
   * sources. Service users are removed from the list.
   *
   * @param list RichUsers to convert
   * @return list of Identities without service ones
   * @throws PerunException
   */
  private List<Identity> convertToIdentities(List<RichUser> list) throws PerunException {

    List<Identity> result = new ArrayList<Identity>();

    if (list != null && !list.isEmpty()) {

      for (RichUser u : list) {

        // skip service users
        if (u.isServiceUser()) continue;

        Identity identity = new Identity();
        identity.setName(u.getDisplayName());
        identity.setId(u.getId());

        for (Attribute a : u.getUserAttributes()) {

          if (MailManagerImpl.URN_USER_PREFERRED_MAIL.equals(a.getName())) {
            if (a.getValue() != null && !((String) a.getValue()).isEmpty()) {

              String safeMail = ((String) a.getValue()).split("@")[0];

              if (safeMail.length() > 2) {
                safeMail =
                    safeMail.substring(0, 1)
                        + "****"
                        + safeMail.substring(safeMail.length() - 1, safeMail.length());
              }

              safeMail += "@" + ((String) a.getValue()).split("@")[1];

              identity.setEmail(safeMail);
            }
          } else if ("urn:perun:user:attribute-def:def:organization".equals(a.getName())) {
            if (a.getValue() != null) {
              identity.setOrganization((String) a.getValue());
            }
          }
        }

        List<ExtSource> es = new ArrayList<ExtSource>();
        for (UserExtSource ues : u.getUserExtSources()) {
          if (ues.getExtSource().getType().equals(ExtSourcesManagerEntry.EXTSOURCE_X509)) {
            es.add(ues.getExtSource());
          } else if (ues.getExtSource().getType().equals(ExtSourcesManagerEntry.EXTSOURCE_IDP)) {
            if (ues.getExtSource().getName().equals("https://extidp.cesnet.cz/idp/shibboleth")) {
              // FIXME - hack Social IdP to let us know proper identity source
              String type = ues.getLogin().split("@")[1].split("\\.")[0];
              ues.getExtSource()
                  .setName(
                      "https://extidp.cesnet.cz/idp/shibboleth&authnContextClassRef=urn:cesnet:extidp:authn:"
                          + type);
            } else if (ues.getExtSource().getName().equals("https://login.elixir-czech.org/idp/")) {
              // FIXME - hack Elixir proxy IdP to let us know proper identity source
              String type = ues.getLogin().split("@")[1];
              ues.getExtSource().setName("https://login.elixir-czech.org/idp/@" + type);
            }
            es.add(ues.getExtSource());
          } else if (ues.getExtSource()
              .getType()
              .equals(ExtSourcesManagerEntry.EXTSOURCE_KERBEROS)) {
            es.add(ues.getExtSource());
          }
        }
        identity.setIdentities(es);

        result.add(identity);
      }
    }

    return result;
  }
  @Override
  public List<Identity> checkForSimilarUsers(PerunSession sess, int appId) throws PerunException {

    String email = "";
    String name = "";
    List<RichUser> result = new ArrayList<RichUser>();

    List<String> attrNames = new ArrayList<String>();
    attrNames.add("urn:perun:user:attribute-def:def:preferredMail");
    attrNames.add("urn:perun:user:attribute-def:def:organization");

    Application app = registrarManager.getApplicationById(registrarSession, appId);

    if (app.getGroup() == null) {
      if (!AuthzResolver.isAuthorized(sess, Role.VOADMIN, app.getVo())) {
        if (sess.getPerunPrincipal().getUser() != null) {
          // check if application to find similar users by belongs to user
          if (!sess.getPerunPrincipal().getUser().equals(app.getUser()))
            throw new PrivilegeException("checkForSimilarUsers");
        } else {
          if (!sess.getPerunPrincipal().getExtSourceName().equals(app.getExtSourceName())
              && !sess.getPerunPrincipal().getActor().equals(app.getCreatedBy()))
            throw new PrivilegeException("checkForSimilarUsers");
        }
      }
    } else {
      if (!AuthzResolver.isAuthorized(sess, Role.VOADMIN, app.getVo())
          && !AuthzResolver.isAuthorized(sess, Role.GROUPADMIN, app.getGroup())) {
        if (sess.getPerunPrincipal().getUser() != null) {
          // check if application to find similar users by belongs to user
          if (!sess.getPerunPrincipal().getUser().equals(app.getUser()))
            throw new PrivilegeException("checkForSimilarUsers");
        } else {
          if (!sess.getPerunPrincipal().getExtSourceName().equals(app.getExtSourceName())
              && !sess.getPerunPrincipal().getActor().equals(app.getCreatedBy()))
            throw new PrivilegeException("checkForSimilarUsers");
        }
      }
    }

    // only for initial VO applications if user==null
    if (app.getType().equals(Application.AppType.INITIAL)
        && app.getGroup() == null
        && app.getUser() == null) {

      try {
        User u =
            perun
                .getUsersManager()
                .getUserByExtSourceNameAndExtLogin(
                    registrarSession, app.getExtSourceName(), app.getCreatedBy());
        if (u != null) {
          // user connected his identity after app creation and before it's approval.
          // do not show error message in GUI by returning an empty array.
          return convertToIdentities(result);
        }
      } catch (Exception ex) {
        // we don't care, let's try to search by name
      }

      List<ApplicationFormItemData> data = registrarManager.getApplicationDataById(sess, appId);

      // search by email, which should be unique (check is more precise)
      for (ApplicationFormItemData item : data) {
        if ("urn:perun:user:attribute-def:def:preferredMail"
            .equals(item.getFormItem().getPerunDestinationAttribute())) {
          email = item.getValue();
        }
        if (email != null && !email.isEmpty()) break;
      }

      List<RichUser> users =
          (email != null && !email.isEmpty())
              ? perun
                  .getUsersManager()
                  .findRichUsersWithAttributesByExactMatch(registrarSession, email, attrNames)
              : new ArrayList<RichUser>();

      if (users != null && !users.isEmpty()) {
        // found by preferredMail
        return convertToIdentities(users);
      }

      // search by different mail

      email = ""; // clear previous value
      for (ApplicationFormItemData item : data) {
        if ("urn:perun:member:attribute-def:def:mail"
            .equals(item.getFormItem().getPerunDestinationAttribute())) {
          email = item.getValue();
        }
        if (email != null && !email.isEmpty()) break;
      }

      users =
          (email != null && !email.isEmpty())
              ? perun
                  .getUsersManager()
                  .findRichUsersWithAttributesByExactMatch(registrarSession, email, attrNames)
              : new ArrayList<RichUser>();
      if (users != null && !users.isEmpty()) {
        // found by member mail
        return convertToIdentities(users);
      }

      // continue to search by display name

      for (ApplicationFormItemData item : data) {
        if (RegistrarManagerImpl.URN_USER_DISPLAY_NAME.equals(
            item.getFormItem().getPerunDestinationAttribute())) {
          name = item.getValue();
          // use parsed name to drop mistakes on IDP side
          try {
            if (name != null && !name.isEmpty()) {
              Map<String, String> nameMap = Utils.parseCommonName(name);
              // drop name titles to spread search
              String newName = "";
              if (nameMap.get("firstName") != null && !nameMap.get("firstName").isEmpty()) {
                newName += nameMap.get("firstName") + " ";
              }
              if (nameMap.get("lastName") != null && !nameMap.get("lastName").isEmpty()) {
                newName += nameMap.get("lastName");
              }
              // fill parsed name instead of input
              if (newName != null && !newName.isEmpty()) {
                name = newName;
              }
            }
          } catch (Exception ex) {
            log.error(
                "[REGISTRAR] Unable to parse new user's display/common name when searching for similar users. Exception: {}",
                ex);
          }
          if (name != null && !name.isEmpty()) break;
        }
      }

      users =
          (name != null && !name.isEmpty())
              ? perun
                  .getUsersManager()
                  .findRichUsersWithAttributesByExactMatch(registrarSession, name, attrNames)
              : new ArrayList<RichUser>();
      if (users != null && !users.isEmpty()) {
        // found by member display name
        return convertToIdentities(users);
      }

      // continue to search by last name

      name = ""; // clear previous value
      for (ApplicationFormItemData item : data) {
        if (RegistrarManagerImpl.URN_USER_LAST_NAME.equals(
            item.getFormItem().getPerunDestinationAttribute())) {
          name = item.getValue();
          if (name != null && !name.isEmpty()) break;
        }
      }

      if (name != null && !name.isEmpty()) {
        // what was found by name
        return convertToIdentities(
            perun
                .getUsersManager()
                .findRichUsersWithAttributesByExactMatch(registrarSession, name, attrNames));
      } else {
        // not found by name
        return convertToIdentities(result);
      }

    } else {
      // not found, since not proper type of application to check users for
      return convertToIdentities(result);
    }
  }