private async Task <bool> SendNativePageAsync(OpenIdConnectResponse response) { using (var buffer = new MemoryStream()) using (var writer = new StreamWriter(buffer)) { foreach (var parameter in response.GetParameters()) { var value = parameter.Value as JValue; if (value == null) { Logger.LogWarning("A parameter whose type was incompatible was ignored " + "and excluded from the response: '{Parameter}'.", parameter.Key); continue; } writer.WriteLine("{0}:{1}", parameter.Key, (string)value); } writer.Flush(); if (!string.IsNullOrEmpty(response.Error)) { Response.StatusCode = 400; } Response.ContentLength = buffer.Length; Response.ContentType = "text/plain;charset=UTF-8"; Response.Headers[HeaderNames.CacheControl] = "no-cache"; Response.Headers[HeaderNames.Pragma] = "no-cache"; Response.Headers[HeaderNames.Expires] = "-1"; buffer.Seek(offset: 0, loc: SeekOrigin.Begin); await buffer.CopyToAsync(Response.Body, 4096, Context.RequestAborted); // Return true to stop processing the request. return(true); } }
private async Task <bool> SendNativePageAsync(OpenIdConnectResponse response) { using (var buffer = new MemoryStream()) using (var writer = new StreamWriter(buffer)) { foreach (var parameter in response.GetParameters()) { // Ignore null or empty parameters, including JSON // objects that can't be represented as strings. var value = (string)parameter.Value; if (string.IsNullOrEmpty(value)) { continue; } writer.WriteLine("{0}:{1}", parameter.Key, value); } writer.Flush(); if (!string.IsNullOrEmpty(response.Error)) { Response.StatusCode = 400; } Response.ContentLength = buffer.Length; Response.ContentType = "text/plain;charset=UTF-8"; Response.Headers[HeaderNames.CacheControl] = "no-cache"; Response.Headers[HeaderNames.Pragma] = "no-cache"; Response.Headers[HeaderNames.Expires] = "-1"; buffer.Seek(offset: 0, loc: SeekOrigin.Begin); await buffer.CopyToAsync(Response.Body, 4096, Context.RequestAborted); // Return true to stop processing the request. return(true); } }
private async Task <bool> SendLogoutResponseAsync(OpenIdConnectResponse response) { var request = Context.GetOpenIdConnectRequest(); Context.SetOpenIdConnectResponse(response); response.SetProperty(OpenIdConnectConstants.Properties.MessageType, OpenIdConnectConstants.MessageTypes.LogoutResponse); // Note: as this stage, the request may be null (e.g if it couldn't be extracted from the HTTP request). var notification = new ApplyLogoutResponseContext(Context, Options, request, response) { PostLogoutRedirectUri = request?.GetProperty <string>(OpenIdConnectConstants.Properties.PostLogoutRedirectUri) }; await Options.Provider.ApplyLogoutResponse(notification); if (notification.HandledResponse) { Logger.LogDebug("The logout request was handled in user code."); return(true); } else if (notification.Skipped) { Logger.LogDebug("The default logout request handling was skipped from user code."); return(false); } if (!string.IsNullOrEmpty(response.Error)) { // Apply a 400 status code by default. Response.StatusCode = 400; if (Options.ApplicationCanDisplayErrors) { // Return false to allow the rest of // the pipeline to handle the request. return(false); } Logger.LogInformation("The logout response was successfully returned " + "as a plain-text document: {Response}.", response); return(await SendNativePageAsync(response)); } // Don't redirect the user agent if no explicit post_logout_redirect_uri was // provided or if the URI was not fully validated by the application code. if (string.IsNullOrEmpty(notification.PostLogoutRedirectUri)) { Logger.LogInformation("The logout response was successfully returned: {Response}.", response); return(true); } // At this stage, throw an exception if the request was not properly extracted, if (request == null) { throw new InvalidOperationException("The logout response cannot be returned."); } // Attach the request state to the end session response. if (string.IsNullOrEmpty(response.State)) { response.State = request.State; } // Create a new parameters dictionary holding the name/value pairs. var parameters = new Dictionary <string, string>(); foreach (var parameter in response.GetParameters()) { // Ignore null or empty parameters, including JSON // objects that can't be represented as strings. var value = (string)parameter.Value; if (string.IsNullOrEmpty(value)) { continue; } parameters.Add(parameter.Key, value); } Logger.LogInformation("The logout response was successfully returned to '{PostLogoutRedirectUri}': {Response}.", notification.PostLogoutRedirectUri, response); var location = WebUtilities.AddQueryString(notification.PostLogoutRedirectUri, parameters); Response.Redirect(location); return(true); }
private async Task <bool> SendAuthorizationResponseAsync(OpenIdConnectResponse response, AuthenticationTicket ticket = null) { var request = Context.GetOpenIdConnectRequest(); Context.SetOpenIdConnectResponse(response); response.SetProperty(OpenIdConnectConstants.Properties.MessageType, OpenIdConnectConstants.MessageTypes.AuthorizationResponse); // Note: as this stage, the request may be null (e.g if it couldn't be extracted from the HTTP request). var notification = new ApplyAuthorizationResponseContext(Context, Scheme, Options, ticket, request, response) { RedirectUri = request?.GetProperty <string>(OpenIdConnectConstants.Properties.ValidatedRedirectUri), ResponseMode = request?.ResponseMode }; // If the response_mode parameter was not specified, try to infer it. if (string.IsNullOrEmpty(notification.ResponseMode) && !string.IsNullOrEmpty(notification.RedirectUri)) { notification.ResponseMode = request.IsFormPostResponseMode() ? OpenIdConnectConstants.ResponseModes.FormPost : request.IsFragmentResponseMode() ? OpenIdConnectConstants.ResponseModes.Fragment : request.IsQueryResponseMode() ? OpenIdConnectConstants.ResponseModes.Query : null; } await Provider.ApplyAuthorizationResponse(notification); if (notification.Result != null) { if (notification.Result.Handled) { Logger.LogDebug("The authorization request was handled in user code."); return(true); } else if (notification.Result.Skipped) { Logger.LogDebug("The default authorization request handling was skipped from user code."); return(false); } } // Directly display an error page if redirect_uri cannot be used to // redirect the user agent back to the client application. if (!string.IsNullOrEmpty(response.Error) && string.IsNullOrEmpty(notification.RedirectUri)) { // Apply a 400 status code by default. Response.StatusCode = 400; if (Options.ApplicationCanDisplayErrors) { // Return false to allow the rest of // the pipeline to handle the request. return(false); } Logger.LogInformation("The authorization response was successfully returned " + "as a plain-text document: {Response}.", response); return(await SendNativePageAsync(response)); } // At this stage, throw an exception if the request was not properly extracted. if (request == null) { throw new InvalidOperationException("The authorization response cannot be returned."); } // Attach the request state to the authorization response. if (string.IsNullOrEmpty(response.State)) { response.State = request.State; } // Create a new parameters dictionary holding the name/value pairs. var parameters = new Dictionary <string, string>(); foreach (var parameter in response.GetParameters()) { // Ignore null or empty parameters, including JSON // objects that can't be represented as strings. var value = (string)parameter.Value; if (string.IsNullOrEmpty(value)) { continue; } parameters.Add(parameter.Key, value); } // Note: at this stage, the redirect_uri parameter MUST be trusted. switch (notification.ResponseMode) { case OpenIdConnectConstants.ResponseModes.FormPost: { Logger.LogInformation("The authorization response was successfully returned to " + "'{RedirectUri}' using the form post response mode: {Response}.", notification.RedirectUri, response); using (var buffer = new MemoryStream()) using (var writer = new StreamWriter(buffer)) { writer.WriteLine("<!doctype html>"); writer.WriteLine("<html>"); writer.WriteLine("<body>"); // While the redirect_uri parameter should be guarded against unknown values // by OpenIdConnectServerProvider.ValidateAuthorizationRequest, // it's still safer to encode it to avoid cross-site scripting attacks // if the authorization server has a relaxed policy concerning redirect URIs. writer.WriteLine($@"<form name=""form"" method=""post"" action=""{Options.HtmlEncoder.Encode(notification.RedirectUri)}"">"); foreach (var parameter in parameters) { var key = Options.HtmlEncoder.Encode(parameter.Key); var value = Options.HtmlEncoder.Encode(parameter.Value); writer.WriteLine($@"<input type=""hidden"" name=""{key}"" value=""{value}"" />"); } writer.WriteLine(@"<noscript>Click here to finish the authorization process: <input type=""submit"" /></noscript>"); writer.WriteLine("</form>"); writer.WriteLine("<script>document.form.submit();</script>"); writer.WriteLine("</body>"); writer.WriteLine("</html>"); writer.Flush(); Response.StatusCode = 200; Response.ContentLength = buffer.Length; Response.ContentType = "text/html;charset=UTF-8"; Response.Headers["Cache-Control"] = "no-cache"; Response.Headers["Pragma"] = "no-cache"; Response.Headers["Expires"] = "-1"; buffer.Seek(offset: 0, loc: SeekOrigin.Begin); await buffer.CopyToAsync(Response.Body, 4096, Context.RequestAborted); return(true); } } case OpenIdConnectConstants.ResponseModes.Fragment: { Logger.LogInformation("The authorization response was successfully returned to " + "'{RedirectUri}' using the fragment response mode: {Response}.", notification.RedirectUri, response); var location = notification.RedirectUri; var appender = new OpenIdConnectServerHelpers.Appender(location, '#'); foreach (var parameter in parameters) { appender.Append(parameter.Key, parameter.Value); } Response.Redirect(appender.ToString()); return(true); } case OpenIdConnectConstants.ResponseModes.Query: { Logger.LogInformation("The authorization response was successfully returned to " + "'{RedirectUri}' using the query response mode: {Response}.", notification.RedirectUri, response); var location = QueryHelpers.AddQueryString(notification.RedirectUri, parameters); Response.Redirect(location); return(true); } default: { Logger.LogError("The authorization request was rejected because the 'response_mode' " + "parameter was invalid: {ResponseMode}.", request.ResponseMode); return(await SendNativePageAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The specified 'response_mode' parameter is not supported." })); } } }
private async Task <bool> SendLogoutResponseAsync(OpenIdConnectResponse response) { var request = Context.GetOpenIdConnectRequest(); if (request == null) { request = new OpenIdConnectRequest(); } Context.SetOpenIdConnectResponse(response); var notification = new ApplyLogoutResponseContext(Context, Options, request, response); await Options.Provider.ApplyLogoutResponse(notification); if (notification.HandledResponse) { return(true); } else if (notification.Skipped) { return(false); } if (!string.IsNullOrEmpty(response.Error)) { // Apply a 400 status code by default. Response.StatusCode = 400; if (Options.ApplicationCanDisplayErrors) { // Return false to allow the rest of // the pipeline to handle the request. return(false); } return(await SendNativePageAsync(response)); } // Don't redirect the user agent if no explicit post_logout_redirect_uri was // provided or if the URI was not fully validated by the application code. if (string.IsNullOrEmpty(response.PostLogoutRedirectUri)) { return(true); } // Create a new parameters dictionary holding the name/value pairs. var parameters = new Dictionary <string, string>(); foreach (var parameter in response.GetParameters()) { // Don't include post_logout_redirect_uri in the parameters dictionary. if (string.Equals(parameter.Key, OpenIdConnectConstants.Parameters.PostLogoutRedirectUri, StringComparison.Ordinal)) { continue; } var value = parameter.Value as JValue; if (value == null) { Logger.LogWarning("A parameter whose type was incompatible was ignored and excluded " + "from the logout response: '{Parameter}'.", parameter.Key); continue; } parameters.Add(parameter.Key, (string)value); } var location = QueryHelpers.AddQueryString(response.PostLogoutRedirectUri, parameters); Response.Redirect(location); return(true); }
private async Task <bool> SendLogoutResponseAsync(OpenIdConnectResponse response) { var request = Context.GetOpenIdConnectRequest(); Context.SetOpenIdConnectResponse(response); response.SetProperty(OpenIdConnectConstants.Properties.MessageType, OpenIdConnectConstants.MessageTypes.LogoutResponse); // Note: as this stage, the request may be null (e.g if it couldn't be extracted from the HTTP request). var notification = new ApplyLogoutResponseContext(Context, Options, request, response) { PostLogoutRedirectUri = request?.GetProperty <string>(OpenIdConnectConstants.Properties.PostLogoutRedirectUri) }; await Options.Provider.ApplyLogoutResponse(notification); if (notification.HandledResponse) { Logger.LogDebug("The logout request was handled in user code."); return(true); } else if (notification.Skipped) { Logger.LogDebug("The default logout request handling was skipped from user code."); return(false); } if (!string.IsNullOrEmpty(response.Error)) { // Apply a 400 status code by default. Response.StatusCode = 400; if (Options.ApplicationCanDisplayErrors) { // Return false to allow the rest of // the pipeline to handle the request. return(false); } Logger.LogInformation("The logout response was successfully returned " + "as a plain-text document: {Response}.", response); return(await SendNativePageAsync(response)); } // Don't redirect the user agent if no explicit post_logout_redirect_uri was // provided or if the URI was not fully validated by the application code. if (string.IsNullOrEmpty(notification.PostLogoutRedirectUri)) { Logger.LogInformation("The logout response was successfully returned: {Response}.", response); return(true); } // At this stage, throw an exception if the request was not properly extracted, if (request == null) { throw new InvalidOperationException("The logout response cannot be returned."); } // Attach the request state to the end session response. if (string.IsNullOrEmpty(response.State)) { response.State = request.State; } // Note: a dictionary is deliberately not used here to allow multiple parameters with the // same name to be specified. While initially not allowed by the core OAuth2 specification, // this is now accepted by derived drafts like the OAuth2 token exchange specification. // For consistency, multiple parameters with the same name are also supported by this endpoint. var parameters = new List <KeyValuePair <string, string> >(); foreach (var parameter in response.GetParameters()) { var values = (string[])parameter.Value; if (values == null) { continue; } foreach (var value in values) { parameters.Add(new KeyValuePair <string, string>(parameter.Key, value)); } } Logger.LogInformation("The logout response was successfully returned to '{PostLogoutRedirectUri}': {Response}.", notification.PostLogoutRedirectUri, response); var location = notification.PostLogoutRedirectUri; foreach (var parameter in parameters) { location = WebUtilities.AddQueryString(location, parameter.Key, parameter.Value); } Response.Redirect(location); return(true); }
private async Task <bool> SendAuthorizationResponseAsync(OpenIdConnectResponse response, AuthenticationTicket ticket = null) { var request = Context.GetOpenIdConnectRequest(); if (request == null) { request = new OpenIdConnectRequest(); } Context.SetOpenIdConnectResponse(response); var notification = new ApplyAuthorizationResponseContext(Context, Options, ticket, request, response); await Options.Provider.ApplyAuthorizationResponse(notification); if (notification.HandledResponse) { return(true); } else if (notification.Skipped) { return(false); } if (!string.IsNullOrEmpty(response.Error)) { // Directly display an error page if redirect_uri cannot be used to // redirect the user agent back to the client application. if (string.IsNullOrEmpty(response.RedirectUri)) { // Apply a 400 status code by default. Response.StatusCode = 400; if (Options.ApplicationCanDisplayErrors) { // Return false to allow the rest of // the pipeline to handle the request. return(false); } return(await SendNativePageAsync(response)); } } // Create a new parameters dictionary holding the name/value pairs. var parameters = new Dictionary <string, string>(); foreach (var parameter in response.GetParameters()) { // Don't include redirect_uri in the parameters dictionary. if (string.Equals(parameter.Key, OpenIdConnectConstants.Parameters.RedirectUri, StringComparison.Ordinal)) { continue; } var value = parameter.Value as JValue; if (value == null) { Logger.LogWarning("A parameter whose type was incompatible was ignored and excluded " + "from the authorization response: '{Parameter}'.", parameter.Key); continue; } parameters.Add(parameter.Key, (string)value); } // Note: at this stage, the redirect_uri parameter MUST be trusted. if (request.IsFormPostResponseMode()) { using (var buffer = new MemoryStream()) using (var writer = new StreamWriter(buffer)) { writer.WriteLine("<!doctype html>"); writer.WriteLine("<html>"); writer.WriteLine("<body>"); // While the redirect_uri parameter should be guarded against unknown values // by OpenIdConnectServerProvider.ValidateAuthorizationRequest, // it's still safer to encode it to avoid cross-site scripting attacks // if the authorization server has a relaxed policy concerning redirect URIs. writer.WriteLine($"<form name='form' method='post' action='{Options.HtmlEncoder.Encode(response.RedirectUri)}'>"); foreach (var parameter in parameters) { var key = Options.HtmlEncoder.Encode(parameter.Key); var value = Options.HtmlEncoder.Encode(parameter.Value); writer.WriteLine($"<input type='hidden' name='{key}' value='{value}' />"); } writer.WriteLine("<noscript>Click here to finish the authorization process: <input type='submit' /></noscript>"); writer.WriteLine("</form>"); writer.WriteLine("<script>document.form.submit();</script>"); writer.WriteLine("</body>"); writer.WriteLine("</html>"); writer.Flush(); Response.StatusCode = 200; Response.ContentLength = buffer.Length; Response.ContentType = "text/html;charset=UTF-8"; buffer.Seek(offset: 0, loc: SeekOrigin.Begin); await buffer.CopyToAsync(Response.Body, 4096, Context.RequestAborted); return(true); } } else if (request.IsFragmentResponseMode()) { var location = response.RedirectUri; var appender = new Appender(location, '#'); foreach (var parameter in parameters) { appender.Append(parameter.Key, parameter.Value); } Response.Redirect(appender.ToString()); return(true); } else if (request.IsQueryResponseMode()) { var location = QueryHelpers.AddQueryString(response.RedirectUri, parameters); Response.Redirect(location); return(true); } Logger.LogError("The authorization request was rejected because the 'response_mode' " + "parameter was invalid: {ResponseMode}.", request.ResponseMode); return(await SendNativePageAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "response_mode unsupported" })); }
private async Task <bool> SendLogoutResponseAsync(OpenIdConnectResponse response) { var request = Context.GetOpenIdConnectRequest(); Context.SetOpenIdConnectResponse(response); response.SetProperty(OpenIdConnectConstants.Properties.MessageType, OpenIdConnectConstants.MessageTypes.LogoutResponse); var notification = new ApplyLogoutResponseContext(Context, Options, request, response); await Options.Provider.ApplyLogoutResponse(notification); if (notification.HandledResponse) { Logger.LogDebug("The logout request was handled in user code."); return(true); } else if (notification.Skipped) { Logger.LogDebug("The default logout request handling was skipped from user code."); return(false); } if (!string.IsNullOrEmpty(response.Error)) { // Apply a 400 status code by default. Response.StatusCode = 400; if (Options.ApplicationCanDisplayErrors) { // Return false to allow the rest of // the pipeline to handle the request. return(false); } Logger.LogInformation("The logout response was successfully returned " + "as a plain-text document: {Response}", response); return(await SendNativePageAsync(response)); } Logger.LogInformation("The logout response was successfully returned: {Response}", response); // Don't redirect the user agent if no explicit post_logout_redirect_uri was // provided or if the URI was not fully validated by the application code. if (string.IsNullOrEmpty(response.PostLogoutRedirectUri)) { return(true); } // Create a new parameters dictionary holding the name/value pairs. var parameters = new Dictionary <string, string>(); foreach (var parameter in response.GetParameters()) { // Don't include post_logout_redirect_uri in the parameters dictionary. if (string.Equals(parameter.Key, OpenIdConnectConstants.Parameters.PostLogoutRedirectUri, StringComparison.Ordinal)) { continue; } // Ignore null or empty parameters, including JSON // objects that can't be represented as strings. var value = (string)parameter.Value; if (string.IsNullOrEmpty(value)) { continue; } parameters.Add(parameter.Key, value); } var location = QueryHelpers.AddQueryString(response.PostLogoutRedirectUri, parameters); Response.Redirect(location); return(true); }