//----------------------------------------------------------------------------- // Copyright (c) Microsoft Corporation. All rights reserved. //----------------------------------------------------------------------------- namespace System.ServiceModel.Channels { using System; using System.ServiceModel.Description; using System.Collections.Generic; using System.Runtime.Serialization; using System.ServiceModel; using System.ServiceModel.Security; using System.ComponentModel; using System.ServiceModel.Transactions; using System.Xml; public sealed class TransactionFlowBindingElement : BindingElement, IPolicyExportExtension { bool transactions; TransactionFlowOption issuedTokens; TransactionProtocol transactionProtocol; public TransactionFlowBindingElement() : this(true, TransactionFlowDefaults.TransactionProtocol) { } public TransactionFlowBindingElement(TransactionProtocol transactionProtocol) : this(true, transactionProtocol) { } internal TransactionFlowBindingElement(bool transactions) : this(transactions, TransactionFlowDefaults.TransactionProtocol) { } internal TransactionFlowBindingElement(bool transactions, TransactionProtocol transactionProtocol) { this.transactions = transactions; this.issuedTokens = transactions ? TransactionFlowOption.Allowed : TransactionFlowOption.NotAllowed; if (!TransactionProtocol.IsDefined(transactionProtocol)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument(SR.GetString(SR.ConfigInvalidTransactionFlowProtocolValue, transactionProtocol.ToString())); } this.transactionProtocol = transactionProtocol; } TransactionFlowBindingElement(TransactionFlowBindingElement elementToBeCloned) : base(elementToBeCloned) { this.transactions = elementToBeCloned.transactions; this.issuedTokens = elementToBeCloned.issuedTokens; if (!TransactionProtocol.IsDefined(elementToBeCloned.transactionProtocol)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument(SR.GetString(SR.ConfigInvalidTransactionFlowProtocolValue, elementToBeCloned.transactionProtocol.ToString())); } this.transactionProtocol = elementToBeCloned.transactionProtocol; this.AllowWildcardAction = elementToBeCloned.AllowWildcardAction; } internal bool Transactions { get { return this.transactions; } set { this.transactions = value; this.issuedTokens = value ? TransactionFlowOption.Allowed : TransactionFlowOption.NotAllowed; } } internal TransactionFlowOption IssuedTokens { get { return this.issuedTokens; } set { ValidateOption(value); this.issuedTokens = value; } } public override BindingElement Clone() { return new TransactionFlowBindingElement(this); } bool IsFlowEnabled(Dictionary dictionary) { if (this.issuedTokens != TransactionFlowOption.NotAllowed) { return true; } if (!this.transactions) { return false; } foreach (TransactionFlowOption option in dictionary.Values) { if (option != TransactionFlowOption.NotAllowed) { return true; } } return false; } internal bool IsFlowEnabled(ContractDescription contract) { if (this.issuedTokens != TransactionFlowOption.NotAllowed) { return true; } if (!this.transactions) { return false; } foreach (OperationDescription operation in contract.Operations) { TransactionFlowAttribute parameter = operation.Behaviors.Find(); if (parameter != null) { if (parameter.Transactions != TransactionFlowOption.NotAllowed) { return true; } } } return false; } public TransactionProtocol TransactionProtocol { get { return this.transactionProtocol; } set { if (!TransactionProtocol.IsDefined(value)) throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("value")); this.transactionProtocol = value; } } [DefaultValue(false)] public bool AllowWildcardAction { get; set; } internal static void ValidateOption(TransactionFlowOption opt) { if (!TransactionFlowOptionHelper.IsDefined(opt)) throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentException(SR.GetString(SR.TransactionFlowBadOption))); } [EditorBrowsable(EditorBrowsableState.Never)] public bool ShouldSerializeTransactionProtocol() { return this.TransactionProtocol != TransactionProtocol.Default; } public override bool CanBuildChannelFactory(BindingContext context) { if (context == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentNullException("context")); } if (typeof(TChannel) == typeof(IOutputChannel) || typeof(TChannel) == typeof(IDuplexChannel) || typeof(TChannel) == typeof(IRequestChannel) || typeof(TChannel) == typeof(IOutputSessionChannel) || typeof(TChannel) == typeof(IRequestSessionChannel) || typeof(TChannel) == typeof(IDuplexSessionChannel)) { return context.CanBuildInnerChannelFactory(); } return false; } // The BuildChannelFactory and BuildListenerFactory methods looks for this BindingParameter // in the BindingContext: // - Dictionary // which has the per-operation TransactionFlowOptions public override IChannelFactory BuildChannelFactory(BindingContext context) { if (context == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("context"); } if (!this.CanBuildChannelFactory(context)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("TChannel", SR.GetString(SR.ChannelTypeNotSupported, typeof(TChannel))); } Dictionary dictionary = GetDictionary(context); if (!this.IsFlowEnabled(dictionary)) { return context.BuildInnerChannelFactory(); } if (this.issuedTokens == TransactionFlowOption.NotAllowed) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.TransactionFlowRequiredIssuedTokens))); } TransactionChannelFactory channelFactory = new TransactionChannelFactory(this.transactionProtocol, context, dictionary, this.AllowWildcardAction); channelFactory.FlowIssuedTokens = this.IssuedTokens; return channelFactory; } public override IChannelListener BuildChannelListener(BindingContext context) { if (context == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentNullException("context")); } if (!context.CanBuildInnerChannelListener()) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("TChannel", SR.GetString(SR.ChannelTypeNotSupported, typeof(TChannel))); } Dictionary dictionary = GetDictionary(context); if (!this.IsFlowEnabled(dictionary)) { return context.BuildInnerChannelListener(); } if (this.issuedTokens == TransactionFlowOption.NotAllowed) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.TransactionFlowRequiredIssuedTokens))); } IChannelListener innerListener = context.BuildInnerChannelListener(); TransactionChannelListener listener = new TransactionChannelListener(this.transactionProtocol, context.Binding, dictionary, innerListener); listener.FlowIssuedTokens = this.IssuedTokens; return listener; } public override bool CanBuildChannelListener(BindingContext context) { if (!context.CanBuildInnerChannelListener()) return false; return (typeof(TChannel) == typeof(IInputChannel) || typeof(TChannel) == typeof(IReplyChannel) || typeof(TChannel) == typeof(IDuplexChannel) || typeof(TChannel) == typeof(IInputSessionChannel) || typeof(TChannel) == typeof(IReplySessionChannel) || typeof(TChannel) == typeof(IDuplexSessionChannel)); } Dictionary GetDictionary(BindingContext context) { Dictionary dictionary = context.BindingParameters.Find>(); if (dictionary == null) dictionary = new Dictionary(); return dictionary; } internal static MessagePartSpecification GetIssuedTokenHeaderSpecification(SecurityStandardsManager standardsManager) { MessagePartSpecification result; if (standardsManager.TrustDriver.IsIssuedTokensSupported) result = new MessagePartSpecification(new XmlQualifiedName(standardsManager.TrustDriver.IssuedTokensHeaderName, standardsManager.TrustDriver.IssuedTokensHeaderNamespace)); else { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.TrustDriverVersionDoesNotSupportIssuedTokens))); } return result; } public override T GetProperty(BindingContext context) { if (typeof(T) == typeof(ChannelProtectionRequirements)) { ChannelProtectionRequirements myRequirements = this.GetProtectionRequirements(); if (myRequirements != null) { myRequirements.Add(context.GetInnerProperty() ?? new ChannelProtectionRequirements()); return (T)(object)myRequirements; } else { return (T)(object)context.GetInnerProperty(); } } else { return context.GetInnerProperty(); } } ChannelProtectionRequirements GetProtectionRequirements() { if (this.Transactions || (this.IssuedTokens != TransactionFlowOption.NotAllowed)) { ChannelProtectionRequirements requirements = new ChannelProtectionRequirements(); if (this.Transactions) { MessagePartSpecification p = new MessagePartSpecification( new XmlQualifiedName(CoordinationExternalStrings.CoordinationContext, CoordinationExternal10Strings.Namespace), new XmlQualifiedName(CoordinationExternalStrings.CoordinationContext, CoordinationExternal11Strings.Namespace), new XmlQualifiedName(OleTxTransactionExternalStrings.OleTxTransaction, OleTxTransactionExternalStrings.Namespace)); p.MakeReadOnly(); requirements.IncomingSignatureParts.AddParts(p); requirements.OutgoingSignatureParts.AddParts(p); requirements.IncomingEncryptionParts.AddParts(p); requirements.OutgoingEncryptionParts.AddParts(p); } if (this.IssuedTokens != TransactionFlowOption.NotAllowed) { MessagePartSpecification trustParts = GetIssuedTokenHeaderSpecification(SecurityStandardsManager.DefaultInstance); trustParts.MakeReadOnly(); requirements.IncomingSignatureParts.AddParts(trustParts); requirements.IncomingEncryptionParts.AddParts(trustParts); requirements.OutgoingSignatureParts.AddParts(trustParts); requirements.OutgoingEncryptionParts.AddParts(trustParts); } MessagePartSpecification body = new MessagePartSpecification(true); body.MakeReadOnly(); requirements.OutgoingSignatureParts.AddParts(body, FaultCodeConstants.Actions.Transactions); requirements.OutgoingEncryptionParts.AddParts(body, FaultCodeConstants.Actions.Transactions); return requirements; } else { return null; } } XmlElement GetAssertion(XmlDocument doc, TransactionFlowOption option, string prefix, string name, string ns, string policyNs) { if (doc == null) throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("doc"); XmlElement result = null; switch (option) { case TransactionFlowOption.NotAllowed: // Don't generate an assertion break; case TransactionFlowOption.Allowed: result = doc.CreateElement(prefix, name, ns); // Always insert the real wsp:Optional attribute XmlAttribute attr = doc.CreateAttribute(TransactionPolicyStrings.OptionalPrefix11, TransactionPolicyStrings.OptionalLocal, policyNs); attr.Value = TransactionPolicyStrings.TrueValue; result.Attributes.Append(attr); // For legacy protocols, also insert the legacy attribute for backward compat if (this.transactionProtocol == TransactionProtocol.OleTransactions || this.transactionProtocol == TransactionProtocol.WSAtomicTransactionOctober2004) { XmlAttribute attrLegacy = doc.CreateAttribute(TransactionPolicyStrings.OptionalPrefix10, TransactionPolicyStrings.OptionalLocal, TransactionPolicyStrings.OptionalNamespaceLegacy); attrLegacy.Value = TransactionPolicyStrings.TrueValue; result.Attributes.Append(attrLegacy); } break; case TransactionFlowOption.Mandatory: result = doc.CreateElement(prefix, name, ns); break; } return result; } void IPolicyExportExtension.ExportPolicy(MetadataExporter exporter, PolicyConversionContext context) { if (exporter == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("exporter"); } if (context == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("context"); } TransactionFlowBindingElement bindingElement = context.BindingElements.Find(); if (bindingElement == null || !bindingElement.Transactions) return; XmlDocument doc = new XmlDocument(); XmlElement assertion = null; foreach (OperationDescription operation in context.Contract.Operations) { TransactionFlowAttribute contextParam = operation.Behaviors.Find(); TransactionFlowOption txFlowOption = contextParam == null ? TransactionFlowOption.NotAllowed : contextParam.Transactions; // Transactions if (bindingElement.TransactionProtocol == TransactionProtocol.OleTransactions) { assertion = GetAssertion(doc, txFlowOption, TransactionPolicyStrings.OleTxTransactionsPrefix, TransactionPolicyStrings.OleTxTransactionsLocal, TransactionPolicyStrings.OleTxTransactionsNamespace, exporter.PolicyVersion.Namespace); } else if (bindingElement.TransactionProtocol == TransactionProtocol.WSAtomicTransactionOctober2004) { assertion = GetAssertion(doc, txFlowOption, TransactionPolicyStrings.WsatTransactionsPrefix, TransactionPolicyStrings.WsatTransactionsLocal, TransactionPolicyStrings.WsatTransactionsNamespace10, exporter.PolicyVersion.Namespace); } else if (bindingElement.TransactionProtocol == TransactionProtocol.WSAtomicTransaction11) { assertion = GetAssertion(doc, txFlowOption, TransactionPolicyStrings.WsatTransactionsPrefix, TransactionPolicyStrings.WsatTransactionsLocal, TransactionPolicyStrings.WsatTransactionsNamespace11, exporter.PolicyVersion.Namespace); } if (assertion != null) context.GetOperationBindingAssertions(operation).Add(assertion); } } internal override bool IsMatch(BindingElement b) { if (b == null) return false; TransactionFlowBindingElement txFlow = b as TransactionFlowBindingElement; if (txFlow == null) return false; if (this.transactions != txFlow.transactions) return false; if (this.issuedTokens != txFlow.issuedTokens) return false; if (this.transactionProtocol != txFlow.transactionProtocol) return false; return true; } } }