//----------------------------------------------------------------------- // // Copyright (c) Outercurve Foundation. All rights reserved. // //----------------------------------------------------------------------- namespace DotNetOpenAuth.OAuth2.Messages { using System; using System.Collections.Generic; using System.Globalization; using System.Net; using System.Text; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OAuth2.ChannelElements; using Validation; /// /// A direct response sent in response to a rejected Bearer access token. /// /// /// This satisfies the spec in: http://self-issued.info/docs/draft-ietf-oauth-v2-bearer.html#authn-header /// public class UnauthorizedResponse : MessageBase, IHttpDirectResponse { /// /// The headers in the response message. /// private readonly WebHeaderCollection headers = new WebHeaderCollection(); /// /// Initializes a new instance of the class. /// /// The protocol version. protected UnauthorizedResponse(Version version = null) : base(version ?? Protocol.Default.Version) { } /// /// Initializes a new instance of the class. /// /// The request. protected UnauthorizedResponse(IDirectedProtocolMessage request) : base(request) { } #region IHttpDirectResponse Members /// /// Gets or sets the HTTP status code that the direct response should be sent with. /// public HttpStatusCode HttpStatusCode { get; set; } /// /// Gets the HTTP headers to add to the response. /// /// May be an empty collection, but must not be null. public WebHeaderCollection Headers { get { return this.headers; } } #endregion /// /// Gets or sets the well known error code. /// /// One of the values from . [MessagePart(Protocol.BearerTokenUnauthorizedResponseParameters.ErrorCode)] public string ErrorCode { get; set; } /// /// Gets or sets a human-readable explanation for developers that is not meant to be displayed to end users. /// [MessagePart(Protocol.BearerTokenUnauthorizedResponseParameters.ErrorDescription)] public string ErrorDescription { get; set; } /// /// Gets or sets an absolute URI identifying a human-readable web page explaining the error. /// [MessagePart(Protocol.BearerTokenUnauthorizedResponseParameters.ErrorUri)] public Uri ErrorUri { get; set; } /// /// Gets or sets the realm. /// /// The realm. [MessagePart(Protocol.BearerTokenUnauthorizedResponseParameters.Realm)] public string Realm { get; set; } /// /// Gets or sets the scope. /// /// The scope. [MessagePart(Protocol.BearerTokenUnauthorizedResponseParameters.Scope, Encoder = typeof(ScopeEncoder))] public HashSet Scope { get; set; } /// /// Gets the scheme to use in the WWW-Authenticate header. /// internal virtual string Scheme { get { return Protocol.BearerHttpAuthorizationScheme; } } /// /// Initializes a new instance of the class /// to inform the client that the request was invalid. /// /// The exception. /// The version of OAuth 2 that is in use. /// The error message. internal static UnauthorizedResponse InvalidRequest(ProtocolException exception, Version version = null) { Requires.NotNull(exception, "exception"); var message = new UnauthorizedResponse(version) { ErrorCode = Protocol.BearerTokenErrorCodes.InvalidRequest, ErrorDescription = exception.Message, HttpStatusCode = System.Net.HttpStatusCode.BadRequest, }; return message; } /// /// Initializes a new instance of the class /// to inform the client that the bearer token included in the request was rejected. /// /// The request. /// The exception. /// The error message. internal static UnauthorizedResponse InvalidToken(IDirectedProtocolMessage request, ProtocolException exception) { Requires.NotNull(request, "request"); Requires.NotNull(exception, "exception"); var message = new UnauthorizedResponse(request) { ErrorCode = Protocol.BearerTokenErrorCodes.InvalidToken, ErrorDescription = exception.Message, HttpStatusCode = System.Net.HttpStatusCode.Unauthorized, }; return message; } /// /// Initializes a new instance of the class /// to inform the client of the required set of scopes required to perform this operation. /// /// The request. /// The set of scopes required to perform this operation. /// The error message. internal static UnauthorizedResponse InsufficientScope(IDirectedProtocolMessage request, HashSet requiredScopes) { Requires.NotNull(request, "request"); Requires.NotNull(requiredScopes, "requiredScopes"); var message = new UnauthorizedResponse(request) { HttpStatusCode = System.Net.HttpStatusCode.Forbidden, ErrorCode = Protocol.BearerTokenErrorCodes.InsufficientScope, Scope = requiredScopes, }; return message; } /// /// Ensures the message is valid. /// protected override void EnsureValidMessage() { base.EnsureValidMessage(); // Make sure the characters used in the supplied parameters satisfy requirements. VerifyErrorCodeOrDescription(this.ErrorCode, Protocol.BearerTokenUnauthorizedResponseParameters.ErrorCode); VerifyErrorCodeOrDescription(this.ErrorDescription, Protocol.BearerTokenUnauthorizedResponseParameters.ErrorDescription); VerifyErrorUri(this.ErrorUri); // Ensure that at least one parameter is specified, as required in the spec. ErrorUtilities.VerifyProtocol( this.ErrorCode != null || this.ErrorDescription != null || this.ErrorUri != null || this.Realm != null || this.Scope != null, OAuthStrings.BearerTokenUnauthorizedAtLeastOneParameterRequired); } /// /// Ensures the error or error_description parameters contain only allowed characters. /// /// The argument. /// The name of the parameter being validated. Used when errors are reported. private static void VerifyErrorCodeOrDescription(string value, string parameterName) { if (value != null) { for (int i = 0; i < value.Length; i++) { // The allowed set of characters comes from http://self-issued.info/docs/draft-ietf-oauth-v2-bearer.html#authn-header char ch = value[i]; if (!((ch >= '\x20' && ch <= '\x21') || (ch >= '\x23' && ch <= '\x5B') || (ch >= '\x5D' && ch <= '\x7E'))) { ErrorUtilities.ThrowProtocol(OAuthStrings.ParameterContainsIllegalCharacters, parameterName, ch); } } } } /// /// Ensures the error_uri parameter contains only allowed characters and is an absolute URI. /// /// The absolute URI. private static void VerifyErrorUri(Uri valueUri) { if (valueUri != null) { ErrorUtilities.VerifyProtocol(valueUri.IsAbsoluteUri, OAuthStrings.AbsoluteUriRequired); string value = valueUri.AbsoluteUri; for (int i = 0; i < value.Length; i++) { // The allowed set of characters comes from http://self-issued.info/docs/draft-ietf-oauth-v2-bearer.html#authn-header char ch = value[i]; if (!(ch == '\x21' || (ch >= '\x23' && ch <= '\x5B') || (ch >= '\x5D' && ch <= '\x7E'))) { ErrorUtilities.ThrowProtocol(OAuthStrings.ParameterContainsIllegalCharacters, Protocol.BearerTokenUnauthorizedResponseParameters.ErrorUri, ch); } } } } } }