1
//-----------------------------------------------------------------------
2 // <copyright file="ServiceProvider.cs" company="Andrew Arnott">
3 // Copyright (c) Andrew Arnott. All rights reserved.
5 //-----------------------------------------------------------------------
7 namespace DotNetOAuth
{
9 using System
.Collections
.Generic
;
10 using System
.Globalization
;
11 using System
.ServiceModel
.Channels
;
13 using DotNetOAuth
.ChannelElements
;
14 using DotNetOAuth
.Messages
;
15 using DotNetOAuth
.Messaging
;
16 using DotNetOAuth
.Messaging
.Bindings
;
19 /// A web application that allows access via OAuth.
22 /// <para>The Service Provider’s documentation should include:</para>
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>
29 public class ServiceProvider
{
31 /// Initializes a new instance of the <see cref="ServiceProvider"/> class.
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
)) {
40 /// Initializes a new instance of the <see cref="ServiceProvider"/> class.
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
;
66 /// Gets the description of this Service Provider.
68 public ServiceProviderDescription ServiceDescription { get; private set; }
71 /// Gets or sets the generator responsible for generating new tokens and secrets.
73 public ITokenGenerator TokenGenerator { get; set; }
76 /// Gets the persistence store for tokens and secrets.
78 public ITokenManager TokenManager { get; private set; }
81 /// Gets or sets the channel to use for sending/receiving messages.
83 internal OAuthChannel Channel { get; set; }
86 /// Reads any incoming OAuth message.
88 /// <returns>The deserialized message.</returns>
89 public IProtocolMessage
ReadRequest() {
90 return this.Channel
.ReadFromRequest();
94 /// Reads any incoming OAuth message.
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
));
103 /// Gets the incoming request for an unauthorized token, if any.
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>
108 /// Requires HttpContext.Current.
110 public RequestTokenMessage
ReadTokenRequest() {
111 return this.ReadTokenRequest(this.Channel
.GetRequestFromContext());
115 /// Gets the incoming request for an unauthorized token, if any.
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
));
125 /// Sends an unauthorized token back to the Consumer for use in a user agent redirect
126 /// for subsequent authorization.
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
);
145 /// Gets the incoming request for the Service Provider to authorize a Consumer's
146 /// access to some protected resources.
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>
151 /// Requires HttpContext.Current.
153 public DirectUserToServiceProviderMessage
ReadAuthorizationRequest() {
154 return this.ReadAuthorizationRequest(this.Channel
.GetRequestFromContext());
158 /// Gets the incoming request for the Service Provider to authorize a Consumer's
159 /// access to some protected resources.
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
));
169 /// Completes user authorization of a token by redirecting the user agent back to the Consumer.
171 /// <param name="request">The Consumer's original authorization request.</param>
173 /// The pending user agent redirect based message to be sent as an HttpResponse,
174 /// or null if the Consumer requested no callback.
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
);
192 /// Gets the incoming request to exchange an authorized token for an access token.
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>
197 /// Requires HttpContext.Current.
199 public RequestAccessTokenMessage
ReadAccessTokenRequest() {
200 return this.ReadAccessTokenRequest(this.Channel
.GetRequestFromContext());
204 /// Gets the incoming request to exchange an authorized token for an access token.
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
));
214 /// Prepares and sends an access token to a Consumer, and invalidates the request token.
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(
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
);
245 /// Gets the authorization (access token) for accessing some protected resource.
247 /// <returns>The authorization message sent by the Consumer, or null if no authorization message is attached.</returns>
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.
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());
259 /// Gets the authorization (access token) for accessing some protected resource.
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>
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.
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
));
274 /// Gets the authorization (access token) for accessing some protected resource.
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>
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.
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
));
290 /// Reads a request for an unauthorized token from the incoming HTTP request.
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
);
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).
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
);
315 /// Reads in a Consumer's request to exchange an authorized request token for an access token.
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
);
327 /// Gets the authorization (access token) for accessing some protected resource.
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>
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.
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(
347 CultureInfo
.CurrentCulture
,
348 Strings
.BadAccessTokenInProtectedResourceRequest
,
349 accessMessage
.AccessToken
));
353 return accessMessage
;
357 /// Fills out the secrets in an incoming message so that signature verification can be performed.
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
);