// This routine is the client-side complement of // Tug.Server.Controllers.ModelResultExt#Model() // // This routine returns an IDisposable that should be cleaned up by the caller // after they are done working with the dscResp instance to release any possible // temporary resources that were created to satisfy the model binding protected async Task <IDisposable> ExtractToResponseModel(HttpResponseMessage respMessage, DscResponse dscResp) { // We keep track of any disposables that we have to create during // the course of binding from the response the model instance and // then we'll clean all those up when the disposer is invoked var disposables = new List <IDisposable>(); var disposer = new DisposableAction( x => { foreach (var d in (List <IDisposable>)x) { d.Dispose(); } }, disposables); PropertyInfo toResultProperty = null; // Used to detect more than one result property foreach (var pi in dscResp.GetType().GetProperties()) { var toHeader = pi.GetCustomAttribute(typeof(ToHeaderAttribute)) as ToHeaderAttribute; if (toHeader != null) { var headerName = toHeader.Name; if (string.IsNullOrEmpty(headerName)) { headerName = pi.Name; } if (LOG.IsEnabled(LogLevel.Debug)) { LOG.LogDebug("Extracting Header[{headerName}]", headerName); } if (respMessage.Headers.Contains(headerName)) { object headerValue = string.Join(",", respMessage.Headers.GetValues(headerName)); if (pi.PropertyType != typeof(string)) { headerValue = ConvertTo(pi.PropertyType, headerValue); } pi.SetValue(dscResp, headerValue); } continue; } var toResult = pi.GetCustomAttribute(typeof(ToResultAttribute)) as ToResultAttribute; if (toResult != null && respMessage.Content != null) { if (toResultProperty != null) { throw new InvalidOperationException("multiple Result-mapping attributes found"); } toResultProperty = pi; object resultValue = null; var contentType = respMessage.Content.Headers.ContentType; var toResultType = pi.PropertyType; if (toResultType.IsAssignableFrom(typeof(IActionResult))) { resultValue = new FileContentResult( await respMessage.Content.ReadAsByteArrayAsync(), contentType.ToString()); } else if (toResultType.IsAssignableFrom(typeof(byte[]))) { resultValue = await respMessage.Content.ReadAsByteArrayAsync(); } else if (toResultType.IsAssignableFrom(typeof(Stream))) { // We'll remember the stream to properly clean it up var stream = await respMessage.Content.ReadAsStreamAsync(); disposables.Add(stream); resultValue = stream; } else if (toResultType.IsAssignableFrom(typeof(FileInfo))) { // If the result type is a FileInfo, we write out the // response body content to a temp file, and then register // an IDisposable that will clean up the temp file var filePath = Path.GetTempFileName(); var fileInfo = new FileInfo(filePath); disposables.Add(new DisposableAction( x => { ((FileInfo)x).Delete(); }, fileInfo)); resultValue = fileInfo; } else if (toResultType.IsAssignableFrom(typeof(string))) { resultValue = await respMessage.Content.ReadAsStringAsync(); } else { // Assume the result is model object that we can construct from // a JSON representation encoded in the result content var json = await respMessage.Content.ReadAsStringAsync(); resultValue = JsonConvert.DeserializeObject(json, toResultType, _jsonSerSettings); } if (resultValue != null) { pi.SetValue(dscResp, resultValue); } } } return(disposer); }
protected async Task <IDisposable> SendDscAsync(DscPullConfig.ServerConfig server, HttpMethod verb, string route, DscRequest dscRequ, DscResponse dscResp) { if (LOG.IsEnabled(LogLevel.Trace)) { LOG.LogTrace(nameof(SendDscAsync)); } AssertInit(); dscRequ.ProtocolVersionHeader = "2.0"; var routeExpanded = route; if (dscRequ is DscAgentRequest) { var dscAgentRequ = (DscAgentRequest)dscRequ; routeExpanded = route.Replace("{AgentId}", dscAgentRequ.AgentId.ToString()); } var requUrl = new Uri(server.ServerUrl, routeExpanded); if (LOG.IsEnabled(LogLevel.Debug)) { LOG.LogDebug("Computed request URL: [{url}]", requUrl); } var httpHandler = new HttpClientHandler(); if (server.Proxy != null) { if (LOG.IsEnabled(LogLevel.Debug)) { LOG.LogDebug("Enabling Proxy: [{proxy}] supported=[{supported}]", server.Proxy, httpHandler.SupportsProxy); } httpHandler.UseProxy = true; httpHandler.Proxy = server.Proxy; } var requMessage = new HttpRequestMessage { Method = verb, RequestUri = requUrl, }; // By default, we only send JSON unless something else was specified var contentType = dscRequ.ContentTypeHeader; if (string.IsNullOrEmpty(contentType)) { contentType = DscContentTypes.JSON; } requMessage.Headers.Accept.Clear(); requMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(contentType)); ExtractFromRequestModel(dscRequ, requMessage); // See if we need to add RegKey authorization data if (!string.IsNullOrEmpty(server.RegistrationKey) && dscRequ.MsDateHeader == COMPUTE_MS_DATE_HEADER && requMessage.Content != null) { LOG.LogInformation("Computing RegKey Authorization"); // Shhh! This is the super-secret formula for computing an // Authorization challenge when using Reg Key Authentication // Details can be found at /references/regkey-authorization.md var msDate = DateTime.UtcNow.ToString(DscRequest.X_MS_DATE_FORMAT); requMessage.Headers.Remove(DscRequest.X_MS_DATE_HEADER); requMessage.Headers.Add(DscRequest.X_MS_DATE_HEADER, msDate); dscRequ.MsDateHeader = null; var macKey = Encoding.UTF8.GetBytes(server.RegistrationKey); using (var sha = SHA256.Create()) using (var mac = new HMACSHA256(macKey)) using (var ms = new MemoryStream()) { await requMessage.Content.CopyToAsync(ms); var body = ms.ToArray(); LOG.LogDebug("Computing hash over body content"); LOG.LogDebug("BODY:-----------------------------"); LOG.LogDebug($"<{Encoding.UTF8.GetString(body)}>"); LOG.LogDebug("-----------------------------:BODY"); var digest = sha.ComputeHash(body); var digB64 = Convert.ToBase64String(digest); LOG.LogDebug(" * digB64=" + digB64); var concat = $"{digB64}\n{msDate}"; LOG.LogDebug(" * concat=" + concat); var macSig = mac.ComputeHash(Encoding.UTF8.GetBytes(concat)); var sigB64 = Convert.ToBase64String(macSig); requMessage.Headers.Authorization = new AuthenticationHeaderValue("Shared", sigB64); } } // TODO: Eventually we'll address this improper usage pattern as described here: // https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/ HttpResponseMessage respMessage = null; using (var http = _clientFactory(httpHandler)) { respMessage = await http.SendAsync(requMessage); } respMessage.EnsureSuccessStatusCode(); if (dscResp == null) { return(null); } else { return(await ExtractToResponseModel(respMessage, dscResp)); } }