diff --git a/config/alfresco/extension/index-tracking-context.xml.sample b/config/alfresco/extension/index-tracking-context.xml.sample index 9ed6a3ae86..9b04517bcd 100644 --- a/config/alfresco/extension/index-tracking-context.xml.sample +++ b/config/alfresco/extension/index-tracking-context.xml.sample @@ -39,8 +39,11 @@ + 5 @@ -67,5 +69,6 @@ 0 + --> diff --git a/config/alfresco/messages/schema-update.properties b/config/alfresco/messages/schema-update.properties index 8680024eef..01255d0d70 100644 --- a/config/alfresco/messages/schema-update.properties +++ b/config/alfresco/messages/schema-update.properties @@ -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} diff --git a/source/java/org/alfresco/filesys/server/auth/AlfrescoAuthenticator.java b/source/java/org/alfresco/filesys/server/auth/AlfrescoAuthenticator.java index 10b40b4895..aa7bf81ecb 100644 --- a/source/java/org/alfresco/filesys/server/auth/AlfrescoAuthenticator.java +++ b/source/java/org/alfresco/filesys/server/auth/AlfrescoAuthenticator.java @@ -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 diff --git a/source/java/org/alfresco/filesys/server/auth/CifsAuthenticator.java b/source/java/org/alfresco/filesys/server/auth/CifsAuthenticator.java index 4197d26d83..cb4b43030a 100644 --- a/source/java/org/alfresco/filesys/server/auth/CifsAuthenticator.java +++ b/source/java/org/alfresco/filesys/server/auth/CifsAuthenticator.java @@ -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; + } } \ No newline at end of file diff --git a/source/java/org/alfresco/filesys/server/auth/passthru/AuthenticateSession.java b/source/java/org/alfresco/filesys/server/auth/passthru/AuthenticateSession.java index 2ee9618192..f2b392c526 100644 --- a/source/java/org/alfresco/filesys/server/auth/passthru/AuthenticateSession.java +++ b/source/java/org/alfresco/filesys/server/auth/passthru/AuthenticateSession.java @@ -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); diff --git a/source/java/org/alfresco/filesys/server/auth/passthru/DomainMapping.java b/source/java/org/alfresco/filesys/server/auth/passthru/DomainMapping.java new file mode 100644 index 0000000000..e890a354a6 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/auth/passthru/DomainMapping.java @@ -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); +} diff --git a/source/java/org/alfresco/filesys/server/auth/passthru/PassthruAuthenticator.java b/source/java/org/alfresco/filesys/server/auth/passthru/PassthruAuthenticator.java index b9638b4d4a..7d3c1c5987 100644 --- a/source/java/org/alfresco/filesys/server/auth/passthru/PassthruAuthenticator.java +++ b/source/java/org/alfresco/filesys/server/auth/passthru/PassthruAuthenticator.java @@ -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) { diff --git a/source/java/org/alfresco/filesys/server/auth/passthru/PassthruServerDetails.java b/source/java/org/alfresco/filesys/server/auth/passthru/PassthruServerDetails.java index a2200a980f..adbfdad738 100644 --- a/source/java/org/alfresco/filesys/server/auth/passthru/PassthruServerDetails.java +++ b/source/java/org/alfresco/filesys/server/auth/passthru/PassthruServerDetails.java @@ -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 * diff --git a/source/java/org/alfresco/filesys/server/auth/passthru/PassthruServers.java b/source/java/org/alfresco/filesys/server/auth/passthru/PassthruServers.java index 423d9be333..27d0e9c382 100644 --- a/source/java/org/alfresco/filesys/server/auth/passthru/PassthruServers.java +++ b/source/java/org/alfresco/filesys/server/auth/passthru/PassthruServers.java @@ -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 diff --git a/source/java/org/alfresco/filesys/server/auth/passthru/RangeDomainMapping.java b/source/java/org/alfresco/filesys/server/auth/passthru/RangeDomainMapping.java new file mode 100644 index 0000000000..f97b0af926 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/auth/passthru/RangeDomainMapping.java @@ -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(); + } +} diff --git a/source/java/org/alfresco/filesys/server/auth/passthru/SubnetDomainMapping.java b/source/java/org/alfresco/filesys/server/auth/passthru/SubnetDomainMapping.java new file mode 100644 index 0000000000..10f7812d9c --- /dev/null +++ b/source/java/org/alfresco/filesys/server/auth/passthru/SubnetDomainMapping.java @@ -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(); + } +} diff --git a/source/java/org/alfresco/filesys/server/config/ServerConfiguration.java b/source/java/org/alfresco/filesys/server/config/ServerConfiguration.java index b905c1b867..0cefcb2ee0 100644 --- a/source/java/org/alfresco/filesys/server/config/ServerConfiguration.java +++ b/source/java/org/alfresco/filesys/server/config/ServerConfiguration.java @@ -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 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 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(); + 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 + */ + public final List getDomainMappings() + { + return m_domainMappings; + } + /** * Return the primary filesystem shared device, or null if not available * diff --git a/source/java/org/alfresco/filesys/smb/server/NTProtocolHandler.java b/source/java/org/alfresco/filesys/smb/server/NTProtocolHandler.java index ae319b9c0b..ed2a78f731 100644 --- a/source/java/org/alfresco/filesys/smb/server/NTProtocolHandler.java +++ b/source/java/org/alfresco/filesys/smb/server/NTProtocolHandler.java @@ -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); } } diff --git a/source/java/org/alfresco/filesys/util/IPAddress.java b/source/java/org/alfresco/filesys/util/IPAddress.java index d015cb9992..a9fe2faf7f 100644 --- a/source/java/org/alfresco/filesys/util/IPAddress.java +++ b/source/java/org/alfresco/filesys/util/IPAddress.java @@ -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 * diff --git a/source/java/org/alfresco/repo/domain/hibernate/Transaction.hbm.xml b/source/java/org/alfresco/repo/domain/hibernate/Transaction.hbm.xml index a3c9bad0c9..59aa9821f2 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/Transaction.hbm.xml +++ b/source/java/org/alfresco/repo/domain/hibernate/Transaction.hbm.xml @@ -78,6 +78,17 @@ join status.transaction as txn + + 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 + + select count(txn.id) diff --git a/source/java/org/alfresco/repo/domain/schema/SchemaBootstrap.java b/source/java/org/alfresco/repo/domain/schema/SchemaBootstrap.java index 96dc4efa75..844647f172 100644 --- a/source/java/org/alfresco/repo/domain/schema/SchemaBootstrap.java +++ b/source/java/org/alfresco/repo/domain/schema/SchemaBootstrap.java @@ -82,6 +82,7 @@ public class SchemaBootstrap extends AbstractLifecycleBean /** The placeholder for the configured Dialect class name: ${db.script.dialect} */ 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); diff --git a/source/java/org/alfresco/repo/node/db/NodeDaoService.java b/source/java/org/alfresco/repo/node/db/NodeDaoService.java index 45ec879e45..34669b053d 100644 --- a/source/java/org/alfresco/repo/node/db/NodeDaoService.java +++ b/source/java/org/alfresco/repo/node/db/NodeDaoService.java @@ -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); diff --git a/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java b/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java index fb0989efb8..f0d9e052fe 100644 --- a/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java +++ b/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java @@ -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) { diff --git a/source/java/org/alfresco/repo/node/index/AbstractReindexComponent.java b/source/java/org/alfresco/repo/node/index/AbstractReindexComponent.java index 732ddb9926..041a93e784 100644 --- a/source/java/org/alfresco/repo/node/index/AbstractReindexComponent.java +++ b/source/java/org/alfresco/repo/node/index/AbstractReindexComponent.java @@ -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 true 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 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 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 { diff --git a/source/java/org/alfresco/repo/node/index/FullIndexRecoveryComponent.java b/source/java/org/alfresco/repo/node/index/FullIndexRecoveryComponent.java index 3e9a259ccb..a348a70e98 100644 --- a/source/java/org/alfresco/repo/node/index/FullIndexRecoveryComponent.java +++ b/source/java/org/alfresco/repo/node/index/FullIndexRecoveryComponent.java @@ -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); diff --git a/source/java/org/alfresco/repo/node/index/IndexRemoteTransactionTracker.java b/source/java/org/alfresco/repo/node/index/IndexRemoteTransactionTracker.java index 0347606daa..a9a544f0af 100644 --- a/source/java/org/alfresco/repo/node/index/IndexRemoteTransactionTracker.java +++ b/source/java/org/alfresco/repo/node/index/IndexRemoteTransactionTracker.java @@ -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 nextTxns = nodeDaoService.getNextRemoteTxns(currentTxnId, 1); + if (nextTxns.size() > 0) + { + nextTxn = nextTxns.get(0); + } + } + else + { + List 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 getNextTransactions(long currentTxnId) { diff --git a/source/java/org/alfresco/repo/security/authentication/ntlm/NTLMAuthenticationComponentImpl.java b/source/java/org/alfresco/repo/security/authentication/ntlm/NTLMAuthenticationComponentImpl.java index c8daf754ee..93691d65f4 100644 --- a/source/java/org/alfresco/repo/security/authentication/ntlm/NTLMAuthenticationComponentImpl.java +++ b/source/java/org/alfresco/repo/security/authentication/ntlm/NTLMAuthenticationComponentImpl.java @@ -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) + { + } + } } } } diff --git a/source/java/org/alfresco/repo/security/authentication/ntlm/NTLMLocalToken.java b/source/java/org/alfresco/repo/security/authentication/ntlm/NTLMLocalToken.java index 22b99e133c..f4c082ceb6 100644 --- a/source/java/org/alfresco/repo/security/authentication/ntlm/NTLMLocalToken.java +++ b/source/java/org/alfresco/repo/security/authentication/ntlm/NTLMLocalToken.java @@ -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; + } } diff --git a/source/java/org/alfresco/repo/security/authentication/ntlm/NTLMPassthruToken.java b/source/java/org/alfresco/repo/security/authentication/ntlm/NTLMPassthruToken.java index 7231ea2aca..c59ef79676 100644 --- a/source/java/org/alfresco/repo/security/authentication/ntlm/NTLMPassthruToken.java +++ b/source/java/org/alfresco/repo/security/authentication/ntlm/NTLMPassthruToken.java @@ -24,6 +24,8 @@ */ package org.alfresco.repo.security.authentication.ntlm; +import java.net.InetAddress; + /** *

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 *