gt:                         .filter(userName -> userName.startsWith(prefix))
context: /*
   *  eXist Open Source Native XML Database
   *  Copyright (C) 2010-2011 The eXist Project
   *  http://exist-db.org
   *  
   *  This program is free software; you can redistribute it and/or
   *  modify it under the terms of the GNU Lesser General Public License
   *  as published by the Free Software Foundation; either version 2
   *  of the License, or (at your option) any later version.
   *  
   *  This program is distributed in the hope that it will be useful,
   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   *  GNU Lesser General Public License for more details.
   *  
   *  You should have received a copy of the GNU Lesser General Public License
   *  along with this program; if not, write to the Free Software
   *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
   *  
   *  $Id$
   */
  package org.exist.security.internal;
  
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.Collections;
  import java.util.List;
  import java.util.stream.Collectors;
  import org.apache.logging.log4j.LogManager;
  import org.apache.logging.log4j.Logger;
  import org.exist.EXistException;
  import org.exist.config.Configuration;
  import org.exist.config.ConfigurationException;
  import org.exist.config.Reference;
  import org.exist.config.ReferenceImpl;
  import org.exist.security.AXSchemaType;
  import org.exist.security.AbstractAccount;
  import org.exist.security.AbstractPrincipal;
  import org.exist.security.AbstractRealm;
  import org.exist.security.Account;
  import org.exist.security.AuthenticationException;
  import org.exist.security.EXistSchemaType;
  import org.exist.security.Group;
  import org.exist.security.PermissionDeniedException;
  import org.exist.security.SecurityManager;
  import org.exist.security.Subject;
  import org.exist.security.UUIDGenerator;
  import org.exist.security.internal.aider.UserAider;
  import org.exist.storage.BrokerPool;
  import org.exist.storage.DBBroker;
  import org.exist.storage.txn.TransactionManager;
  import org.exist.storage.txn.Txn;
  import org.exist.xmldb.XmldbURI;
  
  /**
   * @author <a href="mailto:shabanovd@gmail.com">Dmitriy Shabanov</a>
   *
   */
  public class RealmImpl extends AbstractRealm {
  	
      public static String ID = "exist"; //TODO: final "eXist-db";
  
      private final static Logger LOG = LogManager.getLogger(RealmImpl.class);
  
      static public void setPasswordRealm(String value) {
          ID = value;
      }
  
      public final static int SYSTEM_ACCOUNT_ID = 1048575;
      public final static int ADMIN_ACCOUNT_ID = 1048574;
      public final static int GUEST_ACCOUNT_ID = 1048573;
      public final static int UNKNOWN_ACCOUNT_ID = 1048572;
      public final static int INITIAL_LAST_ACCOUNT_ID = 10;
  
      public final static int DBA_GROUP_ID = 1048575;
      public final static int GUEST_GROUP_ID = 1048574;
      public final static int UNKNOWN_GROUP_ID = 1048573;
      public final static int INITIAL_LAST_GROUP_ID = 10;
  
      protected final AccountImpl ACCOUNT_SYSTEM;
      protected final AccountImpl ACCOUNT_UNKNOWN;
      
      protected final GroupImpl GROUP_DBA;
      protected final GroupImpl GROUP_GUEST;
      protected final GroupImpl GROUP_UNKNOWN;
      
      private final static String DEFAULT_ADMIN_PASSWORD = "";
      private final static String DEFAULT_GUEST_PASSWORD = "guest";
  
      //@ConfigurationFieldAsElement("allow-guest-authentication")
      public boolean allowGuestAuthentication = true;
  
      protected RealmImpl(final DBBroker broker, final SecurityManagerImpl sm, final Configuration config) throws ConfigurationException { //, Configuration conf
  
      	super(sm, config);
  
      	sm.lastUserId = INITIAL_LAST_ACCOUNT_ID;     //TODO this is horrible!
      	sm.lastGroupId = INITIAL_LAST_GROUP_ID;    //TODO this is horrible!
          
          //DBA group
          GROUP_DBA = new GroupImpl(broker, this, DBA_GROUP_ID, SecurityManager.DBA_GROUP);
          GROUP_DBA.setManagers(new ArrayList<Reference<SecurityManager, Account>>(){
              { add(new ReferenceImpl<>(sm, "getAccount", SecurityManager.DBA_USER)); }
          });
          GROUP_DBA.setMetadataValue(EXistSchemaType.DESCRIPTION, "Database Administrators");
      	sm.addGroup(GROUP_DBA.getId(), GROUP_DBA);
          registerGroup(GROUP_DBA);
          //sm.groupsById.put(GROUP_DBA.getId(), GROUP_DBA);
      	//groupsByName.put(GROUP_DBA.getName(), GROUP_DBA);
          
          //System account
      	ACCOUNT_SYSTEM = new AccountImpl(broker, this, SYSTEM_ACCOUNT_ID, SecurityManager.SYSTEM, "", GROUP_DBA, true);
          ACCOUNT_SYSTEM.setMetadataValue(AXSchemaType.FULLNAME, SecurityManager.SYSTEM);
          ACCOUNT_SYSTEM.setMetadataValue(EXistSchemaType.DESCRIPTION, "System Internals");
          sm.addUser(ACCOUNT_SYSTEM.getId(), ACCOUNT_SYSTEM);
          registerAccount(ACCOUNT_SYSTEM);
      	//sm.usersById.put(ACCOUNT_SYSTEM.getId(), ACCOUNT_SYSTEM);
      	//usersByName.put(ACCOUNT_SYSTEM.getName(), ACCOUNT_SYSTEM);
          
          //guest group
          GROUP_GUEST = new GroupImpl(broker, this, GUEST_GROUP_ID, SecurityManager.GUEST_GROUP);
          GROUP_GUEST.setManagers(new ArrayList<Reference<SecurityManager, Account>>(){
              { add(new ReferenceImpl<>(sm, "getAccount", SecurityManager.DBA_USER)); }
          });
          GROUP_GUEST.setMetadataValue(EXistSchemaType.DESCRIPTION, "Anonymous Users");
          sm.addGroup(GROUP_GUEST.getId(), GROUP_GUEST);
          registerGroup(GROUP_GUEST);
      	//sm.groupsById.put(GROUP_GUEST.getId(), GROUP_GUEST);
      	//groupsByName.put(GROUP_GUEST.getName(), GROUP_GUEST);
          
          //unknown account and group
          GROUP_UNKNOWN = new GroupImpl(broker, this, UNKNOWN_GROUP_ID, "");
      	ACCOUNT_UNKNOWN = new AccountImpl(broker, this, UNKNOWN_ACCOUNT_ID, "", (String)null, GROUP_UNKNOWN);
          
          //XXX: GROUP_DBA._addManager(ACCOUNT_ADMIN);
      	//XXX: GROUP_GUEST._addManager(ACCOUNT_ADMIN);
      }
  
      @Override
      public void start(final DBBroker broker) throws EXistException {
          super.start(broker);
          try {
              createAdminAndGuestIfNotExist(broker);
          } catch(final PermissionDeniedException pde) {
              final boolean exportOnly =  (Boolean) broker.getConfiguration().getProperty(BrokerPool.PROPERTY_EXPORT_ONLY, false);
              if(!exportOnly) {
              	throw new EXistException(pde.getMessage(), pde);
              }
          }
      }
      
      private void createAdminAndGuestIfNotExist(final DBBroker broker) throws EXistException, PermissionDeniedException {
      	
          //Admin account
          if(getSecurityManager().getAccount(ADMIN_ACCOUNT_ID) == null) {
              //AccountImpl actAdmin = new AccountImpl(broker, this, ADMIN_ACCOUNT_ID, SecurityManager.DBA_USER, "", GROUP_DBA, true);
              final UserAider actAdmin = new UserAider(ADMIN_ACCOUNT_ID, getId(), SecurityManager.DBA_USER);
              actAdmin.setPassword(DEFAULT_ADMIN_PASSWORD);
              actAdmin.setMetadataValue(AXSchemaType.FULLNAME, SecurityManager.DBA_USER);
              actAdmin.setMetadataValue(EXistSchemaType.DESCRIPTION, "System Administrator");
              actAdmin.addGroup(SecurityManager.DBA_GROUP);
              getSecurityManager().addAccount(broker, actAdmin);
          }
  
          //Guest account
          if(getSecurityManager().getAccount(GUEST_ACCOUNT_ID) == null) {
              //AccountImpl actGuest = new AccountImpl(broker, this, GUEST_ACCOUNT_ID, SecurityManager.GUEST_USER, SecurityManager.GUEST_USER, GROUP_GUEST, false);
              final UserAider actGuest = new UserAider(GUEST_ACCOUNT_ID, getId(), SecurityManager.GUEST_USER);
              actGuest.setMetadataValue(AXSchemaType.FULLNAME, SecurityManager.GUEST_USER);
              actGuest.setMetadataValue(EXistSchemaType.DESCRIPTION, "Anonymous User");
              actGuest.setPassword(DEFAULT_GUEST_PASSWORD);
              actGuest.addGroup(SecurityManager.GUEST_GROUP);
              getSecurityManager().addAccount(broker, actGuest);
          }
      }
      
      @Override
      public String getId() {
          return ID;
      }
  
      @Override
      public boolean deleteAccount(final Account account) throws PermissionDeniedException, EXistException {
          if(account == null) {
              return false;
          }
          
          usersByName.<PermissionDeniedException, EXistException>modify2E(principalDb -> {
              final AbstractAccount remove_account = (AbstractAccount)principalDb.get(account.getName());
              if(remove_account == null){
                  throw new IllegalArgumentException("No such account exists!");
              }
  
              try(final DBBroker broker = getDatabase().getBroker()) {
                  final Account user = broker.getCurrentSubject();
  
                  if(!(account.getName().equals(user.getName()) || user.hasDbaRole()) ) {
                      throw new PermissionDeniedException("You are not allowed to delete '" +account.getName() + "' user");
                  }
  
                  remove_account.setRemoved(true);
                  remove_account.setCollection(broker, collectionRemovedAccounts, XmldbURI.create(UUIDGenerator.getUUID()+".xml"));
  
                  final TransactionManager transaction = getDatabase().getTransactionManager();
                  try(final Txn txn = transaction.beginTransaction()) {
                      collectionAccounts.removeXMLResource(txn, broker, XmldbURI.create( remove_account.getName() + ".xml"));
  
                      transaction.commit(txn);
                  } catch(final Exception e) {
                      LOG.warn(e.getMessage(), e);
                  }
  
                  getSecurityManager().addUser(remove_account.getId(), remove_account);
                  principalDb.remove(remove_account.getName());
              }
          });
          
          return true;
      }
  
      @Override
      public boolean deleteGroup(final Group group) throws PermissionDeniedException, EXistException {
          if(group == null)
              {return false;}
          
          groupsByName.<PermissionDeniedException, EXistException>modify2E(principalDb -> {
              final AbstractPrincipal remove_group = (AbstractPrincipal)principalDb.get(group.getName());
              if(remove_group == null)
                  {throw new IllegalArgumentException("Group does '"+group.getName()+"' not exist!");}
  
              final DBBroker broker = getDatabase().getActiveBroker();
              final Subject subject = broker.getCurrentSubject();
  
              ((Group)remove_group).assertCanModifyGroup(subject);
  
              remove_group.setRemoved(true);
              remove_group.setCollection(broker, collectionRemovedGroups, XmldbURI.create(UUIDGenerator.getUUID() + ".xml"));
  
              final TransactionManager transaction = getDatabase().getTransactionManager();
              try(final Txn txn = transaction.beginTransaction()) {
  
                  collectionGroups.removeXMLResource(txn, broker, XmldbURI.create(remove_group.getName() + ".xml" ));
  
                  transaction.commit(txn);
              } catch (final Exception e) {
                  LOG.warn(e.getMessage(), e);
              }
  
              getSecurityManager().addGroup(remove_group.getId(), (Group)remove_group);
              principalDb.remove(remove_group.getName());
          });
          
          return true;
      }
  
      @Override
      public Subject authenticate(final String accountName, Object credentials) throws AuthenticationException {
          final Account account = getAccount(accountName);
          
          if(account == null) {
              throw new AuthenticationException(AuthenticationException.ACCOUNT_NOT_FOUND, "Account '" + accountName + "' not found.");
          }
          
          if("SYSTEM".equals(accountName) || (!allowGuestAuthentication && "guest".equals(accountName))) {
              throw new AuthenticationException(AuthenticationException.ACCOUNT_NOT_FOUND, "Account '" + accountName + "' can not be used.");
          }
  
          if(!account.isEnabled()) {
              throw new AuthenticationException(AuthenticationException.ACCOUNT_LOCKED, "Account '" + accountName + "' is disabled.");
          }
  
          final Subject subject = new SubjectImpl((AccountImpl) account, credentials);
          if(!subject.isAuthenticated()) {
              throw new AuthenticationException(AuthenticationException.WRONG_PASSWORD, "Wrong password for user [" + accountName + "] ");
          }
              
          return subject;
      }
  
      @Override
      public List<String> findUsernamesWhereUsernameStarts(final String prefix) {
          return usersByName.read(principalDb ->
                  principalDb.keySet()
                          .stream()
  