Added strong-typed request token message to sample.
[dotnetoauth.git] / src / DotNetOAuth / ServiceProvider.cs
blob135ecd03acc4dd47bdb3e035d942600890b6911a
1 //-----------------------------------------------------------------------
2 // <copyright file="ServiceProvider.cs" company="Andrew Arnott">
3 // Copyright (c) Andrew Arnott. All rights reserved.
4 // </copyright>
5 //-----------------------------------------------------------------------
7 namespace DotNetOAuth {
8 using System;
9 using System.Collections.Generic;
10 using System.Globalization;
11 using System.ServiceModel.Channels;
12 using System.Web;
13 using DotNetOAuth.ChannelElements;
14 using DotNetOAuth.Messages;
15 using DotNetOAuth.Messaging;
16 using DotNetOAuth.Messaging.Bindings;
18 /// <summary>
19 /// A web application that allows access via OAuth.
20 /// </summary>
21 /// <remarks>
22 /// <para>The Service Provider’s documentation should include:</para>
23 /// <list>
24 /// <item>The URLs (Request URLs) the Consumer will use when making OAuth requests, and the HTTP methods (i.e. GET, POST, etc.) used in the Request Token URL and Access Token URL.</item>
25 /// <item>Signature methods supported by the Service Provider.</item>
26 /// <item>Any additional request parameters that the Service Provider requires in order to obtain a Token. Service Provider specific parameters MUST NOT begin with oauth_.</item>
27 /// </list>
28 /// </remarks>
29 public class ServiceProvider {
30 /// <summary>
31 /// Initializes a new instance of the <see cref="ServiceProvider"/> class.
32 /// </summary>
33 /// <param name="serviceDescription">The endpoints and behavior on the Service Provider.</param>
34 /// <param name="tokenManager">The host's method of storing and recalling tokens and secrets.</param>
35 public ServiceProvider(ServiceProviderDescription serviceDescription, ITokenManager tokenManager)
36 : this(serviceDescription, tokenManager, new OAuthServiceProviderMessageTypeProvider(tokenManager)) {
39 /// <summary>
40 /// Initializes a new instance of the <see cref="ServiceProvider"/> class.
41 /// </summary>
42 /// <param name="serviceDescription">The endpoints and behavior on the Service Provider.</param>
43 /// <param name="tokenManager">The host's method of storing and recalling tokens and secrets.</param>
44 /// <param name="messageTypeProvider">An object that can figure out what type of message is being received for deserialization.</param>
45 public ServiceProvider(ServiceProviderDescription serviceDescription, ITokenManager tokenManager, OAuthServiceProviderMessageTypeProvider messageTypeProvider) {
46 if (serviceDescription == null) {
47 throw new ArgumentNullException("serviceDescription");
49 if (tokenManager == null) {
50 throw new ArgumentNullException("tokenManager");
52 if (messageTypeProvider == null) {
53 throw new ArgumentNullException("messageTypeProvider");
56 var signingElement = serviceDescription.CreateTamperProtectionElement();
57 signingElement.SignatureVerificationCallback = this.TokenSignatureVerificationCallback;
58 INonceStore store = new NonceMemoryStore(StandardExpirationBindingElement.DefaultMaximumMessageAge);
59 this.ServiceDescription = serviceDescription;
60 this.Channel = new OAuthChannel(signingElement, store, messageTypeProvider, new StandardWebRequestHandler());
61 this.TokenGenerator = new StandardTokenGenerator();
62 this.TokenManager = tokenManager;
65 /// <summary>
66 /// Gets the description of this Service Provider.
67 /// </summary>
68 public ServiceProviderDescription ServiceDescription { get; private set; }
70 /// <summary>
71 /// Gets or sets the generator responsible for generating new tokens and secrets.
72 /// </summary>
73 public ITokenGenerator TokenGenerator { get; set; }
75 /// <summary>
76 /// Gets the persistence store for tokens and secrets.
77 /// </summary>
78 public ITokenManager TokenManager { get; private set; }
80 /// <summary>
81 /// Gets or sets the channel to use for sending/receiving messages.
82 /// </summary>
83 internal OAuthChannel Channel { get; set; }
85 /// <summary>
86 /// Reads any incoming OAuth message.
87 /// </summary>
88 /// <returns>The deserialized message.</returns>
89 public IProtocolMessage ReadRequest() {
90 return this.Channel.ReadFromRequest();
93 /// <summary>
94 /// Reads any incoming OAuth message.
95 /// </summary>
96 /// <param name="request">The HTTP request to read the message from.</param>
97 /// <returns>The deserialized message.</returns>
98 public IProtocolMessage ReadRequest(HttpRequest request) {
99 return this.Channel.ReadFromRequest(new HttpRequestInfo(request));
102 /// <summary>
103 /// Gets the incoming request for an unauthorized token, if any.
104 /// </summary>
105 /// <returns>The incoming request, or null if no OAuth message was attached.</returns>
106 /// <exception cref="ProtocolException">Thrown if an unexpected OAuth message is attached to the incoming request.</exception>
107 /// <remarks>
108 /// Requires HttpContext.Current.
109 /// </remarks>
110 public RequestTokenMessage ReadTokenRequest() {
111 return this.ReadTokenRequest(this.Channel.GetRequestFromContext());
114 /// <summary>
115 /// Gets the incoming request for an unauthorized token, if any.
116 /// </summary>
117 /// <param name="request">The incoming HTTP request.</param>
118 /// <returns>The incoming request, or null if no OAuth message was attached.</returns>
119 /// <exception cref="ProtocolException">Thrown if an unexpected OAuth message is attached to the incoming request.</exception>
120 public RequestTokenMessage ReadTokenRequest(HttpRequest request) {
121 return this.ReadTokenRequest(new HttpRequestInfo(request));
124 /// <summary>
125 /// Sends an unauthorized token back to the Consumer for use in a user agent redirect
126 /// for subsequent authorization.
127 /// </summary>
128 /// <param name="request">The token request message the Consumer sent that the Service Provider is now responding to.</param>
129 /// <param name="extraParameters">Any extra parameters the Consumer should receive with the OAuth message. May be null.</param>
130 /// <returns>The actual response the Service Provider will need to forward as the HTTP response.</returns>
131 public Response SendUnauthorizedTokenResponse(RequestTokenMessage request, IDictionary<string, string> extraParameters) {
132 string token = this.TokenGenerator.GenerateRequestToken(request.ConsumerKey);
133 string secret = this.TokenGenerator.GenerateSecret();
134 UnauthorizedRequestTokenMessage response = new UnauthorizedRequestTokenMessage {
135 RequestToken = token,
136 TokenSecret = secret,
138 response.AddNonOAuthParameters(extraParameters);
139 this.TokenManager.StoreNewRequestToken(request, response);
141 return this.Channel.Send(response);
144 /// <summary>
145 /// Gets the incoming request for the Service Provider to authorize a Consumer's
146 /// access to some protected resources.
147 /// </summary>
148 /// <returns>The incoming request, or null if no OAuth message was attached.</returns>
149 /// <exception cref="ProtocolException">Thrown if an unexpected OAuth message is attached to the incoming request.</exception>
150 /// <remarks>
151 /// Requires HttpContext.Current.
152 /// </remarks>
153 public DirectUserToServiceProviderMessage ReadAuthorizationRequest() {
154 return this.ReadAuthorizationRequest(this.Channel.GetRequestFromContext());
157 /// <summary>
158 /// Gets the incoming request for the Service Provider to authorize a Consumer's
159 /// access to some protected resources.
160 /// </summary>
161 /// <param name="request">The incoming HTTP request.</param>
162 /// <returns>The incoming request, or null if no OAuth message was attached.</returns>
163 /// <exception cref="ProtocolException">Thrown if an unexpected OAuth message is attached to the incoming request.</exception>
164 public DirectUserToServiceProviderMessage ReadAuthorizationRequest(HttpRequest request) {
165 return this.ReadAuthorizationRequest(new HttpRequestInfo(request));
168 /// <summary>
169 /// Completes user authorization of a token by redirecting the user agent back to the Consumer.
170 /// </summary>
171 /// <param name="request">The Consumer's original authorization request.</param>
172 /// <returns>
173 /// The pending user agent redirect based message to be sent as an HttpResponse,
174 /// or null if the Consumer requested no callback.
175 /// </returns>
176 public Response SendAuthorizationResponse(DirectUserToServiceProviderMessage request) {
177 if (request == null) {
178 throw new ArgumentNullException("request");
181 if (request.Callback != null) {
182 var authorization = new DirectUserToConsumerMessage(request.Callback) {
183 RequestToken = request.RequestToken,
185 return this.Channel.Send(authorization);
186 } else {
187 return null;
191 /// <summary>
192 /// Gets the incoming request to exchange an authorized token for an access token.
193 /// </summary>
194 /// <returns>The incoming request, or null if no OAuth message was attached.</returns>
195 /// <exception cref="ProtocolException">Thrown if an unexpected OAuth message is attached to the incoming request.</exception>
196 /// <remarks>
197 /// Requires HttpContext.Current.
198 /// </remarks>
199 public RequestAccessTokenMessage ReadAccessTokenRequest() {
200 return this.ReadAccessTokenRequest(this.Channel.GetRequestFromContext());
203 /// <summary>
204 /// Gets the incoming request to exchange an authorized token for an access token.
205 /// </summary>
206 /// <param name="request">The incoming HTTP request.</param>
207 /// <returns>The incoming request, or null if no OAuth message was attached.</returns>
208 /// <exception cref="ProtocolException">Thrown if an unexpected OAuth message is attached to the incoming request.</exception>
209 public RequestAccessTokenMessage ReadAccessTokenRequest(HttpRequest request) {
210 return this.ReadAccessTokenRequest(new HttpRequestInfo(request));
213 /// <summary>
214 /// Prepares and sends an access token to a Consumer, and invalidates the request token.
215 /// </summary>
216 /// <param name="request">The Consumer's message requesting an access token.</param>
217 /// <param name="extraParameters">Any extra parameters the Consumer should receive with the OAuth message. May be null.</param>
218 /// <returns>The HTTP response to actually send to the Consumer.</returns>
219 public Response SendAccessToken(RequestAccessTokenMessage request, IDictionary<string, string> extraParameters) {
220 if (request == null) {
221 throw new ArgumentNullException("request");
224 if (!this.TokenManager.IsRequestTokenAuthorized(request.RequestToken)) {
225 throw new ProtocolException(
226 string.Format(
227 CultureInfo.CurrentCulture,
228 Strings.AccessTokenNotAuthorized,
229 request.RequestToken));
232 string accessToken = this.TokenGenerator.GenerateAccessToken(request.ConsumerKey);
233 string tokenSecret = this.TokenGenerator.GenerateSecret();
234 this.TokenManager.ExpireRequestTokenAndStoreNewAccessToken(request.ConsumerKey, request.RequestToken, accessToken, tokenSecret);
235 var grantAccess = new GrantAccessTokenMessage {
236 AccessToken = accessToken,
237 TokenSecret = tokenSecret,
239 grantAccess.AddNonOAuthParameters(extraParameters);
241 return this.Channel.Send(grantAccess);
244 /// <summary>
245 /// Gets the authorization (access token) for accessing some protected resource.
246 /// </summary>
247 /// <returns>The authorization message sent by the Consumer, or null if no authorization message is attached.</returns>
248 /// <remarks>
249 /// This method verifies that the access token and token secret are valid.
250 /// It falls on the caller to verify that the access token is actually authorized
251 /// to access the resources being requested.
252 /// </remarks>
253 /// <exception cref="ProtocolException">Thrown if an unexpected message is attached to the request.</exception>
254 public AccessProtectedResourcesMessage GetProtectedResourceAuthorization() {
255 return this.GetProtectedResourceAuthorization(this.Channel.GetRequestFromContext());
258 /// <summary>
259 /// Gets the authorization (access token) for accessing some protected resource.
260 /// </summary>
261 /// <param name="request">The incoming HTTP request.</param>
262 /// <returns>The authorization message sent by the Consumer, or null if no authorization message is attached.</returns>
263 /// <remarks>
264 /// This method verifies that the access token and token secret are valid.
265 /// It falls on the caller to verify that the access token is actually authorized
266 /// to access the resources being requested.
267 /// </remarks>
268 /// <exception cref="ProtocolException">Thrown if an unexpected message is attached to the request.</exception>
269 public AccessProtectedResourcesMessage GetProtectedResourceAuthorization(HttpRequest request) {
270 return this.GetProtectedResourceAuthorization(new HttpRequestInfo(request));
273 /// <summary>
274 /// Gets the authorization (access token) for accessing some protected resource.
275 /// </summary>
276 /// <param name="request">HTTP details from an incoming WCF message.</param>
277 /// <param name="requestUri">The URI of the WCF service endpoint.</param>
278 /// <returns>The authorization message sent by the Consumer, or null if no authorization message is attached.</returns>
279 /// <remarks>
280 /// This method verifies that the access token and token secret are valid.
281 /// It falls on the caller to verify that the access token is actually authorized
282 /// to access the resources being requested.
283 /// </remarks>
284 /// <exception cref="ProtocolException">Thrown if an unexpected message is attached to the request.</exception>
285 public AccessProtectedResourcesMessage GetProtectedResourceAuthorization(HttpRequestMessageProperty request, Uri requestUri) {
286 return this.GetProtectedResourceAuthorization(new HttpRequestInfo(request, requestUri));
289 /// <summary>
290 /// Reads a request for an unauthorized token from the incoming HTTP request.
291 /// </summary>
292 /// <param name="request">The HTTP request to read from.</param>
293 /// <returns>The incoming request, or null if no OAuth message was attached.</returns>
294 /// <exception cref="ProtocolException">Thrown if an unexpected OAuth message is attached to the incoming request.</exception>
295 internal RequestTokenMessage ReadTokenRequest(HttpRequestInfo request) {
296 RequestTokenMessage message;
297 this.Channel.TryReadFromRequest(request, out message);
298 return message;
301 /// <summary>
302 /// Reads in a Consumer's request for the Service Provider to obtain permission from
303 /// the user to authorize the Consumer's access of some protected resource(s).
304 /// </summary>
305 /// <param name="request">The HTTP request to read from.</param>
306 /// <returns>The incoming request, or null if no OAuth message was attached.</returns>
307 /// <exception cref="ProtocolException">Thrown if an unexpected OAuth message is attached to the incoming request.</exception>
308 internal DirectUserToServiceProviderMessage ReadAuthorizationRequest(HttpRequestInfo request) {
309 DirectUserToServiceProviderMessage message;
310 this.Channel.TryReadFromRequest(request, out message);
311 return message;
314 /// <summary>
315 /// Reads in a Consumer's request to exchange an authorized request token for an access token.
316 /// </summary>
317 /// <param name="request">The HTTP request to read from.</param>
318 /// <returns>The incoming request, or null if no OAuth message was attached.</returns>
319 /// <exception cref="ProtocolException">Thrown if an unexpected OAuth message is attached to the incoming request.</exception>
320 internal RequestAccessTokenMessage ReadAccessTokenRequest(HttpRequestInfo request) {
321 RequestAccessTokenMessage message;
322 this.Channel.TryReadFromRequest(request, out message);
323 return message;
326 /// <summary>
327 /// Gets the authorization (access token) for accessing some protected resource.
328 /// </summary>
329 /// <param name="request">The incoming HTTP request.</param>
330 /// <returns>The authorization message sent by the Consumer, or null if no authorization message is attached.</returns>
331 /// <remarks>
332 /// This method verifies that the access token and token secret are valid.
333 /// It falls on the caller to verify that the access token is actually authorized
334 /// to access the resources being requested.
335 /// </remarks>
336 /// <exception cref="ProtocolException">Thrown if an unexpected message is attached to the request.</exception>
337 internal AccessProtectedResourcesMessage GetProtectedResourceAuthorization(HttpRequestInfo request) {
338 if (request == null) {
339 throw new ArgumentNullException("request");
342 AccessProtectedResourcesMessage accessMessage;
343 if (this.Channel.TryReadFromRequest<AccessProtectedResourcesMessage>(request, out accessMessage)) {
344 if (this.TokenManager.GetTokenType(accessMessage.AccessToken) != TokenType.AccessToken) {
345 throw new ProtocolException(
346 string.Format(
347 CultureInfo.CurrentCulture,
348 Strings.BadAccessTokenInProtectedResourceRequest,
349 accessMessage.AccessToken));
353 return accessMessage;
356 /// <summary>
357 /// Fills out the secrets in an incoming message so that signature verification can be performed.
358 /// </summary>
359 /// <param name="message">The incoming message.</param>
360 private void TokenSignatureVerificationCallback(ITamperResistantOAuthMessage message) {
361 message.ConsumerSecret = this.TokenManager.GetConsumerSecret(message.ConsumerKey);
363 var tokenMessage = message as ITokenContainingMessage;
364 if (tokenMessage != null) {
365 message.TokenSecret = this.TokenManager.GetTokenSecret(tokenMessage.Token);