1 //------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation.  All rights reserved.
3 //------------------------------------------------------------
4 namespace System.ServiceModel.Transactions
5 {
6     using System;
7     using System.Runtime;
8     using System.Security.Permissions;
9     using System.ServiceModel.Channels;
10     using System.ServiceModel.Security;
11     using System.Transactions;
12     using System.Xml;
13     using Microsoft.Transactions.Wsat.Messaging;
14     using Microsoft.Transactions.Wsat.Protocol;
15     using DiagnosticUtility = System.ServiceModel.DiagnosticUtility;
16 
17     abstract class WsatTransactionFormatter : TransactionFormatter
18     {
19         bool initialized;
20         WsatConfiguration wsatConfig;
21         WsatProxy wsatProxy;
22         ProtocolVersion protocolVersion;
23 
WsatTransactionFormatter(ProtocolVersion protocolVersion)24         protected WsatTransactionFormatter(ProtocolVersion protocolVersion)
25         {
26             this.protocolVersion = protocolVersion;
27         }
28 
29         //=======================================================================================
EnsureInitialized()30         void EnsureInitialized()
31         {
32             if (!this.initialized)
33             {
34                 lock (this)
35                 {
36                     if (!this.initialized)
37                     {
38                         this.wsatConfig = new WsatConfiguration();
39                         this.wsatProxy = new WsatProxy(this.wsatConfig, this.protocolVersion);
40                         this.initialized = true;
41                     }
42                 }
43             }
44         }
45 
46         //=======================================================================================
47         // The demand is not added now (in 4.5), to avoid a breaking change. To be considered in the next version.
48         /*
49         [PermissionSet(SecurityAction.Demand, Unrestricted = true)] // because we call code from a non-APTCA assembly; WSATs are not supported in partial trust, so customers should not be broken by this demand
50         */
WriteTransaction(Transaction transaction, Message message)51         public override void WriteTransaction(Transaction transaction, Message message)
52         {
53             EnsureInitialized();
54 
55             ForcePromotion(transaction);
56 
57             // Make a context and add it to the message
58             CoordinationContext context;
59             RequestSecurityTokenResponse issuedToken;
60             MarshalAsCoordinationContext(transaction, out context, out issuedToken);
61             if (issuedToken != null)
62             {
63                 CoordinationServiceSecurity.AddIssuedToken(message, issuedToken);
64             }
65 
66             WsatTransactionHeader header = new WsatTransactionHeader(context, this.protocolVersion);
67             message.Headers.Add(header);
68         }
69 
70         //=======================================================================================
ForcePromotion(Transaction transaction)71         void ForcePromotion(Transaction transaction)
72         {
73             // Force promotion. This may throw TransactionException.
74             // We used to check the DistributedIdentifier property first, but VSWhidbey bug 547901
75             // prevents us from doing so reliably in multi-threaded scenarios (there is a ----
76             // in the System.Transactions code that can cause a NullReferenceException if we ask
77             // for the identifier while the transaction is being promoted)
78             TransactionInterop.GetTransmitterPropagationToken(transaction);
79         }
80 
81         //=======================================================================================
82         // The demand is not added now (in 4.5), to avoid a breaking change. To be considered in the next version.
83         /*
84         // We demand full trust because we use CoordinationServiceSecurity from a non-APTCA assembly and CoordinationServiceSecurity.GetIssuedToken(..) can call Environment.FailFast.
85         // It's recommended to not let partially trusted callers to bring down the process.
86         // WSATs are not supported in partial trust, so customers should not be broken by this demand.
87         [PermissionSet(SecurityAction.Demand, Unrestricted = true)]
88         */
ReadTransaction(Message message)89         public override TransactionInfo ReadTransaction(Message message)
90         {
91             EnsureInitialized();
92 
93             CoordinationContext context = WsatTransactionHeader.GetCoordinationContext(message, this.protocolVersion);
94             if (context == null)
95                 return null;
96 
97             // Incoming transaction tokens are optional
98             RequestSecurityTokenResponse issuedToken;
99             try
100             {
101                 issuedToken = CoordinationServiceSecurity.GetIssuedToken(message, context.Identifier, this.protocolVersion);
102             }
103             catch (XmlException e)
104             {
105                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
106                     new TransactionException(SR.FailedToDeserializeIssuedToken, e));
107             }
108 
109             return new WsatTransactionInfo(this.wsatProxy, context, issuedToken);
110         }
111 
112         //=======================================================================================
CreateTransactionInfo(CoordinationContext context, RequestSecurityTokenResponse issuedToken)113         public WsatTransactionInfo CreateTransactionInfo(CoordinationContext context,
114                                                          RequestSecurityTokenResponse issuedToken)
115         {
116             return new WsatTransactionInfo(this.wsatProxy, context, issuedToken);
117         }
118 
119         //=======================================================================================
120         // The demand is not added now (in 4.5), to avoid a breaking change. To be considered in the next version.
121         /*
122         // We demand full trust because we use CoordinationContext and CoordinationServiceSecurity from a non-APTCA assembly.
123         // The CoordinationContext constructor can call Environment.FailFast and it's recommended to not let partially trusted callers to bring down the process.
124         // WSATs are not supported in partial trust, so customers should not be broken by this demand.
125         [PermissionSet(SecurityAction.Demand, Unrestricted = true)]
126         */
MarshalAsCoordinationContext(Transaction transaction, out CoordinationContext context, out RequestSecurityTokenResponse issuedToken)127         public void MarshalAsCoordinationContext(Transaction transaction,
128                                                  out CoordinationContext context,
129                                                  out RequestSecurityTokenResponse issuedToken)
130         {
131             Guid transactionId = transaction.TransactionInformation.DistributedIdentifier;
132             string nonNativeContextId = null;
133 
134             context = new CoordinationContext(this.protocolVersion);
135 
136             // Get timeout, description and isolation flags
137             uint timeout;
138             IsolationFlags isoFlags;
139             string description;
140             OleTxTransactionFormatter.GetTransactionAttributes(transaction,
141                                                                out timeout,
142                                                                out isoFlags,
143                                                                out description);
144             context.IsolationFlags = isoFlags;
145             context.Description = description;
146 
147             // If we can, use cached extended information
148             // Note - it may be worth using outgoing contexts more than once.
149             // We'll let performance profiling decide that question
150             WsatExtendedInformation info;
151             if (WsatExtendedInformationCache.Find(transaction, out info))
152             {
153                 context.Expires = info.Timeout;
154 
155                 // The extended info cache only contains an identifier when it's non-native
156                 if (!string.IsNullOrEmpty(info.Identifier))
157                 {
158                     context.Identifier = info.Identifier;
159                     nonNativeContextId = info.Identifier;
160                 }
161             }
162             else
163             {
164                 context.Expires = timeout;
165                 if (context.Expires == 0)
166                 {
167                     // If the timeout is zero, there are two possibilities:
168                     // 1) This is a root transaction with an infinite timeout.
169                     // 2) This is a subordinate transaction whose timeout was not flowed.
170                     // We have no mechanism for distinguishing between the two cases.
171                     //
172                     // We could always return zero here, instead of using the local max timeout.
173                     // The problem is that the 2004/08 WS-C spec does not specify the meaning
174                     // of a zero expires field. While we accept zero to mean "as large as possible"
175                     // it would be risky to expect others to do the same.  So we only propagate
176                     // zero in the expires field if the local max timeout has been disabled.
177                     //
178                     // This is MB 34596: how can we flow the real timeout?
179                     context.Expires = (uint)TimeoutHelper.ToMilliseconds(this.wsatConfig.MaxTimeout);
180                 }
181             }
182 
183             if (context.Identifier == null)
184             {
185                 context.Identifier = CoordinationContext.CreateNativeIdentifier(transactionId);
186                 nonNativeContextId = null;
187             }
188 
189             string tokenId;
190             if (!this.wsatConfig.IssuedTokensEnabled)
191             {
192                 tokenId = null;
193                 issuedToken = null;
194             }
195             else
196             {
197                 CoordinationServiceSecurity.CreateIssuedToken(transactionId,
198                                                               context.Identifier,
199                                                               this.protocolVersion,
200                                                               out issuedToken,
201                                                               out tokenId);
202             }
203 
204             AddressHeader refParam = new WsatRegistrationHeader(transactionId, nonNativeContextId, tokenId);
205             context.RegistrationService = wsatConfig.CreateRegistrationService(refParam, this.protocolVersion);
206             context.IsolationLevel = transaction.IsolationLevel;
207             context.LocalTransactionId = transactionId;
208 
209             if (this.wsatConfig.OleTxUpgradeEnabled)
210             {
211                 context.PropagationToken = TransactionInterop.GetTransmitterPropagationToken(transaction);
212             }
213         }
214     }
215 
216     //------------------------------------------------------------------------------------------
217     //                          Versioned Wsat transaction formatters
218     //------------------------------------------------------------------------------------------
219 
220     class WsatTransactionFormatter10 : WsatTransactionFormatter
221     {
222         static WsatTransactionHeader emptyTransactionHeader = new WsatTransactionHeader(null, ProtocolVersion.Version10);
223 
WsatTransactionFormatter10()224         public WsatTransactionFormatter10() : base(ProtocolVersion.Version10) { }
225 
226         //=======================================================================================
227         public override MessageHeader EmptyTransactionHeader
228         {
229             get { return emptyTransactionHeader; }
230         }
231     }
232 
233     class WsatTransactionFormatter11 : WsatTransactionFormatter
234     {
235         static WsatTransactionHeader emptyTransactionHeader = new WsatTransactionHeader(null, ProtocolVersion.Version11);
236 
WsatTransactionFormatter11()237         public WsatTransactionFormatter11() : base(ProtocolVersion.Version11) { }
238 
239         //=======================================================================================
240         public override MessageHeader EmptyTransactionHeader
241         {
242             get { return emptyTransactionHeader; }
243         }
244     }
245 }
246