Line data Source code
1 : #region Copyright
2 : // // -----------------------------------------------------------------------
3 : // // <copyright company="Chinchilla Software Limited">
4 : // // Copyright Chinchilla Software Limited. All rights reserved.
5 : // // </copyright>
6 : // // -----------------------------------------------------------------------
7 : #endregion
8 :
9 : using System;
10 : using System.Collections.Generic;
11 : using System.Linq;
12 : using System.Net;
13 : using System.Net.Http;
14 : using System.Net.Http.Headers;
15 : using System.Web.Http;
16 : using cdmdotnet.Logging;
17 : using Cqrs.Authentication;
18 : using Cqrs.Services;
19 : using System.Net.Http.Formatting;
20 : using System.Web;
21 : using Cqrs.Configuration;
22 :
23 : namespace Cqrs.WebApi
24 : {
25 : /// <summary>
26 : /// A <see cref="ApiController"/> that expects the <see cref="ISingleSignOnToken.Token"/> to be sent as a <see cref="HttpHeaders"/> with a key whose name is defined by the <see cref="System.Configuration.ConfigurationManager.AppSettings"/> "Cqrs.Web.AuthenticationTokenName", in accordance with OAuth specifications
27 : /// </summary>
28 : /// <remarks>
29 : /// See https://www.asp.net/web-api/overview/getting-started-with-aspnet-web-api/creating-api-help-pages for details on adding WebApi Help Pages.
30 : /// </remarks>
31 : public abstract class CqrsApiController
32 : : ApiController
33 2 : {
34 : /// <summary>
35 : /// Instantiates a new instance of <see cref="CqrsApiController"/>.
36 : /// </summary>
37 : protected CqrsApiController(ILogger logger, ICorrelationIdHelper correlationIdHelper, IConfigurationManager configurationManager)
38 : {
39 : CorrelationIdHelper = correlationIdHelper;
40 : ConfigurationManager = configurationManager;
41 : Logger = logger;
42 : }
43 :
44 : /// <summary>
45 : /// Gets or set the <see cref="ICorrelationIdHelper"/>.
46 : /// </summary>
47 : protected ICorrelationIdHelper CorrelationIdHelper { get; private set; }
48 :
49 : /// <summary>
50 : /// Gets or set the <see cref="ILogger"/>.
51 : /// </summary>
52 : protected ILogger Logger { get; private set; }
53 :
54 : /// <summary>
55 : /// Gets or set the <see cref="IConfigurationManager"/>.
56 : /// </summary>
57 : protected IConfigurationManager ConfigurationManager { get; private set; }
58 :
59 : /// <summary>
60 : /// Extracts the authentication token looking for a <see cref="KeyValuePair{TKey,TValue}"/> where the key as defined by the <see cref="System.Configuration.ConfigurationManager.AppSettings"/> "Cqrs.Web.AuthenticationTokenName",
61 : /// from the <see cref="HttpRequest.Headers"/>, if one isn't found we then try the <see cref="HttpRequest.Cookies"/>
62 : /// </summary>
63 : protected virtual string GetToken()
64 : {
65 : string authenticationTokenName = ConfigurationManager.GetSetting("Cqrs.Web.AuthenticationTokenName") ?? "X-Token";
66 :
67 : string xToken = null;
68 : IEnumerable<string> tokenValue;
69 : if (Request.Headers.TryGetValues(authenticationTokenName, out tokenValue))
70 : xToken = tokenValue.First();
71 : else
72 : {
73 : CookieHeaderValue cookie = Request.Headers.GetCookies(authenticationTokenName).FirstOrDefault();
74 : if (cookie != null)
75 : xToken = cookie[authenticationTokenName].Value;
76 : else
77 : {
78 : string[] queryStrings = (string.IsNullOrWhiteSpace(Request.RequestUri.Query) ? "?" : Request.RequestUri.Query)
79 : .Substring(1)
80 : .Split('&');
81 : foreach(string queryString in queryStrings)
82 : {
83 : string[] queryStringParts = queryString.Split('=');
84 : if (queryStringParts.Length != 2)
85 : continue;
86 : if (queryStringParts[0].ToLowerInvariant() == authenticationTokenName.ToLowerInvariant())
87 : {
88 : xToken = queryStringParts[1];
89 : break;
90 : }
91 : }
92 : }
93 : }
94 :
95 : return xToken;
96 : }
97 :
98 : /// <summary>
99 : /// Create a <see cref="IServiceRequest{TAuthenticationToken}"/> setting header information.
100 : /// </summary>
101 : protected virtual IServiceRequest<TSingleSignOnToken> CreateRequest<TSingleSignOnToken>()
102 : {
103 : return new ServiceRequest<TSingleSignOnToken>
104 : {
105 : AuthenticationToken = CreateAuthenticationToken<TSingleSignOnToken>(),
106 : CorrelationId = CorrelationIdHelper.GetCorrelationId()
107 : };
108 : }
109 :
110 : /// <summary>
111 : /// Create a <see cref="IServiceRequestWithData{TAuthenticationToken,TData}"/> setting header information.
112 : /// If <paramref name="createParameterDelegate"/> is not null, it is used to populate <see cref="IServiceRequestWithData{TAuthenticationToken,TData}.Data"/> otherwise <see cref="CreateParameter{TParameters}"/> is used.
113 : /// </summary>
114 : protected virtual IServiceRequestWithData<TSingleSignOnToken, TParameters> CreateRequestWithData<TSingleSignOnToken, TParameters>(Func<TParameters> createParameterDelegate = null)
115 : where TParameters : new()
116 : {
117 : return new ServiceRequestWithData<TSingleSignOnToken, TParameters>
118 : {
119 : AuthenticationToken = CreateAuthenticationToken<TSingleSignOnToken>(),
120 : CorrelationId = CorrelationIdHelper.GetCorrelationId(),
121 : Data = createParameterDelegate == null ? CreateParameter<TParameters>() : createParameterDelegate()
122 : };
123 : }
124 :
125 : /// <summary>
126 : /// Create an <typeparamref name="TSingleSignOnToken"/>.
127 : /// </summary>
128 : /// <typeparam name="TSingleSignOnToken">The <see cref="Type"/> of <see cref="ISingleSignOnToken"/>.</typeparam>
129 : protected virtual TSingleSignOnToken CreateAuthenticationToken<TSingleSignOnToken>()
130 : {
131 : if (typeof(TSingleSignOnToken) == typeof(int))
132 : return (TSingleSignOnToken)(object)int.Parse(GetToken());
133 : if (typeof(TSingleSignOnToken) == typeof(Guid))
134 : return (TSingleSignOnToken)(object)new Guid(GetToken());
135 : if (typeof(TSingleSignOnToken) == typeof(string))
136 : return (TSingleSignOnToken)(object)GetToken();
137 :
138 : if (typeof(TSingleSignOnToken) == typeof(SingleSignOnToken))
139 : return (TSingleSignOnToken)(object)new SingleSignOnToken
140 : {
141 : DateIssued = GetDateTokenIssued(),
142 : Token = GetToken(),
143 : TimeOfExpiry = GetTokenTimeOfExpiry()
144 : };
145 : if (typeof(TSingleSignOnToken) == typeof(SingleSignOnTokenWithUserRsn))
146 : return (TSingleSignOnToken)(object)new SingleSignOnTokenWithUserRsn
147 : {
148 : DateIssued = GetDateTokenIssued(),
149 : Token = GetToken(),
150 : TimeOfExpiry = GetTokenTimeOfExpiry()
151 : };
152 : if (typeof(TSingleSignOnToken) == typeof(SingleSignOnTokenWithCompanyRsn))
153 : return (TSingleSignOnToken)(object)new SingleSignOnTokenWithCompanyRsn
154 : {
155 : DateIssued = GetDateTokenIssued(),
156 : Token = GetToken(),
157 : TimeOfExpiry = GetTokenTimeOfExpiry()
158 : };
159 : if (typeof(TSingleSignOnToken) == typeof(SingleSignOnTokenWithUserRsnAndCompanyRsn))
160 : return (TSingleSignOnToken)(object)new SingleSignOnTokenWithUserRsnAndCompanyRsn
161 : {
162 : DateIssued = GetDateTokenIssued(),
163 : Token = GetToken(),
164 : TimeOfExpiry = GetTokenTimeOfExpiry()
165 : };
166 :
167 : if (typeof(TSingleSignOnToken) == typeof(ISingleSignOnToken))
168 : return (TSingleSignOnToken)(object)new SingleSignOnToken
169 : {
170 : DateIssued = GetDateTokenIssued(),
171 : Token = GetToken(),
172 : TimeOfExpiry = GetTokenTimeOfExpiry()
173 : };
174 : if (typeof(TSingleSignOnToken) == typeof(ISingleSignOnTokenWithUserRsn))
175 : return (TSingleSignOnToken)(object)new SingleSignOnTokenWithUserRsn
176 : {
177 : DateIssued = GetDateTokenIssued(),
178 : Token = GetToken(),
179 : TimeOfExpiry = GetTokenTimeOfExpiry()
180 : };
181 : if (typeof(TSingleSignOnToken) == typeof(ISingleSignOnTokenWithCompanyRsn))
182 : return (TSingleSignOnToken)(object)new SingleSignOnTokenWithCompanyRsn
183 : {
184 : DateIssued = GetDateTokenIssued(),
185 : Token = GetToken(),
186 : TimeOfExpiry = GetTokenTimeOfExpiry()
187 : };
188 : if (typeof(TSingleSignOnToken) == typeof(ISingleSignOnTokenWithUserRsnAndCompanyRsn))
189 : return (TSingleSignOnToken)(object)new SingleSignOnTokenWithUserRsnAndCompanyRsn
190 : {
191 : DateIssued = GetDateTokenIssued(),
192 : Token = GetToken(),
193 : TimeOfExpiry = GetTokenTimeOfExpiry()
194 : };
195 : return default(TSingleSignOnToken);
196 : }
197 :
198 : /// <summary>
199 : /// Creates a blank <typeparamref name="TParameters"/>
200 : /// </summary>
201 : protected virtual TParameters CreateParameter<TParameters>()
202 : where TParameters : new()
203 : {
204 : return new TParameters();
205 : }
206 :
207 : /// <summary>
208 : /// Get the <see cref="DateTime"/> the current authentication token was issued.
209 : /// </summary>
210 : /// <returns>default(DateTime)</returns>
211 : protected virtual DateTime GetDateTokenIssued()
212 : {
213 : return default(DateTime);
214 : }
215 :
216 : /// <summary>
217 : /// Get the <see cref="DateTime"/> the current authentication token will expire.
218 : /// </summary>
219 : /// <returns>default(DateTime)</returns>
220 : protected virtual DateTime GetTokenTimeOfExpiry()
221 : {
222 : return default(DateTime);
223 : }
224 :
225 : /// <summary>
226 : /// Completes the provided <paramref name="response"/> by setting the appropriate <see cref="HttpResponseMessage.StatusCode"/> and populating <see cref="HttpResponseMessage.Content"/> with <paramref name="serviceResponse"/>.
227 : /// </summary>
228 : protected virtual HttpResponseMessage CompleteResponse<TServiceResponse>(HttpResponseMessage response, TServiceResponse serviceResponse)
229 : where TServiceResponse : IServiceResponse
230 : {
231 : serviceResponse.CorrelationId = CorrelationIdHelper.GetCorrelationId();
232 :
233 : HttpConfiguration configuration = Request.GetConfiguration();
234 : var contentNegotiator = configuration.Services.GetContentNegotiator();
235 : ContentNegotiationResult negotiationResult = contentNegotiator.Negotiate(typeof(IServiceResponse), Request, configuration.Formatters);
236 :
237 : response.Content = new ObjectContent<IServiceResponse>(serviceResponse, negotiationResult.Formatter, negotiationResult.MediaType);
238 :
239 : switch (serviceResponse.State)
240 : {
241 : case ServiceResponseStateType.Succeeded:
242 : response.StatusCode = HttpStatusCode.Accepted;
243 : break;
244 : case ServiceResponseStateType.FailedAuthentication:
245 : response.StatusCode = HttpStatusCode.Forbidden;
246 : break;
247 : case ServiceResponseStateType.FailedAuthorisation:
248 : response.StatusCode = HttpStatusCode.Unauthorized;
249 : break;
250 : case ServiceResponseStateType.FailedValidation:
251 : response.StatusCode = HttpStatusCode.PreconditionFailed;
252 : break;
253 : case ServiceResponseStateType.FailedWithAFatalException:
254 : response.StatusCode = HttpStatusCode.InternalServerError;
255 : break;
256 : case ServiceResponseStateType.FailedWithAnUnexpectedException:
257 : response.StatusCode = HttpStatusCode.InternalServerError;
258 : break;
259 : case ServiceResponseStateType.Unknown:
260 : response.StatusCode = HttpStatusCode.BadRequest;
261 : break;
262 : default:
263 : response.StatusCode = HttpStatusCode.Ambiguous;
264 : break;
265 : }
266 :
267 : return response;
268 : }
269 :
270 : /// <summary>
271 : /// Creates a new <see cref="HttpResponseMessage"/> and completes the response by setting the appropriate <see cref="HttpResponseMessage.StatusCode"/> and populating <see cref="HttpResponseMessage.Content"/> with <paramref name="serviceResponse"/>.
272 : /// </summary>
273 : protected virtual HttpResponseMessage CompleteResponse<TServiceResponse>(TServiceResponse serviceResponse)
274 : where TServiceResponse : IServiceResponse
275 : {
276 : var response = new HttpResponseMessage();
277 :
278 : return CompleteResponse(response, serviceResponse);
279 : }
280 :
281 : /// <summary>
282 : /// Creates a new <see cref="HttpResponseMessage"/> and completes the response by setting the appropriate <see cref="HttpResponseMessage.StatusCode"/> and populating <see cref="HttpResponseMessage.Content"/> with <paramref name="serviceResponse"/>.
283 : /// </summary>
284 : protected virtual HttpResponseMessage<TServiceResponse> CompleteResponseWithData<TServiceResponse>(TServiceResponse serviceResponse)
285 : where TServiceResponse : IServiceResponse
286 : {
287 : var response = new HttpResponseMessage<TServiceResponse>();
288 :
289 : CompleteResponse(response, serviceResponse);
290 :
291 : return response;
292 : }
293 : }
294 :
295 : /// <summary>
296 : /// A <see cref="ApiController"/> that expects the <see cref="ISingleSignOnToken.Token"/> to be sent as a <see cref="HttpHeaders"/> with a key of "X-Token", in accordance with OAuth specifications
297 : /// </summary>
298 : /// <remarks>
299 : /// See https://www.asp.net/web-api/overview/getting-started-with-aspnet-web-api/creating-api-help-pages for details on adding WebApi Help Pages.
300 : /// </remarks>
301 : public abstract class CqrsApiController<TAuthenticationToken>
302 : : CqrsApiController
303 : {
304 : /// <summary>
305 : /// Instantiates a new instance of <see cref="CqrsApiController"/>.
306 : /// </summary>
307 : protected CqrsApiController(ILogger logger, ICorrelationIdHelper correlationIdHelper, IConfigurationManager configurationManager, IAuthenticationTokenHelper<TAuthenticationToken> authenticationTokenHelper)
308 : : base(logger, correlationIdHelper, configurationManager)
309 : {
310 : AuthenticationTokenHelper = authenticationTokenHelper;
311 : }
312 :
313 : /// <summary>
314 : /// Gets or set the <see cref="IAuthenticationTokenHelper{TAuthenticationToken}"/>.
315 : /// </summary>
316 : protected IAuthenticationTokenHelper<TAuthenticationToken> AuthenticationTokenHelper { get; private set; }
317 :
318 : /// <summary>
319 : /// Reads the current authentication token for the request from <see cref="AuthenticationTokenHelper"/>.
320 : /// </summary>
321 : protected override string GetToken()
322 : {
323 : TAuthenticationToken token = AuthenticationTokenHelper.GetAuthenticationToken();
324 : if (token != null)
325 : return token.ToString();
326 : return null;
327 : }
328 :
329 : /// <summary>
330 : /// Create a <see cref="IServiceRequest{TAuthenticationToken}"/> setting header information.
331 : /// </summary>
332 : protected virtual IServiceRequest<TAuthenticationToken> CreateRequest()
333 : {
334 : TAuthenticationToken token = AuthenticationTokenHelper.GetAuthenticationToken();
335 : return new ServiceRequest<TAuthenticationToken>
336 : {
337 : AuthenticationToken = token,
338 : CorrelationId = CorrelationIdHelper.GetCorrelationId()
339 : };
340 : }
341 :
342 : /// <summary>
343 : /// Create a <see cref="IServiceRequestWithData{TAuthenticationToken,TData}"/> setting header information.
344 : /// If <paramref name="createParameterDelegate"/> is not null, it is used to populate <see cref="IServiceRequestWithData{TAuthenticationToken,TData}.Data"/> otherwise <see cref="CqrsApiController.CreateParameter{TParameters}"/> is used.
345 : /// </summary>
346 : protected virtual IServiceRequestWithData<TAuthenticationToken, TParameters> CreateRequestWithData<TParameters>(Func<TParameters> createParameterDelegate = null)
347 : where TParameters : new()
348 : {
349 : TAuthenticationToken token = AuthenticationTokenHelper.GetAuthenticationToken();
350 : return new ServiceRequestWithData<TAuthenticationToken, TParameters>
351 : {
352 : AuthenticationToken = token,
353 : CorrelationId = CorrelationIdHelper.GetCorrelationId(),
354 : Data = createParameterDelegate == null ? CreateParameter<TParameters>() : createParameterDelegate()
355 : };
356 : }
357 : }
358 : }
|