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
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
|
//-----------------------------------------------------------------------
// <copyright file="AuthorizationServer.cs" company="Outercurve Foundation">
// Copyright (c) Outercurve Foundation. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
namespace DotNetOpenAuth.OAuth2 {
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using DotNetOpenAuth.Configuration;
using DotNetOpenAuth.Logging;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.OAuth2.ChannelElements;
using DotNetOpenAuth.OAuth2.Messages;
using Validation;
/// <summary>
/// Authorization Server supporting the web server flow.
/// </summary>
public class AuthorizationServer {
/// <summary>
/// A reusable instance of the scope satisfied checker.
/// </summary>
private static readonly IScopeSatisfiedCheck DefaultScopeSatisfiedCheck = new StandardScopeSatisfiedCheck();
/// <summary>
/// The list of modules that verify client authentication data.
/// </summary>
private readonly List<ClientAuthenticationModule> clientAuthenticationModules = new List<ClientAuthenticationModule>();
/// <summary>
/// The lone aggregate client authentication module that uses the <see cref="clientAuthenticationModules"/> and applies aggregating policy.
/// </summary>
private readonly ClientAuthenticationModule aggregatingClientAuthenticationModule;
/// <summary>
/// Initializes a new instance of the <see cref="AuthorizationServer"/> class.
/// </summary>
/// <param name="authorizationServer">The authorization server.</param>
public AuthorizationServer(IAuthorizationServerHost authorizationServer) {
Requires.NotNull(authorizationServer, "authorizationServer");
this.aggregatingClientAuthenticationModule = new AggregatingClientCredentialReader(this.clientAuthenticationModules);
this.Channel = new OAuth2AuthorizationServerChannel(authorizationServer, this.aggregatingClientAuthenticationModule);
this.clientAuthenticationModules.AddRange(OAuth2AuthorizationServerSection.Configuration.ClientAuthenticationModules.CreateInstances(true, null));
this.ScopeSatisfiedCheck = DefaultScopeSatisfiedCheck;
}
/// <summary>
/// Gets the channel.
/// </summary>
/// <value>The channel.</value>
public Channel Channel { get; internal set; }
/// <summary>
/// Gets the authorization server.
/// </summary>
/// <value>The authorization server.</value>
public IAuthorizationServerHost AuthorizationServerServices {
get { return ((IOAuth2ChannelWithAuthorizationServer)this.Channel).AuthorizationServer; }
}
/// <summary>
/// Gets the extension modules that can read client authentication data from incoming messages.
/// </summary>
public IList<ClientAuthenticationModule> ClientAuthenticationModules {
get { return this.clientAuthenticationModules; }
}
/// <summary>
/// Gets or sets the service that checks whether a granted set of scopes satisfies a required set of scopes.
/// </summary>
public IScopeSatisfiedCheck ScopeSatisfiedCheck {
get { return ((IOAuth2ChannelWithAuthorizationServer)this.Channel).ScopeSatisfiedCheck; }
set { ((IOAuth2ChannelWithAuthorizationServer)this.Channel).ScopeSatisfiedCheck = value; }
}
/// <summary>
/// Reads in a client's request for the Authorization Server to obtain permission from
/// the user to authorize the Client's access of some protected resource(s).
/// </summary>
/// <param name="request">The HTTP request to read from.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>
/// The incoming request, or null if no OAuth message was attached.
/// </returns>
/// <exception cref="ProtocolException">Thrown if an unexpected OAuth message is attached to the incoming request.</exception>
[SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "unauthorizedclient", Justification = "Protocol required.")]
public async Task<EndUserAuthorizationRequest> ReadAuthorizationRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken = default(CancellationToken)) {
Requires.NotNull(request, "request");
var message = await this.Channel.TryReadFromRequestAsync<EndUserAuthorizationRequest>(request, cancellationToken);
if (message != null) {
if (message.ResponseType == EndUserAuthorizationResponseType.AuthorizationCode) {
// Clients with no secrets can only request implicit grant types.
var client = this.AuthorizationServerServices.GetClientOrThrow(message.ClientIdentifier);
ErrorUtilities.VerifyProtocol(client.HasNonEmptySecret, Protocol.EndUserAuthorizationRequestErrorCodes.UnauthorizedClient);
}
}
return message;
}
/// <summary>
/// Reads in a client's request for the Authorization Server to obtain permission from
/// the user to authorize the Client's access of some protected resource(s).
/// </summary>
/// <param name="request">The HTTP request to read from.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>
/// The incoming request, or null if no OAuth message was attached.
/// </returns>
/// <exception cref="ProtocolException">Thrown if an unexpected OAuth message is attached to the incoming request.</exception>
public Task<EndUserAuthorizationRequest> ReadAuthorizationRequestAsync(
HttpRequestBase request = null, CancellationToken cancellationToken = default(CancellationToken)) {
request = request ?? this.Channel.GetRequestFromContext();
return this.ReadAuthorizationRequestAsync(request.AsHttpRequestMessage(), cancellationToken);
}
/// <summary>
/// Reads in a client's request for the Authorization Server to obtain permission from
/// the user to authorize the Client's access of some protected resource(s).
/// </summary>
/// <param name="requestUri">The URL that carries the authorization request.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>
/// The incoming request, or null if no OAuth message was attached.
/// </returns>
/// <exception cref="ProtocolException">Thrown if an unexpected OAuth message is attached to the incoming request.</exception>
public Task<EndUserAuthorizationRequest> ReadAuthorizationRequestAsync(Uri requestUri, CancellationToken cancellationToken = default(CancellationToken)) {
var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
return this.ReadAuthorizationRequestAsync(request, cancellationToken);
}
/// <summary>
/// Handles an incoming request to the authorization server's token endpoint.
/// </summary>
/// <param name="request">The HTTP request.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>
/// The HTTP response to send to the client.
/// </returns>
public async Task<HttpResponseMessage> HandleTokenRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken = default(CancellationToken)) {
Requires.NotNull(request, "request");
IProtocolMessage responseMessage;
try {
AccessTokenRequestBase requestMessage = await this.Channel.TryReadFromRequestAsync<AccessTokenRequestBase>(request, cancellationToken);
if (requestMessage != null) {
var accessTokenResult = this.AuthorizationServerServices.CreateAccessToken(requestMessage);
ErrorUtilities.VerifyHost(accessTokenResult != null, "IAuthorizationServerHost.CreateAccessToken must not return null.");
IAccessTokenRequestInternal accessRequestInternal = requestMessage;
accessRequestInternal.AccessTokenResult = accessTokenResult;
var successResponseMessage = this.PrepareAccessTokenResponse(requestMessage, accessTokenResult.AllowRefreshToken);
successResponseMessage.Lifetime = accessTokenResult.AccessToken.Lifetime;
var authCarryingRequest = requestMessage as IAuthorizationCarryingRequest;
if (authCarryingRequest != null) {
accessTokenResult.AccessToken.ApplyAuthorization(authCarryingRequest.AuthorizationDescription);
IAccessTokenIssuingResponse accessTokenIssuingResponse = successResponseMessage;
accessTokenIssuingResponse.AuthorizationDescription = accessTokenResult.AccessToken;
}
responseMessage = successResponseMessage;
} else {
responseMessage = new AccessTokenFailedResponse() { Error = Protocol.AccessTokenRequestErrorCodes.InvalidRequest };
}
} catch (TokenEndpointProtocolException ex) {
responseMessage = ex.GetResponse();
} catch (ProtocolException) {
responseMessage = new AccessTokenFailedResponse() { Error = Protocol.AccessTokenRequestErrorCodes.InvalidRequest };
}
return await this.Channel.PrepareResponseAsync(responseMessage, cancellationToken);
}
/// <summary>
/// Handles an incoming request to the authorization server's token endpoint.
/// </summary>
/// <param name="request">The HTTP request.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>
/// The HTTP response to send to the client.
/// </returns>
public Task<HttpResponseMessage> HandleTokenRequestAsync(HttpRequestBase request = null, CancellationToken cancellationToken = default(CancellationToken)) {
request = request ?? this.Channel.GetRequestFromContext();
return this.HandleTokenRequestAsync(request.AsHttpRequestMessage(), cancellationToken);
}
/// <summary>
/// Prepares a response to inform the Client that the user has rejected the Client's authorization request.
/// </summary>
/// <param name="authorizationRequest">The authorization request.</param>
/// <param name="callback">The Client callback URL to use when formulating the redirect to send the user agent back to the Client.</param>
/// <returns>The authorization response message to send to the Client.</returns>
public EndUserAuthorizationFailedResponse PrepareRejectAuthorizationRequest(EndUserAuthorizationRequest authorizationRequest, Uri callback = null) {
Requires.NotNull(authorizationRequest, "authorizationRequest");
if (callback == null) {
callback = this.GetCallback(authorizationRequest);
}
var response = new EndUserAuthorizationFailedResponse(callback, authorizationRequest);
return response;
}
/// <summary>
/// Approves an authorization request.
/// </summary>
/// <param name="authorizationRequest">The authorization request to approve.</param>
/// <param name="userName">The username of the account that approved the request (or whose data will be accessed by the client).</param>
/// <param name="scopes">The scope of access the client should be granted. If <c>null</c>, all scopes in the original request will be granted.</param>
/// <param name="callback">The Client callback URL to use when formulating the redirect to send the user agent back to the Client.</param>
/// <returns>The authorization response message to send to the Client.</returns>
public EndUserAuthorizationSuccessResponseBase PrepareApproveAuthorizationRequest(EndUserAuthorizationRequest authorizationRequest, string userName, IEnumerable<string> scopes = null, Uri callback = null) {
Requires.NotNull(authorizationRequest, "authorizationRequest");
Requires.NotNullOrEmpty(userName, "userName");
if (callback == null) {
callback = this.GetCallback(authorizationRequest);
}
var client = this.AuthorizationServerServices.GetClientOrThrow(authorizationRequest.ClientIdentifier);
EndUserAuthorizationSuccessResponseBase response;
switch (authorizationRequest.ResponseType) {
case EndUserAuthorizationResponseType.AccessToken:
IAccessTokenRequestInternal accessRequestInternal = (EndUserAuthorizationImplicitRequest)authorizationRequest;
var accessTokenResult = this.AuthorizationServerServices.CreateAccessToken(accessRequestInternal);
ErrorUtilities.VerifyHost(accessTokenResult != null, "IAuthorizationServerHost.CreateAccessToken must not return null.");
accessRequestInternal.AccessTokenResult = accessTokenResult;
var implicitGrantResponse = new EndUserAuthorizationSuccessAccessTokenResponse(callback, authorizationRequest);
implicitGrantResponse.Lifetime = accessTokenResult.AccessToken.Lifetime;
accessTokenResult.AccessToken.ApplyAuthorization(implicitGrantResponse.Scope, userName, implicitGrantResponse.Lifetime);
IAccessTokenCarryingRequest tokenCarryingResponse = implicitGrantResponse;
tokenCarryingResponse.AuthorizationDescription = accessTokenResult.AccessToken;
response = implicitGrantResponse;
break;
case EndUserAuthorizationResponseType.AuthorizationCode:
var authCodeResponse = new EndUserAuthorizationSuccessAuthCodeResponseAS(callback, authorizationRequest);
IAuthorizationCodeCarryingRequest codeCarryingResponse = authCodeResponse;
codeCarryingResponse.AuthorizationDescription = new AuthorizationCode(
authorizationRequest.ClientIdentifier,
authorizationRequest.Callback,
authCodeResponse.Scope,
userName);
response = authCodeResponse;
break;
default:
throw ErrorUtilities.ThrowInternal("Unexpected response type.");
}
response.AuthorizingUsername = userName;
// Customize the approved scope if the authorization server has decided to do so.
if (scopes != null) {
response.Scope.ResetContents(scopes);
}
return response;
}
/// <summary>
/// Decodes a refresh token into its authorization details.
/// </summary>
/// <param name="refreshToken">The encoded refresh token as it would appear to the client.</param>
/// <returns>A description of the authorization represented by the refresh token.</returns>
/// <exception cref="ProtocolException">Thrown if the refresh token is not valid due to expiration, corruption or not being authentic.</exception>
/// <remarks>
/// This can be useful if the authorization server supports the client revoking its own access (on uninstall, for example).
/// Outside the scope of the OAuth 2 spec, the client may contact the authorization server host requesting that its refresh
/// token be revoked. The authorization server would need to decode the refresh token so it knows which authorization in
/// the database to delete.
/// </remarks>
public IAuthorizationDescription DecodeRefreshToken(string refreshToken) {
var refreshTokenFormatter = RefreshToken.CreateFormatter(this.AuthorizationServerServices.CryptoKeyStore);
var token = new RefreshToken();
refreshTokenFormatter.Deserialize(token, refreshToken);
return token;
}
/// <summary>
/// Gets the redirect URL to use for a particular authorization request.
/// </summary>
/// <param name="authorizationRequest">The authorization request.</param>
/// <returns>The URL to redirect to. Never <c>null</c>.</returns>
/// <exception cref="ProtocolException">Thrown if no callback URL could be determined.</exception>
protected Uri GetCallback(EndUserAuthorizationRequest authorizationRequest) {
Requires.NotNull(authorizationRequest, "authorizationRequest");
var client = this.AuthorizationServerServices.GetClientOrThrow(authorizationRequest.ClientIdentifier);
// Prefer a request-specific callback to the pre-registered one (if any).
if (authorizationRequest.Callback != null) {
// The OAuth channel has already validated the callback parameter against
// the authorization server's whitelist for this client.
return authorizationRequest.Callback;
}
// Since the request didn't include a callback URL, look up the callback from
// the client's preregistration with this authorization server.
Uri defaultCallback = client.DefaultCallback;
ErrorUtilities.VerifyProtocol(defaultCallback != null, AuthServerStrings.NoCallback);
return defaultCallback;
}
/// <summary>
/// Prepares the response to an access token request.
/// </summary>
/// <param name="request">The request for an access token.</param>
/// <param name="allowRefreshToken">If set to <c>true</c>, the response will include a long-lived refresh token.</param>
/// <returns>The response message to send to the client.</returns>
private AccessTokenSuccessResponse PrepareAccessTokenResponse(AccessTokenRequestBase request, bool allowRefreshToken = true) {
Requires.NotNull(request, "request");
if (allowRefreshToken) {
if (request is AccessTokenClientCredentialsRequest) {
// Per OAuth 2.0 section 4.4.3 (draft 23), refresh tokens should never be included
// in a response to an access token request that used the client credential grant type.
Logger.OAuth.Debug("Suppressing refresh token in access token response because the grant type used by the client disallows it.");
allowRefreshToken = false;
}
}
var tokenRequest = (IAuthorizationCarryingRequest)request;
var accessTokenRequest = (IAccessTokenRequestInternal)request;
var response = new AccessTokenSuccessResponse(request) {
HasRefreshToken = allowRefreshToken,
};
response.Scope.ResetContents(tokenRequest.AuthorizationDescription.Scope);
return response;
}
}
}
|