summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenId/Provider/OpenIdProvider.cs
blob: 29318a770c5dbbc40c5460f105b22e32d2b1fbe0 (plain)
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
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration;
using System.Diagnostics;
using System.Web;
using IProviderAssociationStore = DotNetOpenId.IAssociationStore<DotNetOpenId.AssociationRelyingPartyType>;
using ProviderMemoryStore = DotNetOpenId.AssociationMemoryStore<DotNetOpenId.AssociationRelyingPartyType>;
using DotNetOpenId.Configuration;

namespace DotNetOpenId.Provider {
	/// <summary>
	/// Offers services for a web page that is acting as an OpenID identity server.
	/// </summary>
	[DebuggerDisplay("Endpoint: {Endpoint}, OpenId Request: {Query.ContainsKey(\"openid.mode\")}")]
	public class OpenIdProvider {
		internal Signatory Signatory { get; private set; }
		internal MessageEncoder Encoder;
		/// <summary>
		/// The incoming request's Url.
		/// </summary>
		/// <remarks>
		/// This is used for certain security checks internally.  It should not
		/// be used for its Query property, as it will be irrelevant on POST requests.
		/// Instead, use the OpenIdProvider.Query field.
		/// </remarks>
		internal Uri RequestUrl;
		/// <summary>
		/// The query of the incoming request.
		/// </summary>
		internal IDictionary<string, string> Query;
		/// <summary>
		/// The version of OpenId being used by the Relying Party
		/// sending the incoming request.
		/// </summary>
		internal Protocol Protocol { get; private set; }

		internal static Uri DefaultProviderEndpoint { get { return getProviderEndpointFromContext(); } }
		internal static Uri DefaultRequestUrl { get { return Util.GetRequestUrlFromContext(); } }
		internal static NameValueCollection DefaultQuery { get { return Util.GetQueryOrFormFromContextNVC(); } }

		/// <summary>
		/// Constructs an OpenId server that uses the HttpApplication dictionary as
		/// its association store and detects common settings.
		/// </summary>
		/// <remarks>
		/// This method requires a current ASP.NET HttpContext.
		/// </remarks>
		public OpenIdProvider()
			: this(Configuration.Store.CreateInstanceOfStore(HttpApplicationStore),
			getProviderEndpointFromContext(), Util.GetRequestUrlFromContext(), Util.GetQueryOrFormFromContext()) { }
		/// <summary>
		/// Constructs an OpenId server that uses a given query and IAssociationStore.
		/// </summary>
		/// <param name="store">
		/// The application-level store where associations with OpenId consumers will be preserved.
		/// </param>
		/// <param name="providerEndpoint">
		/// The Internet-facing URL that responds to OpenID requests.
		/// </param>
		/// <param name="requestUrl">The incoming request URL.</param>
		/// <param name="query">
		/// The name/value pairs that came in on the 
		/// QueryString of a GET request or in the entity of a POST request.
		/// For example: (Request.HttpMethod == "GET" ? Request.QueryString : Request.Form).
		/// </param>
		public OpenIdProvider(IProviderAssociationStore store, Uri providerEndpoint, Uri requestUrl, NameValueCollection query)
			: this(store, providerEndpoint, requestUrl, Util.NameValueCollectionToDictionary(query)) { }
		OpenIdProvider(IProviderAssociationStore store, Uri providerEndpoint, Uri requestUrl, IDictionary<string, string> query) {
			if (store == null) throw new ArgumentNullException("store");
			if (providerEndpoint == null) throw new ArgumentNullException("providerEndpoint");
			if (requestUrl == null) throw new ArgumentNullException("requestUrl");
			if (query == null) throw new ArgumentNullException("query");
			Settings = new ProviderSecuritySettings();
			Endpoint = providerEndpoint;
			RequestUrl = requestUrl;
			Query = query;
			Signatory = new Signatory(store);
			Encoder = new SigningMessageEncoder(Signatory);
			store.ClearExpiredAssociations(); // every so often we should do this.
		}

		/// <summary>
		/// The provider URL that responds to OpenID requests.
		/// </summary>
		/// <remarks>
		/// An auto-detect attempt is made if an ASP.NET HttpContext is available.
		/// </remarks>
		internal Uri Endpoint { get; private set; }

		// TODO: make this property public WHEN its security settings are actually supported.
		/// <summary>
		/// Provides access to the adjustable security settings of this instance
		/// of <see cref="OpenIdProvider"/>.
		/// </summary>
		internal ProviderSecuritySettings Settings { get; private set; }

		bool requestProcessed;
		Request request;
		/// <summary>
		/// Gets the incoming OpenID request if there is one, or null if none was detected.
		/// </summary>
		/// <remarks>
		/// Requests may be infrastructural to OpenID and allow auto-responses, or they may
		/// be authentication requests where the Provider site has to make decisions based
		/// on its own user database and policies.
		/// </remarks>
		[DebuggerBrowsable(DebuggerBrowsableState.Never)] // this property getter executes code
		public IRequest Request {
			get {
				if (!requestProcessed) {
					request = decodeRequest();
					requestProcessed = true;
				}
				return request;
			}
		}

		/// <summary>
		/// Decodes an incoming web request in to a <see cref="Request"/>.
		/// </summary>
		/// <returns>A Request object, or null if the given query doesn't represent an OpenId request.</returns>
		Request decodeRequest() {
			if (!Provider.Request.IsOpenIdRequest(Query)) {
				return null;
			}

			Protocol = Protocol.Detect(Query);
			Request req = Provider.Request.CreateRequest(this);

			Logger.InfoFormat("Received OpenID {0} request.{1}{2}", req.Mode, Environment.NewLine,
				Util.ToString(Query));

			return req;
		}

		/// <summary>
		/// Allows a Provider to send an identity assertion on behalf of one
		/// of its members in order to redirect the member to a relying party
		/// web site and log him/her in immediately in one uninterrupted step.
		/// </summary>
		/// <param name="relyingParty">
		/// The URL of the relying party web site.
		/// This will typically be the home page, but may be a longer URL if
		/// that Relying Party considers the scope of its realm to be more specific.
		/// The URL provided here must allow discovery of the Relying Party's
		/// XRDS document that advertises its OpenID RP endpoint.
		/// </param>
		/// <param name="claimedIdentifier">
		/// The Identifier you are asserting your member controls.
		/// </param>
		/// <param name="localIdentifier">
		/// The Identifier you know your user by internally.  This will typically
		/// be the same as <paramref name="claimedIdentifier"/>.
		/// </param>
		/// <returns>
		/// An <see cref="IResponse"/> object describing the HTTP response to send
		/// the user agent to allow the redirect with assertion to happen.
		/// </returns>
		public IResponse PrepareUnsolicitedAssertion(Realm relyingParty, 
			Identifier claimedIdentifier, Identifier localIdentifier) {
			if (relyingParty == null) throw new ArgumentNullException("relyingParty");
			if (claimedIdentifier == null) throw new ArgumentNullException("claimedIdentifier");
			if (localIdentifier == null) throw new ArgumentNullException("localIdentifier");

			Logger.InfoFormat("Preparing unsolicited assertion for {0}", claimedIdentifier);
			return AssertionMessage.CreateUnsolicitedAssertion(this, 
				relyingParty, claimedIdentifier, localIdentifier);
		}

		const string associationStoreKey = "DotNetOpenId.Provider.OpenIdProvider.AssociationStore";
		/// <summary>
		/// The standard state storage mechanism that uses ASP.NET's HttpApplication state dictionary
		/// to store associations.
		/// </summary>
		public static IProviderAssociationStore HttpApplicationStore {
			get {
				HttpContext context = HttpContext.Current;
				if (context == null)
					throw new InvalidOperationException(Strings.IAssociationStoreRequiredWhenNoHttpContextAvailable);
				var store = (IProviderAssociationStore)context.Application[associationStoreKey];
				if (store == null) {
					context.Application.Lock();
					try {
						if ((store = (IProviderAssociationStore)context.Application[associationStoreKey]) == null) {
							context.Application[associationStoreKey] = store = new ProviderMemoryStore();
						}
					} finally {
						context.Application.UnLock();
					}
				}
				return store;
			}
		}
		static Uri getProviderEndpointFromContext() {
			HttpContext context = HttpContext.Current;
			if (context == null)
				throw new InvalidOperationException(Strings.HttpContextRequiredForThisOverload);
			UriBuilder builder = new UriBuilder(Util.GetRequestUrlFromContext());
			builder.Query = null;
			builder.Fragment = null;
			return builder.Uri;
		}

		/// <summary>
		/// Gets the relevant Configuration section for this OpenIdRelyingParty.
		/// </summary>
		/// <remarks>
		/// This is not a static member because depending on the context within which we are
		/// invoked, the configuration section might be different. (location tag, for example).
		/// </remarks>
		internal static ProviderSection Configuration {
			get { return ProviderSection.Configuration; }
		}
	}
}