1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
|
//-----------------------------------------------------------------------
// <copyright file="UnauthorizedResponse.cs" company="Outercurve Foundation">
// Copyright (c) Outercurve Foundation. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
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;
/// <summary>
/// A direct response sent in response to a rejected Bearer access token.
/// </summary>
/// <remarks>
/// This satisfies the spec in: http://self-issued.info/docs/draft-ietf-oauth-v2-bearer.html#authn-header
/// </remarks>
public class UnauthorizedResponse : MessageBase, IHttpDirectResponse {
/// <summary>
/// The headers in the response message.
/// </summary>
private readonly WebHeaderCollection headers = new WebHeaderCollection();
/// <summary>
/// Initializes a new instance of the <see cref="UnauthorizedResponse"/> class.
/// </summary>
/// <param name="version">The protocol version.</param>
protected UnauthorizedResponse(Version version = null)
: base(version ?? Protocol.Default.Version) {
}
/// <summary>
/// Initializes a new instance of the <see cref="UnauthorizedResponse"/> class.
/// </summary>
/// <param name="request">The request.</param>
protected UnauthorizedResponse(IDirectedProtocolMessage request)
: base(request) {
}
#region IHttpDirectResponse Members
/// <summary>
/// Gets or sets the HTTP status code that the direct response should be sent with.
/// </summary>
public HttpStatusCode HttpStatusCode { get; set; }
/// <summary>
/// Gets the HTTP headers to add to the response.
/// </summary>
/// <value>May be an empty collection, but must not be <c>null</c>.</value>
public WebHeaderCollection Headers {
get { return this.headers; }
}
#endregion
/// <summary>
/// Gets or sets the well known error code.
/// </summary>
/// <value>One of the values from <see cref="Protocol.BearerTokenErrorCodes"/>.</value>
[MessagePart(Protocol.BearerTokenUnauthorizedResponseParameters.ErrorCode)]
public string ErrorCode { get; set; }
/// <summary>
/// Gets or sets a human-readable explanation for developers that is not meant to be displayed to end users.
/// </summary>
[MessagePart(Protocol.BearerTokenUnauthorizedResponseParameters.ErrorDescription)]
public string ErrorDescription { get; set; }
/// <summary>
/// Gets or sets an absolute URI identifying a human-readable web page explaining the error.
/// </summary>
[MessagePart(Protocol.BearerTokenUnauthorizedResponseParameters.ErrorUri)]
public Uri ErrorUri { get; set; }
/// <summary>
/// Gets or sets the realm.
/// </summary>
/// <value>The realm.</value>
[MessagePart(Protocol.BearerTokenUnauthorizedResponseParameters.Realm)]
public string Realm { get; set; }
/// <summary>
/// Gets or sets the scope.
/// </summary>
/// <value>The scope.</value>
[MessagePart(Protocol.BearerTokenUnauthorizedResponseParameters.Scope, Encoder = typeof(ScopeEncoder))]
public HashSet<string> Scope { get; set; }
/// <summary>
/// Gets the scheme to use in the WWW-Authenticate header.
/// </summary>
internal virtual string Scheme {
get { return Protocol.BearerHttpAuthorizationScheme; }
}
/// <summary>
/// Initializes a new instance of the <see cref="UnauthorizedResponse"/> class
/// to inform the client that the request was invalid.
/// </summary>
/// <param name="exception">The exception.</param>
/// <param name="version">The version of OAuth 2 that is in use.</param>
/// <returns>The error message.</returns>
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;
}
/// <summary>
/// Initializes a new instance of the <see cref="UnauthorizedResponse"/> class
/// to inform the client that the bearer token included in the request was rejected.
/// </summary>
/// <param name="request">The request.</param>
/// <param name="exception">The exception.</param>
/// <returns>The error message.</returns>
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;
}
/// <summary>
/// Initializes a new instance of the <see cref="UnauthorizedResponse"/> class
/// to inform the client of the required set of scopes required to perform this operation.
/// </summary>
/// <param name="request">The request.</param>
/// <param name="requiredScopes">The set of scopes required to perform this operation.</param>
/// <returns>The error message.</returns>
internal static UnauthorizedResponse InsufficientScope(IDirectedProtocolMessage request, HashSet<string> 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;
}
/// <summary>
/// Ensures the message is valid.
/// </summary>
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);
}
/// <summary>
/// Ensures the error or error_description parameters contain only allowed characters.
/// </summary>
/// <param name="value">The argument.</param>
/// <param name="parameterName">The name of the parameter being validated. Used when errors are reported.</param>
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);
}
}
}
}
/// <summary>
/// Ensures the error_uri parameter contains only allowed characters and is an absolute URI.
/// </summary>
/// <param name="valueUri">The absolute URI.</param>
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);
}
}
}
}
}
}
|