Merged V2.0 to HEAD

5523: Merged V1.4 to V2.0
      5494: db.schema.update=false disables ALL metadata queries
      5500: AR-1399 NTProtocolHander search handle leakage
      5522: AR-1412 IndexRemoteTransactionTracker startup
   5541: Merged V1.4 to V2.0
      5525: Pass-through authentication and domain mapping
         Resolved minor conflict on AlfrescoAuthenticator.java
      5526: Domain mapping support


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@5546 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Derek Hulley 2007-04-25 02:44:53 +00:00
parent 08897ad76b
commit fb1dd4080b
24 changed files with 885 additions and 74 deletions

View File

@ -39,8 +39,11 @@
<!-- Forces the reindexing of nodes where content may have been missing before -->
<!--
This component can be triggered at intervals where asynchronous content sharing
between clustered servers has been set up.
between clustered servers has been set up. If content sharing is synchronous
(see ReplicatingContentStore.setOutboundThreadPoolExecutor) then this component
is not required.
-->
<!--
<bean id="missingContentReindexTrigger" class="org.alfresco.util.TriggerBean">
<property name="jobDetail">
<bean class="org.springframework.scheduling.quartz.JobDetailBean">
@ -59,7 +62,6 @@
<property name="scheduler">
<ref bean="schedulerFactory" />
</property>
<!-- Give the server 5 minutes and then check for missing content -->
<property name="startDelayMinutes">
<value>5</value>
</property>
@ -67,5 +69,6 @@
<value>0</value>
</property>
</bean>
-->
</beans>

View File

@ -1,5 +1,6 @@
# Schema update messages
schema.update.msg.bypassing=Bypassing schema update checks.
schema.update.msg.executing_script=Executing database script: {0}
schema.update.msg.optional_statement_failed=Optional statement execution failed:\n SQL: {0}\n Error: {1}\n File: {2}\n Line: {3}
schema.update.msg.dumping_schema_create=Generating unmodified schema creation script: {0}

View File

@ -245,7 +245,7 @@ public class AlfrescoAuthenticator extends CifsAuthenticator
{
// Create an authentication token for the session
NTLMPassthruToken authToken = new NTLMPassthruToken();
NTLMPassthruToken authToken = new NTLMPassthruToken( mapClientAddressToDomain( sess.getRemoteAddress()));
// Run the first stage of the passthru authentication to get the challenge

View File

@ -24,8 +24,11 @@
*/
package org.alfresco.filesys.server.auth;
import java.net.InetAddress;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.transaction.UserTransaction;
@ -33,7 +36,11 @@ import javax.transaction.UserTransaction;
import net.sf.acegisecurity.Authentication;
import org.alfresco.config.ConfigElement;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.filesys.server.SrvSession;
import org.alfresco.filesys.server.auth.passthru.DomainMapping;
import org.alfresco.filesys.server.auth.passthru.RangeDomainMapping;
import org.alfresco.filesys.server.auth.passthru.SubnetDomainMapping;
import org.alfresco.filesys.server.config.InvalidConfigurationException;
import org.alfresco.filesys.server.config.ServerConfiguration;
import org.alfresco.filesys.server.core.SharedDevice;
@ -53,6 +60,7 @@ import org.alfresco.filesys.smb.server.VirtualCircuit;
import org.alfresco.filesys.smb.server.repo.ContentContext;
import org.alfresco.filesys.util.DataPacker;
import org.alfresco.filesys.util.HexDump;
import org.alfresco.filesys.util.IPAddress;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.security.authentication.AuthenticationComponent;
import org.alfresco.repo.security.authentication.MD4PasswordEncoder;
@ -969,4 +977,44 @@ public abstract class CifsAuthenticator
m_authComponent.setGuestUserAsCurrentUser();
}
}
/**
* Map a client IP address to a domain
*
* @param clientIP InetAddress
* @return String
*/
protected final String mapClientAddressToDomain( InetAddress clientIP)
{
// Check if there are any domain mappings
if ( m_config.hasDomainMappings() == false)
return null;
// convert the client IP address to an integer value
int clientAddr = IPAddress.asInteger( clientIP);
for ( DomainMapping domainMap : m_config.getDomainMappings())
{
if ( domainMap.isMemberOfDomain( clientAddr))
{
// DEBUG
if ( logger.isDebugEnabled())
logger.debug( "Mapped client IP " + clientIP + " to domain " + domainMap.getDomain());
return domainMap.getDomain();
}
}
// DEBUG
if ( logger.isDebugEnabled())
logger.debug( "Failed to map client IP " + clientIP + " to a domain");
// No domain mapping for the client address
return null;
}
}

View File

@ -1119,6 +1119,8 @@ public class AuthenticateSession
if (getPCShare().hasDomain())
pkt.packString(getPCShare().getDomain(), false);
else if ( domain != null)
pkt.packString( domain, false);
else
pkt.packString("?", false);

View File

@ -0,0 +1,58 @@
/*
* Copyright (C) 2006 Alfresco, Inc.
*
* Licensed under the Mozilla Public License version 1.1
* with a permitted attribution clause. You may obtain a
* copy of the License at
*
* http://www.alfresco.org/legal/license.txt
*
* 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.alfresco.filesys.server.auth.passthru;
/**
* Domain Mapping Class
*
* @author gkspencer
*/
public abstract class DomainMapping {
// Domain name
private String m_domain;
/**
* Class consructor
*
* @param domain String
*/
public DomainMapping( String domain)
{
m_domain = domain;
}
/**
* Return the domain name
*
* @return String
*/
public final String getDomain()
{
return m_domain;
}
/**
* Check if the client address is a member of this domain
*
* @param clientIP int
* @return boolean
*/
public abstract boolean isMemberOfDomain( int clientIP);
}

View File

@ -358,7 +358,11 @@ public class PassthruAuthenticator extends CifsAuthenticator implements SessionL
try
{
AuthenticateSession authSess = m_passthruServers.openSession();
// Try and map the client address to a domain
String domain = mapClientAddressToDomain( sess.getRemoteAddress());
AuthenticateSession authSess = m_passthruServers.openSession( false, domain);
if (authSess != null)
{

View File

@ -126,6 +126,16 @@ public class PassthruServerDetails
return m_lastAuthTime;
}
/**
* Set the domain that the offline server belongs to
*
* @param domain String
*/
public final void setDomain(String domain)
{
m_domain = domain;
}
/**
* Set the online status for the server
*

View File

@ -306,27 +306,34 @@ public class PassthruServers
*/
public final AuthenticateSession openSession()
{
return openSession( false);
return openSession( false, null);
}
/**
* Open a new session to an authentication server
*
* @param useExtSec boolean
* @param clientDomain String
* @return AuthenticateSession
*/
public final AuthenticateSession openSession(boolean useExtSec)
public final AuthenticateSession openSession(boolean useExtSec, String clientDomain)
{
// Get the details of an authentication server to connect to
// Get the details of an authentication server to connect to
PassthruServerDetails passthruServer = getAuthenticationServer();
PassthruServerDetails passthruServer = null;
if ( clientDomain != null)
passthruServer = getAuthenticationServer( clientDomain);
else
passthruServer = getAuthenticationServer();
if ( passthruServer == null)
return null;
// Debug
if ( logger.isDebugEnabled())
logger.debug("Open authenticate session to " + passthruServer);
logger.debug("Open authenticate session to " + passthruServer + ( clientDomain != null ? " (routed for client domain " + clientDomain + ")" : ""));
// Open a new authentication session to the server
@ -402,6 +409,49 @@ public class PassthruServers
return passthruServer;
}
/**
* Return the details of an online server to use for authentication of the specified client
* domain
*
* @params clientDomain String
* @return PassthruServerDetails
*/
protected PassthruServerDetails getAuthenticationServer( String clientDomain)
{
// Rotate the head of the list and return the new head of list server details
PassthruServerDetails passthruServer = null;
synchronized ( m_onlineList)
{
int idx = 0;
while ( idx < m_onlineList.size() && passthruServer == null)
{
// Get the current passthru server details
PassthruServerDetails curServer = m_onlineList.get( idx);
if ( curServer.getDomain() != null && curServer.getDomain().equals( clientDomain))
{
// Use this passthru server
passthruServer = curServer;
// Move to the back of the list
m_onlineList.add( m_onlineList.remove( idx));
}
// Update the server index
idx++;
}
}
return passthruServer;
}
/**
* Move a server from the list of online servers to the offline list
*
@ -502,6 +552,17 @@ public class PassthruServers
String srvName = tokens.nextToken().trim();
// Check if the server address also contains a domain name
String domain = null;
int pos = srvName.indexOf( '\\');
if ( pos != -1)
{
domain = srvName.substring(0, pos);
srvName = srvName.substring( pos + 1);
}
// If a name a has been specified convert it to an address, if an address has been specified
// then convert to a name.
@ -549,7 +610,7 @@ public class PassthruServers
{
// Create the passthru server details
PassthruServerDetails passthruServer = new PassthruServerDetails(srvName, null, srvAddr, false);
PassthruServerDetails passthruServer = new PassthruServerDetails(srvName, domain, srvAddr, false);
m_offlineList.add( passthruServer);
// Debug

View File

@ -0,0 +1,101 @@
/*
* Copyright (C) 2006 Alfresco, Inc.
*
* Licensed under the Mozilla Public License version 1.1
* with a permitted attribution clause. You may obtain a
* copy of the License at
*
* http://www.alfresco.org/legal/license.txt
*
* 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.alfresco.filesys.server.auth.passthru;
import org.alfresco.filesys.util.IPAddress;
/**
* Address Range Domain Mapping Class
*
* @author gkspencer
*/
public class RangeDomainMapping extends DomainMapping {
// Range from/to addresses
private int m_rangeFrom;
private int m_rangeTo;
/**
* class constructor
*
* @param domain String
* @param rangeFrom int
* @param rangeTo int
*/
public RangeDomainMapping( String domain, int rangeFrom, int rangeTo)
{
super( domain);
m_rangeFrom = rangeFrom;
m_rangeTo = rangeTo;
}
/**
* Return the from range address
*
* @return int
*/
public final int getRangeFrom()
{
return m_rangeFrom;
}
/**
* Return the to range address
*
* @return int
*/
public final int getRangeTo()
{
return m_rangeTo;
}
/**
* Check if the client address is a member of this domain
*
* @param clientIP int
* @return boolean
*/
public boolean isMemberOfDomain( int clientIP)
{
if (clientIP >= m_rangeFrom && clientIP <= m_rangeTo)
return true;
return false;
}
/**
* Return the domain mapping as a string
*
* @return String
*/
public String toString()
{
StringBuilder str = new StringBuilder();
str.append("[");
str.append(getDomain());
str.append(",");
str.append(IPAddress.asString( getRangeFrom()));
str.append(":");
str.append(IPAddress.asString( getRangeTo()));
str.append("]");
return str.toString();
}
}

View File

@ -0,0 +1,101 @@
/*
* Copyright (C) 2006 Alfresco, Inc.
*
* Licensed under the Mozilla Public License version 1.1
* with a permitted attribution clause. You may obtain a
* copy of the License at
*
* http://www.alfresco.org/legal/license.txt
*
* 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.alfresco.filesys.server.auth.passthru;
import org.alfresco.filesys.util.IPAddress;
/**
* Subnet Domain Mapping Class
*
* @author gkspencer
*/
public class SubnetDomainMapping extends DomainMapping {
// Subnet and mask for the domain
private int m_subnet;
private int m_mask;
/**
* class constructor
*
* @param domain String
* @param subnet int
* @param mask int
*/
public SubnetDomainMapping( String domain, int subnet, int mask)
{
super( domain);
m_subnet = subnet;
m_mask = mask;
}
/**
* Return the subnet
*
* @return int
*/
public final int getSubnet()
{
return m_subnet;
}
/**
* Return the subnet mask
*
* @return int
*/
public final int getSubnetMask()
{
return m_mask;
}
/**
* Check if the client address is a member of this domain
*
* @param clientIP int
* @return boolean
*/
public boolean isMemberOfDomain( int clientIP)
{
if (( clientIP & m_mask) == m_subnet)
return true;
return false;
}
/**
* Return the domain mapping as a string
*
* @return String
*/
public String toString()
{
StringBuilder str = new StringBuilder();
str.append("[");
str.append(getDomain());
str.append(",");
str.append(IPAddress.asString( getSubnet()));
str.append(":");
str.append(IPAddress.asString( getSubnetMask()));
str.append("]");
return str.toString();
}
}

View File

@ -75,6 +75,9 @@ import org.alfresco.filesys.server.auth.acl.AccessControlManager;
import org.alfresco.filesys.server.auth.acl.AccessControlParser;
import org.alfresco.filesys.server.auth.acl.DefaultAccessControlManager;
import org.alfresco.filesys.server.auth.acl.InvalidACLTypeException;
import org.alfresco.filesys.server.auth.passthru.DomainMapping;
import org.alfresco.filesys.server.auth.passthru.RangeDomainMapping;
import org.alfresco.filesys.server.auth.passthru.SubnetDomainMapping;
import org.alfresco.filesys.server.core.DeviceContext;
import org.alfresco.filesys.server.core.DeviceContextException;
import org.alfresco.filesys.server.core.ShareMapper;
@ -402,6 +405,10 @@ public class ServerConfiguration extends AbstractLifecycleBean
private PersonService m_personService;
private TransactionService m_transactionService;
// Domain mappings, by subnet
private List<DomainMapping> m_domainMappings;
/**
* Class constructor
*/
@ -2260,6 +2267,75 @@ public class ServerConfiguration extends AbstractLifecycleBean
setJCEProvider("cryptix.jce.provider.CryptixCrypto");
}
// Check if any domain mappings have been specified
ConfigElement domainMappings = config.getConfigElement( "DomainMappings");
if ( domainMappings != null)
{
// Get the domain mapping elements
List<ConfigElement> mappings = domainMappings.getChildren();
if ( mappings != null)
{
DomainMapping mapping = null;
for ( ConfigElement domainMap : mappings)
{
if ( domainMap.getName().equals( "Domain"))
{
// Get the domain name
String name = domainMap.getAttribute( "name");
// Check if the domain is specified by subnet or range
if ( domainMap.hasAttribute( "subnet"))
{
String subnetStr = domainMap.getAttribute( "subnet");
String maskStr = domainMap.getAttribute( "mask");
// Parse the subnet and mask, to validate and convert to int values
int subnet = IPAddress.parseNumericAddress( subnetStr);
int mask = IPAddress.parseNumericAddress( maskStr);
if ( subnet == 0 || mask == 0)
throw new AlfrescoRuntimeException( "Invalid subnet/mask for domain mapping " + name);
// Create the subnet domain mapping
mapping = new SubnetDomainMapping( name, subnet, mask);
}
else if ( domainMap.hasAttribute( "rangeFrom"))
{
String rangeFromStr = domainMap.getAttribute( "rangeFrom");
String rangeToStr = domainMap.getAttribute( "rangeTo");
// Parse the range from/to values and convert to int values
int rangeFrom = IPAddress.parseNumericAddress( rangeFromStr);
int rangeTo = IPAddress.parseNumericAddress( rangeToStr);
if ( rangeFrom == 0 || rangeTo == 0)
throw new AlfrescoRuntimeException( "Invalid address range domain mapping " + name);
// Create the subnet domain mapping
mapping = new RangeDomainMapping( name, rangeFrom, rangeTo);
}
else
throw new AlfrescoRuntimeException( "Invalid domain mapping specified");
// Create the domain mapping
if ( m_domainMappings == null)
m_domainMappings = new ArrayList<DomainMapping>();
m_domainMappings.add( mapping);
}
}
}
}
// Check if an authenticator has been specified
ConfigElement authElem = config.getConfigElement("authenticator");
@ -3197,6 +3273,26 @@ public class ServerConfiguration extends AbstractLifecycleBean
return domainName;
}
/**
* Check if there are domain mappings
*
* @return boolean
*/
public final boolean hasDomainMappings()
{
return m_domainMappings != null ? true : false;
}
/**
* Return the domain mappings
*
* @return List<DomainMapping>
*/
public final List<DomainMapping> getDomainMappings()
{
return m_domainMappings;
}
/**
* Return the primary filesystem shared device, or null if not available
*

View File

@ -3414,7 +3414,6 @@ public class NTProtocolHandler extends CoreProtocolHandler
if (searchDone == true || ctx.hasMoreFiles() == false)
{
// Debug
if (logger.isDebugEnabled() && m_sess.hasDebug(SMBSrvSession.DBG_SEARCH))
@ -3422,6 +3421,17 @@ public class NTProtocolHandler extends CoreProtocolHandler
// Release the search context
vc.deallocateSearchSlot(searchId);
}
else if (( srchFlag & FindFirstNext.CloseSearch) != 0)
{
// Debug
if (logger.isDebugEnabled() && m_sess.hasDebug(SMBSrvSession.DBG_SEARCH))
logger.debug("End start search [" + searchId + "] (Close)");
// Release the search context
vc.deallocateSearchSlot(searchId);
}
}
@ -3677,6 +3687,17 @@ public class NTProtocolHandler extends CoreProtocolHandler
// Release the search context
vc.deallocateSearchSlot(searchId);
}
else if (( srchFlag & FindFirstNext.CloseSearch) != 0)
{
// Debug
if (logger.isDebugEnabled() && m_sess.hasDebug(SMBSrvSession.DBG_SEARCH))
logger.debug("End start search [" + searchId + "] (Close)");
// Release the search context
vc.deallocateSearchSlot(searchId);
}
}

View File

@ -181,6 +181,23 @@ public class IPAddress
return false;
}
/**
* Convert a raw IP address array as a String
*
* @param ipaddr int
* @return String
*/
public final static String asString(int ipaddr)
{
byte[] ipbyts = new byte[4];
ipbyts[0] = (byte) ((ipaddr >> 24) & 0xFF);
ipbyts[1] = (byte) ((ipaddr >> 16) & 0xFF);
ipbyts[2] = (byte) ((ipaddr >> 8) & 0xFF);
ipbyts[3] = (byte) (ipaddr & 0xFF);
return asString( ipbyts);
}
/**
* Convert a raw IP address array as a String
*

View File

@ -78,6 +78,17 @@
join status.transaction as txn
</query>
<query name="txn.GetLastRemoteTxnId">
select
max(txn.id)
from
org.alfresco.repo.domain.hibernate.NodeStatusImpl as status
join status.transaction as txn
join txn.server as server
where
server.ipAddress != :serverIpAddress
</query>
<query name="txn.CountTransactions">
select
count(txn.id)

View File

@ -82,6 +82,7 @@ public class SchemaBootstrap extends AbstractLifecycleBean
/** The placeholder for the configured <code>Dialect</code> class name: <b>${db.script.dialect}</b> */
private static final String PLACEHOLDER_SCRIPT_DIALECT = "\\$\\{db\\.script\\.dialect\\}";
private static final String MSG_BYPASSING_SCHEMA_UPDATE = "schema.update.msg.bypassing";
private static final String MSG_EXECUTING_SCRIPT = "schema.update.msg.executing_script";
private static final String MSG_OPTIONAL_STATEMENT_FAILED = "schema.update.msg.optional_statement_failed";
private static final String MSG_DUMPING_SCHEMA_CREATE = "schema.update.msg.dumping_schema_create";
@ -650,12 +651,16 @@ public class SchemaBootstrap extends AbstractLifecycleBean
if (updateSchema)
{
updateSchema(cfg, session, connection);
// verify that all patches have been applied correctly
checkSchemaPatchScripts(cfg, session, connection, validateUpdateScriptPatches, false); // check scripts
checkSchemaPatchScripts(cfg, session, connection, preUpdateScriptPatches, false); // check scripts
checkSchemaPatchScripts(cfg, session, connection, postUpdateScriptPatches, false); // check scripts
}
else
{
logger.info(I18NUtil.getMessage(MSG_BYPASSING_SCHEMA_UPDATE));
}
// verify that all patches have been applied correctly
checkSchemaPatchScripts(cfg, session, connection, validateUpdateScriptPatches, false); // check scripts
checkSchemaPatchScripts(cfg, session, connection, preUpdateScriptPatches, false); // check scripts
checkSchemaPatchScripts(cfg, session, connection, postUpdateScriptPatches, false); // check scripts
// Reset the configuration
cfg.setProperty(Environment.CONNECTION_PROVIDER, defaultConnectionProviderFactoryClass);

View File

@ -277,6 +277,7 @@ public interface NodeDaoService
public Transaction getTxnById(long txnId);
public Transaction getLastTxn();
public Transaction getLastRemoteTxn();
public Transaction getLastTxnForStore(final StoreRef storeRef);
public int getTxnUpdateCount(final long txnId);
public int getTxnDeleteCount(final long txnId);

View File

@ -1185,6 +1185,7 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements
* Queries for transactions
*/
private static final String QUERY_GET_LAST_TXN_ID = "txn.GetLastTxnId";
private static final String QUERY_GET_LAST_REMOTE_TXN_ID = "txn.GetLastRemoteTxnId";
private static final String QUERY_GET_LAST_TXN_ID_FOR_STORE = "txn.GetLastTxnIdForStore";
private static final String QUERY_GET_TXN_UPDATE_COUNT_FOR_STORE = "txn.GetTxnUpdateCountForStore";
private static final String QUERY_GET_TXN_DELETE_COUNT_FOR_STORE = "txn.GetTxnDeleteCountForStore";
@ -1222,6 +1223,30 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements
return txn;
}
@SuppressWarnings("unchecked")
public Transaction getLastRemoteTxn()
{
HibernateCallback callback = new HibernateCallback()
{
public Object doInHibernate(Session session)
{
Query query = session.getNamedQuery(QUERY_GET_LAST_REMOTE_TXN_ID);
query.setString("serverIpAddress", ipAddress)
.setMaxResults(1)
.setReadOnly(true);
return query.uniqueResult();
}
};
Long txnId = (Long) getHibernateTemplate().execute(callback);
Transaction txn = null;
if (txnId != null)
{
txn = (Transaction) getSession().get(TransactionImpl.class, txnId);
}
// done
return txn;
}
@SuppressWarnings("unchecked")
public Transaction getLastTxnForStore(final StoreRef storeRef)
{

View File

@ -242,37 +242,19 @@ public abstract class AbstractReindexComponent implements IndexRecovery
}
}
}
/**
* Gets the last indexed transaction working back from the provided index.
* This method can be used to hunt for a starting point for indexing of
* transactions not yet in the index.
*/
protected long getLastIndexedTxn(long lastTxnId)
protected enum InIndex
{
// get the last transaction
long lastFoundTxnId = lastTxnId + 10L;
boolean found = false;
while (!found && lastFoundTxnId >= 0)
{
// reduce the transaction ID
lastFoundTxnId = lastFoundTxnId - 10L;
// break out as soon as we find a transaction that is in the index
found = isTxnIdPresentInIndex(lastFoundTxnId);
if (found)
{
break;
}
}
// done
if (logger.isDebugEnabled())
{
logger.debug("Found last index txn before " + lastTxnId + ": " + lastFoundTxnId);
}
return lastFoundTxnId;
YES, NO, INDETERMINATE;
}
protected boolean isTxnIdPresentInIndex(long txnId)
/**
* Determines if a given transaction is definitely in the index or not.
*
* @param txnId a specific transaction
* @return Returns <tt>true</tt> if the transaction is definitely in the index
*/
protected InIndex isTxnIdPresentInIndex(long txnId)
{
if (logger.isDebugEnabled())
{
@ -282,7 +264,7 @@ public abstract class AbstractReindexComponent implements IndexRecovery
Transaction txn = nodeDaoService.getTxnById(txnId);
if (txn == null)
{
return true;
return InIndex.YES;
}
// count the changes in the transaction
@ -290,28 +272,38 @@ public abstract class AbstractReindexComponent implements IndexRecovery
int deleteCount = nodeDaoService.getTxnDeleteCount(txnId);
if (logger.isDebugEnabled())
{
logger.debug("Transaction has " + updateCount + " updates and " + deleteCount + " deletes: " + txnId);
logger.debug("Transaction " + txnId + " has " + updateCount + " updates and " + deleteCount + " deletes.");
}
// get the stores
boolean found = false;
List<StoreRef> storeRefs = nodeService.getStores();
for (StoreRef storeRef : storeRefs)
InIndex result = InIndex.NO;
if (updateCount == 0 && deleteCount == 0)
{
boolean inStore = isTxnIdPresentInIndex(storeRef, txn, updateCount, deleteCount);
if (inStore)
// If there are no update or deletes, then it is impossible to know if the transaction was removed
// from the index or was never there in the first place.
result = InIndex.INDETERMINATE;
}
else
{
// get the stores
List<StoreRef> storeRefs = nodeService.getStores();
for (StoreRef storeRef : storeRefs)
{
// found in a particular store
found = true;
break;
boolean inStore = isTxnIdPresentInIndex(storeRef, txn, updateCount, deleteCount);
if (inStore)
{
// found in a particular store
result = InIndex.YES;
break;
}
}
}
// done
if (logger.isDebugEnabled())
{
logger.debug("Transaction " + txnId + " was " + (found ? "found" : "not found") + " in indexes.");
logger.debug("Transaction " + txnId + " present in indexes: " + result);
}
return found;
return result;
}
/**
@ -340,7 +332,7 @@ public abstract class AbstractReindexComponent implements IndexRecovery
{
if (logger.isDebugEnabled())
{
logger.debug("Index has results for txn (OK): " + txnId);
logger.debug("Index has results for txn " + txnId + " for store " + storeRef);
}
return true; // there were updates/creates and results for the txn were found
}
@ -348,7 +340,7 @@ public abstract class AbstractReindexComponent implements IndexRecovery
{
if (logger.isDebugEnabled())
{
logger.debug("Index has no results for txn (Index out of date): " + txnId);
logger.debug("Transaction " + txnId + " not in index for store " + storeRef + ". Possibly out of date.");
}
return false;
}
@ -450,7 +442,7 @@ public abstract class AbstractReindexComponent implements IndexRecovery
null,
null,
nodeRef);
indexer.deleteNode(assocRef);
indexer.deleteNode(assocRef);
}
else // node created
{

View File

@ -132,8 +132,8 @@ public class FullIndexRecoveryComponent extends AbstractReindexComponent
return;
}
long txnId = txn.getId();
boolean txnInIndex = isTxnIdPresentInIndex(txnId);
if (!txnInIndex)
InIndex txnInIndex = isTxnIdPresentInIndex(txnId);
if (txnInIndex != InIndex.YES)
{
String msg = I18NUtil.getMessage(ERR_INDEX_OUT_OF_DATE);
logger.warn(msg);

View File

@ -40,6 +40,7 @@ public class IndexRemoteTransactionTracker extends AbstractReindexComponent
private static Log logger = LogFactory.getLog(IndexRemoteTransactionTracker.class);
private boolean remoteOnly;
private boolean started;
private long currentTxnId;
public IndexRemoteTransactionTracker()
@ -67,17 +68,11 @@ public class IndexRemoteTransactionTracker extends AbstractReindexComponent
@Override
protected void reindexImpl()
{
if (currentTxnId < 0)
if (!started)
{
// initialize the starting point
Transaction lastTxn = nodeDaoService.getLastTxn();
if (lastTxn == null)
{
// there is nothing to do
return;
}
long lastTxnId = lastTxn.getId();
currentTxnId = getLastIndexedTxn(lastTxnId);
// Initialize the starting poing
currentTxnId = getLastIndexedTxn();
started = true;
}
if (logger.isDebugEnabled())
@ -109,6 +104,88 @@ public class IndexRemoteTransactionTracker extends AbstractReindexComponent
}
}
private static final long DECREMENT_COUNT = 10L;
/**
* Finds the last indexed transaction. It works backwards from the
* last index in increments, respecting the {@link #setRemoteOnly(boolean) remoteOnly}
* flag.
*
* @return Returns the last index transaction or -1 if there is none
*/
protected long getLastIndexedTxn()
{
// get the last transaction
Transaction txn = null;
if (remoteOnly)
{
txn = nodeDaoService.getLastRemoteTxn();
}
else
{
txn = nodeDaoService.getLastTxn();
}
if (txn == null)
{
// There is no last transaction to use
return -1L;
}
long currentTxnId = txn.getId();
while (currentTxnId >= 0L)
{
// Check if the current txn is in the index
InIndex txnInIndex = isTxnIdPresentInIndex(currentTxnId);
if (txnInIndex == InIndex.YES)
{
// We found somewhere to start
break;
}
// Get back in time
long lastCheckTxnId = currentTxnId;
currentTxnId -= DECREMENT_COUNT;
if (currentTxnId < 0L)
{
currentTxnId = -1L;
}
// We don't know if this number we have is a local or remote txn, so get the very next one
Transaction nextTxn = null;
if (remoteOnly)
{
List<Transaction> nextTxns = nodeDaoService.getNextRemoteTxns(currentTxnId, 1);
if (nextTxns.size() > 0)
{
nextTxn = nextTxns.get(0);
}
}
else
{
List<Transaction> nextTxns = nodeDaoService.getNextTxns(currentTxnId, 1);
if (nextTxns.size() > 0)
{
nextTxn = nextTxns.get(0);
}
}
if (nextTxn == null)
{
// There was nothing relevant after this, so keep going back in time
continue;
}
else if (nextTxn.getId() >= lastCheckTxnId)
{
// Decrementing by DECREMENT_COUNT was not enough
continue;
}
// Adjust the last one we looked at to reflect the correct txn id
currentTxnId = nextTxn.getId();
}
// We are close enough to the beginning, so just go for the first transaction
if (currentTxnId < 0L)
{
currentTxnId = -1L;
}
return currentTxnId;
}
private static final int MAX_TXN_COUNT = 1000;
private List<Transaction> getNextTransactions(long currentTxnId)
{

View File

@ -34,6 +34,8 @@ import java.security.Security;
import java.util.Enumeration;
import java.util.Hashtable;
import javax.transaction.UserTransaction;
import net.sf.acegisecurity.Authentication;
import net.sf.acegisecurity.AuthenticationServiceException;
import net.sf.acegisecurity.BadCredentialsException;
@ -53,7 +55,9 @@ import org.alfresco.repo.security.authentication.AuthenticationException;
import org.alfresco.repo.security.authentication.NTLMMode;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.security.NoSuchPersonException;
import org.alfresco.service.cmr.security.PersonService;
import org.alfresco.service.transaction.TransactionService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@ -95,6 +99,10 @@ public class NTLMAuthenticationComponentImpl extends AbstractAuthenticationCompo
private boolean m_allowGuest;
// Allow authenticated users that do not have an Alfresco person to logon as guest
private boolean m_allowAuthUserAsGuest;
// Table of currently active passthru authentications and the associated authentication session
//
// If the two authentication stages are not completed within a reasonable time the authentication
@ -114,6 +122,7 @@ public class NTLMAuthenticationComponentImpl extends AbstractAuthenticationCompo
private PersonService m_personService;
private NodeService m_nodeService;
private TransactionService m_transactionService;
/**
* Passthru Session Reaper Thread
@ -362,6 +371,16 @@ public class NTLMAuthenticationComponentImpl extends AbstractAuthenticationCompo
m_allowGuest = Boolean.parseBoolean(guest);
}
/**
* Allow authenticated users with no alfresco person record to logon with guest access
*
* @param auth String
*/
public void setAllowAuthUserAsGuest(String auth)
{
m_allowAuthUserAsGuest = Boolean.parseBoolean(auth);
}
/**
* Set the JCE provider
*
@ -461,6 +480,16 @@ public class NTLMAuthenticationComponentImpl extends AbstractAuthenticationCompo
m_nodeService = nodeService;
}
/**
* Set the transaction service
*
* @param transService TransactionService
*/
public final void setTransactionService(TransactionService transService)
{
m_transactionService = transService;
}
/**
* Return the authentication session timeout, in milliseconds
*
@ -756,7 +785,7 @@ public class NTLMAuthenticationComponentImpl extends AbstractAuthenticationCompo
// Open an authentication session for the new token and add to the active session list
authSess = m_passthruServers.openSession();
authSess = m_passthruServers.openSession( false, ntlmToken.getClientDomain());
// Check if the session was opened to the passthru server
@ -792,6 +821,8 @@ public class NTLMAuthenticationComponentImpl extends AbstractAuthenticationCompo
}
else
{
UserTransaction tx = null;
try
{
// Stage two of the authentication, send the hashed password to the authentication server
@ -835,6 +866,11 @@ public class NTLMAuthenticationComponentImpl extends AbstractAuthenticationCompo
ntlmToken.setAuthenticated(true);
// Wrap the service calls in a transaction
tx = m_transactionService.getUserTransaction( true);
tx.begin();
// Map the passthru username to an Alfresco person
NodeRef userNode = m_personService.getPerson(username);
@ -861,7 +897,32 @@ public class NTLMAuthenticationComponentImpl extends AbstractAuthenticationCompo
if ( logger.isDebugEnabled())
logger.debug("Setting current user using username " + username);
}
}
}
catch (NoSuchPersonException ex)
{
// Check if authenticated users are allowed on as guest when there is no Alfresco person record
if ( m_allowAuthUserAsGuest == true)
{
// Set the guest authority
GrantedAuthority[] authorities = new GrantedAuthority[1];
authorities[0] = new GrantedAuthorityImpl(NTLMAuthorityGuest);
ntlmToken.setAuthorities(authorities);
// DEBUG
if ( logger.isDebugEnabled())
logger.debug("Allow passthru authenticated user to logon as guest, user=" + ntlmToken.getName());
}
else
{
// Logon failure, no matching person record
throw new AuthenticationServiceException("Logon failure", ex);
}
}
catch (IOException ex)
{
// Error connecting to the authentication server
@ -899,6 +960,12 @@ public class NTLMAuthenticationComponentImpl extends AbstractAuthenticationCompo
else
throw new BadCredentialsException("Logon failure");
}
catch (Exception ex)
{
// General error
throw new AuthenticationServiceException("General error", ex);
}
finally
{
// Make sure the authentication session is closed
@ -919,6 +986,19 @@ public class NTLMAuthenticationComponentImpl extends AbstractAuthenticationCompo
{
}
}
// Commit or rollback the transaction, if active
if ( tx != null)
{
try
{
tx.commit();
}
catch ( Exception ex)
{
}
}
}
}
}

View File

@ -24,6 +24,8 @@
*/
package org.alfresco.repo.security.authentication.ntlm;
import java.net.InetAddress;
import net.sf.acegisecurity.GrantedAuthority;
import net.sf.acegisecurity.providers.*;
@ -37,6 +39,11 @@ public class NTLMLocalToken extends UsernamePasswordAuthenticationToken
{
private static final long serialVersionUID = -7946514578455279387L;
// Optional client domain and IP address, used to route the passthru authentication to the correct server(s)
private String m_clientDomain;
private String m_clientAddr;
/**
* Class constructor
*/
@ -44,6 +51,17 @@ public class NTLMLocalToken extends UsernamePasswordAuthenticationToken
{
super(null, null);
}
/**
* Class constructor
*
* @param ipAddr InetAddress
*/
protected NTLMLocalToken( InetAddress ipAddr)
{
if ( ipAddr != null)
m_clientAddr = ipAddr.getHostAddress();
}
/**
* Class constructor
@ -55,6 +73,21 @@ public class NTLMLocalToken extends UsernamePasswordAuthenticationToken
super(username.toLowerCase(), plainPwd);
}
/**
* Class constructor
*
* @param username String
* @param plainPwd String
* @param domain String
* @param ipAddr String
*/
public NTLMLocalToken(String username, String plainPwd, String domain, String ipAddr) {
super(username != null ? username.toLowerCase() : "", plainPwd);
m_clientDomain = domain;
m_clientAddr = ipAddr;
}
/**
* Check if the user logged on as a guest
*
@ -103,4 +136,44 @@ public class NTLMLocalToken extends UsernamePasswordAuthenticationToken
return found;
}
/**
* Check if the client domain name is set
*
* @return boolean
*/
public final boolean hasClientDomain()
{
return m_clientDomain != null ? true : false;
}
/**
* Return the client domain
*
* @return String
*/
public final String getClientDomain()
{
return m_clientDomain;
}
/**
* Check if the client IP address is set
*
* @return boolean
*/
public final boolean hasClientAddress()
{
return m_clientAddr != null ? true : false;
}
/**
* Return the client IP address
*
* @return String
*/
public final String getClientAddress()
{
return m_clientAddr;
}
}

View File

@ -24,6 +24,8 @@
*/
package org.alfresco.repo.security.authentication.ntlm;
import java.net.InetAddress;
/**
* <p>Used to provide passthru authentication to a remote Windows server using multiple stages that
* allows authentication details to be passed between a client and the remote authenticating server without
@ -59,6 +61,28 @@ public class NTLMPassthruToken extends NTLMLocalToken
super("", "");
}
/**
* Class constructor
*
* @params domain String
*/
public NTLMPassthruToken( String domain)
{
// We do not know the username yet, and will not know the password
super("", "", domain, null);
}
/**
* Class constructor
*
* @param ipAddr InetAddress
*/
public NTLMPassthruToken( InetAddress ipAddr)
{
super( ipAddr);
}
/**
* Return the challenge
*