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
|
//-----------------------------------------------------------------------
// <copyright file="OAuth2ResourceServerChannel.cs" company="Outercurve Foundation">
// Copyright (c) Outercurve Foundation. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
namespace DotNetOpenAuth.OAuth2.ChannelElements {
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Mime;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.Messaging.Reflection;
using DotNetOpenAuth.OAuth2.Messages;
using Validation;
using HttpRequestHeaders = DotNetOpenAuth.Messaging.HttpRequestHeaders;
/// <summary>
/// The channel for the OAuth protocol.
/// </summary>
internal class OAuth2ResourceServerChannel : StandardMessageFactoryChannel {
/// <summary>
/// The messages receivable by this channel.
/// </summary>
private static readonly Type[] MessageTypes = new Type[] {
typeof(Messages.AccessProtectedResourceRequest),
};
/// <summary>
/// The protocol versions supported by this channel.
/// </summary>
private static readonly Version[] Versions = Protocol.AllVersions.Select(v => v.Version).ToArray();
/// <summary>
/// Initializes a new instance of the <see cref="OAuth2ResourceServerChannel" /> class.
/// </summary>
/// <param name="hostFactories">The host factories.</param>
protected internal OAuth2ResourceServerChannel(IHostFactories hostFactories = null)
: base(MessageTypes, Versions, hostFactories ?? new OAuth.DefaultOAuthHostFactories()) {
// TODO: add signing (authenticated request) binding element.
}
/// <summary>
/// Gets the protocol message that may be embedded in the given HTTP request.
/// </summary>
/// <param name="request">The request to search for an embedded message.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>
/// The deserialized message, if one is found. Null otherwise.
/// </returns>
protected override IDirectedProtocolMessage ReadFromRequestCore(HttpRequestBase request, CancellationToken cancellationToken) {
var fields = new Dictionary<string, string>();
string accessToken;
if ((accessToken = SearchForBearerAccessTokenInRequest(request)) != null) {
fields[Protocol.token_type] = Protocol.AccessTokenTypes.Bearer;
fields[Protocol.access_token] = accessToken;
}
if (fields.Count > 0) {
MessageReceivingEndpoint recipient;
try {
recipient = request.GetRecipient();
} catch (ArgumentException ex) {
Logger.OAuth.WarnFormat("Unrecognized HTTP request: " + ex.ToString());
return null;
}
// Deserialize the message using all the data we've collected.
var message = (IDirectedProtocolMessage)this.Receive(fields, recipient);
return message;
}
return null;
}
/// <summary>
/// Gets the protocol message that may be in the given HTTP response.
/// </summary>
/// <param name="response">The response that is anticipated to contain an protocol message.</param>
/// <returns>
/// The deserialized message parts, if found. Null otherwise.
/// </returns>
/// <exception cref="ProtocolException">Thrown when the response is not valid.</exception>
protected override Task<IDictionary<string, string>> ReadFromResponseCoreAsync(HttpResponseMessage response) {
// We never expect resource servers to send out direct requests,
// and therefore won't have direct responses.
throw new NotImplementedException();
}
/// <summary>
/// Queues a message for sending in the response stream where the fields
/// are sent in the response stream in querystring style.
/// </summary>
/// <param name="response">The message to send as a response.</param>
/// <returns>
/// The pending user agent redirect based message to be sent as an HttpResponse.
/// </returns>
/// <remarks>
/// This method implements spec OAuth V1.0 section 5.3.
/// </remarks>
protected override HttpResponseMessage PrepareDirectResponse(IProtocolMessage response) {
var webResponse = new HttpResponseMessage();
// The only direct response from a resource server is some authorization error (400, 401, 403).
var unauthorizedResponse = response as UnauthorizedResponse;
ErrorUtilities.VerifyInternal(unauthorizedResponse != null, "Only unauthorized responses are expected.");
// First initialize based on the specifics within the message.
ApplyMessageTemplate(response, webResponse);
if (!(response is IHttpDirectResponse)) {
webResponse.StatusCode = HttpStatusCode.Unauthorized;
}
// Now serialize all the message parts into the WWW-Authenticate header.
var fields = this.MessageDescriptions.GetAccessor(response);
webResponse.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue(unauthorizedResponse.Scheme, MessagingUtilities.AssembleAuthorizationHeader(fields)));
return webResponse;
}
/// <summary>
/// Searches for a bearer access token in the request.
/// </summary>
/// <param name="request">The request.</param>
/// <returns>The bearer access token, if one exists. Otherwise <c>null</c>.</returns>
private static string SearchForBearerAccessTokenInRequest(HttpRequestBase request) {
Requires.NotNull(request, "request");
// First search the authorization header.
string authorizationHeader = request.Headers[HttpRequestHeaders.Authorization];
if (!string.IsNullOrEmpty(authorizationHeader) && authorizationHeader.StartsWith(Protocol.BearerHttpAuthorizationSchemeWithTrailingSpace, StringComparison.OrdinalIgnoreCase)) {
return authorizationHeader.Substring(Protocol.BearerHttpAuthorizationSchemeWithTrailingSpace.Length);
}
// Failing that, scan the entity
if (!string.IsNullOrEmpty(request.Headers[HttpRequestHeaders.ContentType])) {
var contentType = new ContentType(request.Headers[HttpRequestHeaders.ContentType]);
if (string.Equals(contentType.MediaType, HttpFormUrlEncoded, StringComparison.Ordinal)) {
if (request.Form[Protocol.BearerTokenEncodedUrlParameterName] != null) {
return request.Form[Protocol.BearerTokenEncodedUrlParameterName];
}
}
}
// Finally, check the least desirable location: the query string
var unrewrittenQuery = request.GetQueryStringBeforeRewriting();
if (!string.IsNullOrEmpty(unrewrittenQuery[Protocol.BearerTokenEncodedUrlParameterName])) {
return unrewrittenQuery[Protocol.BearerTokenEncodedUrlParameterName];
}
return null;
}
}
}
|