using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; using System.IO; using System.Reflection; using System.Web; using DotNetOpenId; using DotNetOpenId.Provider; using DotNetOpenId.RelyingParty; using DotNetOpenId.Test.Mocks; using DotNetOpenId.Test.UI; using log4net; using NUnit.Framework; using IProviderAssociationStore = DotNetOpenId.IAssociationStore; using ProviderMemoryStore = DotNetOpenId.AssociationMemoryStore; [SetUpFixture] public class TestSupport { public static readonly string TestWebDirectory = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), @"..\..\src\DotNetOpenId.TestWeb")); public const string HostTestPage = "HostTest.aspx"; const string identityPage = "IdentityEndpoint.aspx"; const string directedIdentityPage = "DirectedIdentityEndpoint.aspx"; public const string ProviderPage = "ProviderEndpoint.aspx"; public const string DirectedProviderEndpoint = "DirectedProviderEndpoint.aspx"; public const string MobileConsumerPage = "RelyingPartyMobile.aspx"; public const string ConsumerPage = "RelyingParty.aspx"; public const string OPDefaultPage = "OPDefault.aspx"; public static Uri ReturnTo { get { return TestSupport.GetFullUrl(TestSupport.ConsumerPage); } } public static Realm Realm { get { return new Realm(TestSupport.GetFullUrl(TestSupport.ConsumerPage).AbsoluteUri); } } public readonly static ILog Logger = LogManager.GetLogger("DotNetOpenId.Test"); public enum Scenarios { // Authentication test scenarios AutoApproval, AutoApprovalAddFragment, ApproveOnSetup, AlwaysDeny, // Extension test scenarios /// /// Provides all required and requested fields. /// ExtensionFullCooperation, /// /// Provides only those fields marked as required. /// ExtensionPartialCooperation, } internal static UriIdentifier GetOPIdentityUrl(Scenarios scenario, bool useSsl) { var args = new Dictionary { { "user", scenario.ToString() }, }; return new UriIdentifier(GetFullUrl("/" + OPDefaultPage, args, useSsl)); } internal static UriIdentifier GetIdentityUrl(Scenarios scenario, ProtocolVersion providerVersion) { return GetIdentityUrl(scenario, providerVersion, false); } internal static UriIdentifier GetIdentityUrl(Scenarios scenario, ProtocolVersion providerVersion, bool useSsl) { return new UriIdentifier(GetFullUrl("/" + identityPage, new Dictionary { { "user", scenario.ToString() }, { "version", providerVersion.ToString() }, }, useSsl)); } internal static UriIdentifier GetDirectedIdentityUrl(Scenarios scenario, ProtocolVersion providerVersion) { return GetDirectedIdentityUrl(scenario, providerVersion, false); } internal static UriIdentifier GetDirectedIdentityUrl(Scenarios scenario, ProtocolVersion providerVersion, bool useSsl) { return new UriIdentifier(GetFullUrl("/" + directedIdentityPage, new Dictionary { { "user", scenario.ToString() }, { "version", providerVersion.ToString() }, }, useSsl)); } public static Identifier GetDelegateUrl(Scenarios scenario) { return GetDelegateUrl(scenario, false); } public static Identifier GetDelegateUrl(Scenarios scenario, bool useSsl) { return new UriIdentifier(GetFullUrl("/" + scenario, null, useSsl)); } internal static MockIdentifier GetMockIdentifier(Scenarios scenario, ProtocolVersion providerVersion) { return GetMockIdentifier(scenario, providerVersion, false); } internal static MockIdentifier GetMockIdentifier(Scenarios scenario, ProtocolVersion providerVersion, bool useSsl) { ServiceEndpoint se = GetServiceEndpoint(scenario, providerVersion, 10, useSsl); return new MockIdentifier(GetIdentityUrl(scenario, providerVersion, useSsl), new ServiceEndpoint[] { se }); } internal static ServiceEndpoint GetServiceEndpoint(Scenarios scenario, ProtocolVersion providerVersion, int servicePriority, bool useSsl) { return ServiceEndpoint.CreateForClaimedIdentifier( GetIdentityUrl(scenario, providerVersion, useSsl), GetDelegateUrl(scenario, useSsl), GetFullUrl("/" + ProviderPage, null, useSsl), new string[] { Protocol.Lookup(providerVersion).ClaimedIdentifierServiceTypeURI }, servicePriority, 10 ); } internal static MockIdentifier GetMockOPIdentifier(Scenarios scenario, UriIdentifier expectedClaimedId) { return GetMockOPIdentifier(scenario, expectedClaimedId, false, false); } internal static MockIdentifier GetMockOPIdentifier(Scenarios scenario, UriIdentifier expectedClaimedId, bool useSslOpIdentifier, bool useSslProviderEndpoint) { var fields = new Dictionary { { "user", scenario.ToString() }, }; Uri opEndpoint = GetFullUrl(DirectedProviderEndpoint, fields, useSslProviderEndpoint); Uri opIdentifier = GetOPIdentityUrl(scenario, useSslOpIdentifier); ServiceEndpoint se = ServiceEndpoint.CreateForProviderIdentifier( opIdentifier, opEndpoint, new string[] { Protocol.v20.OPIdentifierServiceTypeURI }, 10, 10 ); // Register the Claimed Identifier that directed identity will choose so that RP // discovery on that identifier can be mocked up. MockHttpRequest.RegisterMockXrdsResponse(expectedClaimedId, se); return new MockIdentifier(opIdentifier, new ServiceEndpoint[] { se }); } public static Uri GetFullUrl(string url) { return GetFullUrl(url, null, false); } public static Uri GetFullUrl(string url, string key, object value) { return GetFullUrl(url, new Dictionary { { key, value.ToString() }, }, false); } public static Uri GetFullUrl(string url, IDictionary args, bool useSsl) { Uri defaultUriBase = new Uri(useSsl ? "https://localhost/" : "http://localhost/"); Uri baseUri = UITestSupport.Host != null ? UITestSupport.Host.BaseUri : defaultUriBase; UriBuilder builder = new UriBuilder(new Uri(baseUri, url)); UriUtil.AppendQueryArgs(builder, args); return builder.Uri; } /// /// Returns the content of a given embedded resource. /// /// The path of the file as it appears within the project, /// where the leading / marks the root directory of the project. internal static string LoadEmbeddedFile(string path) { if (!path.StartsWith("/")) path = "/" + path; path = "DotNetOpenId.Test" + path.Replace('/', '.'); Stream resource = Assembly.GetExecutingAssembly().GetManifestResourceStream(path); if (resource == null) throw new ArgumentException(); using (StreamReader sr = new StreamReader(resource)) { return sr.ReadToEnd(); } } internal static IRelyingPartyApplicationStore RelyingPartyStore; internal static IProviderAssociationStore ProviderStore; /// /// Generates a new, stateful whose direct messages /// will be automatically handled by an internal /// that uses the shared . /// internal static OpenIdRelyingParty CreateRelyingParty(NameValueCollection fields) { return CreateRelyingParty(RelyingPartyStore, null, fields); } internal static OpenIdRelyingParty CreateRelyingParty(IRelyingPartyApplicationStore store, NameValueCollection fields) { return CreateRelyingParty(store, null, fields); } /// /// Generates a new whose direct messages /// will be automatically handled by an internal /// that uses the shared . /// internal static OpenIdRelyingParty CreateRelyingParty(IRelyingPartyApplicationStore store, Uri requestUrl, NameValueCollection fields) { var rp = new OpenIdRelyingParty(store, requestUrl ?? GetFullUrl(ConsumerPage), fields ?? new NameValueCollection()); if (fields == null || fields.Count == 0) { Assert.IsNull(rp.Response); } rp.DirectMessageChannel = new DirectMessageTestRedirector(ProviderStore); return rp; } internal static DotNetOpenId.RelyingParty.IAuthenticationRequest CreateRelyingPartyRequest(bool stateless, Scenarios scenario, ProtocolVersion version) { // Publish RP discovery information MockHttpRequest.RegisterMockRPDiscovery(); var rp = TestSupport.CreateRelyingParty(stateless ? null : RelyingPartyStore, null); var rpReq = rp.CreateRequest(TestSupport.GetMockIdentifier(scenario, version), Realm, ReturnTo); { // Sidetrack: verify URLs and other default properties Assert.AreEqual(AuthenticationRequestMode.Setup, rpReq.Mode); Assert.AreEqual(Realm, rpReq.Realm); Assert.AreEqual(ReturnTo, rpReq.ReturnToUrl); } return rpReq; } /// /// Generates a new ready to process a /// response from an . /// internal static IAuthenticationResponse CreateRelyingPartyResponse(IRelyingPartyApplicationStore store, IResponse providerResponse) { return CreateRelyingPartyResponse(store, providerResponse, false); } internal static IAuthenticationResponse CreateRelyingPartyResponse(IRelyingPartyApplicationStore store, IResponse providerResponse, bool requireSsl) { if (providerResponse == null) throw new ArgumentNullException("providerResponse"); var opAuthWebResponse = (Response)providerResponse; var opAuthResponse = (EncodableResponse)opAuthWebResponse.EncodableMessage; var rp = CreateRelyingParty(store, opAuthResponse.RedirectUrl, opAuthResponse.EncodedFields.ToNameValueCollection()); rp.Settings.RequireSsl = requireSsl; // Get the response now, before trying the replay attack. The Response // property is lazily-evaluated, so the replay attack can be evaluated first // and pass, while this one that SUPPOSED to pass fails, if we don't force it now. var response = rp.Response; // Side-track to test for replay attack while we're at it. // This simulates a network sniffing user who caught the // authenticating query en route to either the user agent or // the consumer, and tries the same query to the consumer in an // attempt to spoof the identity of the authenticating user. try { Logger.Info("Attempting replay attack..."); var replayRP = CreateRelyingParty(store, opAuthResponse.RedirectUrl, opAuthResponse.EncodedFields.ToNameValueCollection()); replayRP.Settings.RequireSsl = requireSsl; Assert.AreNotEqual(AuthenticationStatus.Authenticated, replayRP.Response.Status, "Replay attack succeeded!"); } catch (OpenIdException) { // nonce already used // another way to pass } // Return the result of the initial response (not the replay attack one). return response; } /// /// Generates a new that uses the shared /// store in . /// internal static OpenIdProvider CreateProvider(NameValueCollection fields) { return CreateProvider(fields, false); } internal static OpenIdProvider CreateProvider(NameValueCollection fields, bool useSsl) { Protocol protocol = fields != null ? Protocol.Detect(fields.ToDictionary()) : Protocol.v20; Uri opEndpoint = GetFullUrl(ProviderPage, null, useSsl); var provider = new OpenIdProvider(ProviderStore, opEndpoint, opEndpoint, fields ?? new NameValueCollection()); return provider; } internal static OpenIdProvider CreateProviderForRequest(DotNetOpenId.RelyingParty.IAuthenticationRequest request) { IResponse relyingPartyAuthenticationRequest = request.RedirectingResponse; var rpWebMessageToOP = (Response)relyingPartyAuthenticationRequest; var rpMessageToOP = (IndirectMessageRequest)rpWebMessageToOP.EncodableMessage; var opEndpoint = (ServiceEndpoint)request.Provider; var provider = new OpenIdProvider(ProviderStore, opEndpoint.ProviderEndpoint, opEndpoint.ProviderEndpoint, rpMessageToOP.EncodedFields.ToNameValueCollection()); return provider; } internal static IResponse CreateProviderResponseToRequest( DotNetOpenId.RelyingParty.IAuthenticationRequest request, Action prepareProviderResponse) { { // Sidetrack: Verify the return_to and realm URLs var consumerToProviderQuery = HttpUtility.ParseQueryString(request.RedirectingResponse.ExtractUrl().Query); Protocol protocol = Protocol.Detect(consumerToProviderQuery.ToDictionary()); Assert.IsTrue(consumerToProviderQuery[protocol.openid.return_to].StartsWith(request.ReturnToUrl.AbsoluteUri, StringComparison.Ordinal)); Assert.AreEqual(request.Realm.ToString(), consumerToProviderQuery[protocol.openid.Realm]); } var op = TestSupport.CreateProviderForRequest(request); var opReq = (DotNetOpenId.Provider.IAuthenticationRequest)op.Request; prepareProviderResponse(opReq); Assert.IsTrue(opReq.IsResponseReady); return opReq.Response; } internal static IAuthenticationResponse CreateRelyingPartyResponseThroughProvider( DotNetOpenId.RelyingParty.IAuthenticationRequest request, Action providerAction) { var rpReq = (AuthenticationRequest)request; var opResponse = CreateProviderResponseToRequest(rpReq, providerAction); // Be careful to use whatever store the original RP was using. var rp = CreateRelyingPartyResponse(rpReq.RelyingParty.Store, opResponse, ((AuthenticationRequest)request).RelyingParty.Settings.RequireSsl); Assert.IsNotNull(rp); return rp; } [SetUp] public void SetUp() { log4net.Config.XmlConfigurator.Configure(Assembly.GetExecutingAssembly().GetManifestResourceStream("DotNetOpenId.Test.Logging.config")); ResetStores(); } [TearDown] public void TearDown() { log4net.LogManager.Shutdown(); } internal static void ResetStores() { RelyingPartyStore = new ApplicationMemoryStore(); ProviderStore = new ProviderMemoryStore(); } internal static void SetAuthenticationFromScenario(Scenarios scenario, DotNetOpenId.Provider.IAuthenticationRequest request) { Assert.IsTrue(request.IsReturnUrlDiscoverable); switch (scenario) { case TestSupport.Scenarios.ExtensionFullCooperation: case TestSupport.Scenarios.ExtensionPartialCooperation: case TestSupport.Scenarios.AutoApproval: // immediately approve request.IsAuthenticated = true; break; case TestSupport.Scenarios.AutoApprovalAddFragment: request.SetClaimedIdentifierFragment("frag"); request.IsAuthenticated = true; break; case TestSupport.Scenarios.ApproveOnSetup: request.IsAuthenticated = !request.Immediate; break; case TestSupport.Scenarios.AlwaysDeny: request.IsAuthenticated = false; break; default: throw new InvalidOperationException("Unrecognized scenario"); } } /// /// Uses an RPs stored association to resign an altered message from a Provider, /// to simulate a Provider that deliberately sent a bad message in an attempt /// to thwart RP security. /// internal static void Resign(NameValueCollection nvc, IRelyingPartyApplicationStore store) { Debug.Assert(nvc != null); Debug.Assert(store != null); var dict = Util.NameValueCollectionToDictionary(nvc); Protocol protocol = Protocol.Detect(dict); Uri providerEndpoint = new Uri(nvc[protocol.openid.op_endpoint]); string assoc_handle = nvc[protocol.openid.assoc_handle]; Association assoc = store.GetAssociation(providerEndpoint, assoc_handle); Debug.Assert(assoc != null, "Association not found in RP's store. Maybe you're communicating with a hosted OP instead of the TestSupport one?"); IList signed = nvc[protocol.openid.signed].Split(','); var subsetDictionary = new Dictionary(); foreach (string signedKey in signed) { string keyName = protocol.openid.Prefix + signedKey; subsetDictionary.Add(signedKey, dict[keyName]); } nvc[protocol.openid.sig] = Convert.ToBase64String(assoc.Sign(subsetDictionary, signed)); } public static IAssociationStore ProviderStoreContext { get { return DotNetOpenId.Provider.OpenIdProvider.HttpApplicationStore; } } } static class TestExtensions { /// /// Gets a URL that can be requested to send an indirect message. /// public static Uri ExtractUrl(this IResponse message) { DotNetOpenId.Response response = (DotNetOpenId.Response)message; IEncodable encodable = response.EncodableMessage; UriBuilder builder = new UriBuilder(encodable.RedirectUrl); UriUtil.AppendQueryArgs(builder, encodable.EncodedFields); return builder.Uri; } public static NameValueCollection ToNameValueCollection(this IDictionary dictionary) { NameValueCollection nvc = new NameValueCollection(dictionary.Count); foreach (var pair in dictionary) { nvc.Add(pair.Key, pair.Value); } return nvc; } public static IDictionary ToDictionary(this NameValueCollection nvc) { if (nvc == null) return null; Dictionary dict = new Dictionary(nvc.Count); foreach (string key in nvc) { dict[key] = nvc[key]; } return dict; } }