diff options
author | Andrew Arnott <andrewarnott@gmail.com> | 2009-11-13 01:18:20 -0800 |
---|---|---|
committer | Andrew Arnott <andrewarnott@gmail.com> | 2009-11-13 01:18:20 -0800 |
commit | b929b5106a544c10b6736640b86397938790f523 (patch) | |
tree | 9e3cd58afc20cb4804b0ee37ec8df13cdb48821d | |
parent | 75737b85f5dce833c4fed0b1c839150fc1e6c3dc (diff) | |
parent | b71f75b5a02a72e13a6934524626ac21763ea87f (diff) | |
download | DotNetOpenAuth-b929b5106a544c10b6736640b86397938790f523.zip DotNetOpenAuth-b929b5106a544c10b6736640b86397938790f523.tar.gz DotNetOpenAuth-b929b5106a544c10b6736640b86397938790f523.tar.bz2 |
Merge branch 'projecttemplateOAuth'
27 files changed, 2266 insertions, 24 deletions
diff --git a/projecttemplates/WebFormsRelyingParty/Admin/CreateDatabase.sql b/projecttemplates/WebFormsRelyingParty/Admin/CreateDatabase.sql index 2030155..52ca669 100644 --- a/projecttemplates/WebFormsRelyingParty/Admin/CreateDatabase.sql +++ b/projecttemplates/WebFormsRelyingParty/Admin/CreateDatabase.sql @@ -1,13 +1,36 @@ -/****** Object: Table [dbo].[User] Script Date: 10/08/2009 18:10:17 ******/ +SET ANSI_NULLS ON +GO +SET QUOTED_IDENTIFIER ON +GO +CREATE TABLE [dbo].[Consumer]( + [ConsumerId] [int] IDENTITY(1,1) NOT NULL, + [ConsumerKey] [nvarchar](255) COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL, + [ConsumerSecret] [nvarchar](255) COLLATE SQL_Latin1_General_CP1_CI_AS NULL, + [X509Certificate] [image] NULL, + [Callback] [nvarchar](2048) COLLATE SQL_Latin1_General_CP1_CI_AS NULL, + [VerificationCodeFormat] [int] NOT NULL, + [VerificationCodeLength] [int] NOT NULL, + [Name] [nvarchar](50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL, + CONSTRAINT [PK_Consumer] PRIMARY KEY CLUSTERED +( + [ConsumerId] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] +) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] +GO +CREATE UNIQUE NONCLUSTERED INDEX [IX_Consumer] ON [dbo].[Consumer] +( + [ConsumerKey] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] +GO SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO CREATE TABLE [dbo].[User]( [Id] [int] IDENTITY(1,1) NOT NULL, - [FirstName] [nvarchar](50) NULL, - [LastName] [nvarchar](50) NULL, - [EmailAddress] [nvarchar](100) NULL, + [FirstName] [nvarchar](50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL, + [LastName] [nvarchar](50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL, + [EmailAddress] [nvarchar](100) COLLATE SQL_Latin1_General_CP1_CI_AS NULL, [EmailAddressVerified] [bit] NOT NULL, CONSTRAINT [PK_User] PRIMARY KEY CLUSTERED ( @@ -15,24 +38,51 @@ CREATE TABLE [dbo].[User]( )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO -ALTER TABLE [dbo].[User] ADD CONSTRAINT [DF_User_EmailAddressVerified] DEFAULT ((0)) FOR [EmailAddressVerified] -GO - -/****** Object: Table [dbo].[Role] Script Date: 10/08/2009 18:10:17 ******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO CREATE TABLE [dbo].[Role]( [Id] [int] IDENTITY(1,1) NOT NULL, - [Name] [nvarchar](50) NOT NULL, + [Name] [nvarchar](50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL, CONSTRAINT [PK_Role] PRIMARY KEY CLUSTERED ( [Id] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO -/****** Object: Table [dbo].[UserRole] Script Date: 10/08/2009 18:10:17 ******/ +SET ANSI_NULLS ON +GO +SET QUOTED_IDENTIFIER ON +GO +SET ANSI_PADDING ON +GO +CREATE TABLE [dbo].[IssuedToken]( + [TokenId] [int] IDENTITY(1,1) NOT NULL, + [ConsumerId] [int] NOT NULL, + [UserId] [int] NULL, + [Token] [nvarchar](255) COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL, + [TokenSecret] [nvarchar](255) COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL, + [CreatedOn] [datetime] NOT NULL, + [Callback] [nvarchar](2048) COLLATE SQL_Latin1_General_CP1_CI_AS NULL, + [VerificationCode] [nvarchar](255) COLLATE SQL_Latin1_General_CP1_CI_AS NULL, + [ConsumerVersion] [varchar](10) COLLATE SQL_Latin1_General_CP1_CI_AS NULL, + [ExpirationDate] [datetime] NULL, + [IsAccessToken] [bit] NOT NULL, + [Scope] [nvarchar](255) COLLATE SQL_Latin1_General_CP1_CI_AS NULL, + CONSTRAINT [PK_IssuedToken] PRIMARY KEY CLUSTERED +( + [TokenId] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] +) ON [PRIMARY] +GO +SET ANSI_PADDING OFF +GO +CREATE UNIQUE NONCLUSTERED INDEX [IX_IssuedToken] ON [dbo].[IssuedToken] +( + [Token] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] +GO SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON @@ -47,7 +97,6 @@ CREATE TABLE [dbo].[UserRole]( )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO -/****** Object: Table [dbo].[AuthenticationToken] Script Date: 10/08/2009 18:10:17 ******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON @@ -55,15 +104,14 @@ GO CREATE TABLE [dbo].[AuthenticationToken]( [Id] [int] IDENTITY(1,1) NOT NULL, [UserId] [int] NOT NULL, - [OpenIdClaimedIdentifier] [nvarchar](250) COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL, -- very important that claimed_id comparisons be case sensitive - [OpenIdFriendlyIdentifier] [nvarchar](250) NULL, + [OpenIdClaimedIdentifier] [nvarchar](250) COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL, + [OpenIdFriendlyIdentifier] [nvarchar](250) COLLATE SQL_Latin1_General_CP1_CI_AS NULL, CONSTRAINT [PK_AuthenticationToken] PRIMARY KEY CLUSTERED ( [Id] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO -/****** Object: StoredProcedure [dbo].[AddUser] Script Date: 10/08/2009 18:10:32 ******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON @@ -106,7 +154,26 @@ AS RETURN @userid GO -/****** Object: ForeignKey [FK_UserRole_Role] Script Date: 10/08/2009 18:10:17 ******/ +ALTER TABLE [dbo].[User] ADD CONSTRAINT [DF_User_EmailAddressVerified] DEFAULT ((0)) FOR [EmailAddressVerified] +GO +ALTER TABLE [dbo].[IssuedToken] ADD CONSTRAINT [DF_IssuedToken_CreatedOn] DEFAULT (getdate()) FOR [CreatedOn] +GO +ALTER TABLE [dbo].[IssuedToken] ADD CONSTRAINT [DF_IssuedToken_IsAccessToken] DEFAULT ((0)) FOR [IsAccessToken] +GO +ALTER TABLE [dbo].[IssuedToken] WITH CHECK ADD CONSTRAINT [FK_IssuedToken_Consumer] FOREIGN KEY([ConsumerId]) +REFERENCES [dbo].[Consumer] ([ConsumerId]) +ON UPDATE CASCADE +ON DELETE CASCADE +GO +ALTER TABLE [dbo].[IssuedToken] CHECK CONSTRAINT [FK_IssuedToken_Consumer] +GO +ALTER TABLE [dbo].[IssuedToken] WITH CHECK ADD CONSTRAINT [FK_IssuedToken_User] FOREIGN KEY([UserId]) +REFERENCES [dbo].[User] ([Id]) +ON UPDATE CASCADE +ON DELETE CASCADE +GO +ALTER TABLE [dbo].[IssuedToken] CHECK CONSTRAINT [FK_IssuedToken_User] +GO ALTER TABLE [dbo].[UserRole] WITH CHECK ADD CONSTRAINT [FK_UserRole_Role] FOREIGN KEY([RoleId]) REFERENCES [dbo].[Role] ([Id]) ON UPDATE CASCADE @@ -114,7 +181,6 @@ ON DELETE CASCADE GO ALTER TABLE [dbo].[UserRole] CHECK CONSTRAINT [FK_UserRole_Role] GO -/****** Object: ForeignKey [FK_UserRole_User] Script Date: 10/08/2009 18:10:17 ******/ ALTER TABLE [dbo].[UserRole] WITH CHECK ADD CONSTRAINT [FK_UserRole_User] FOREIGN KEY([UserId]) REFERENCES [dbo].[User] ([Id]) ON UPDATE CASCADE @@ -122,7 +188,6 @@ ON DELETE CASCADE GO ALTER TABLE [dbo].[UserRole] CHECK CONSTRAINT [FK_UserRole_User] GO -/****** Object: ForeignKey [FK_AuthenticationToken_User] Script Date: 10/08/2009 18:10:17 ******/ ALTER TABLE [dbo].[AuthenticationToken] WITH CHECK ADD CONSTRAINT [FK_AuthenticationToken_User] FOREIGN KEY([UserId]) REFERENCES [dbo].[User] ([Id]) ON UPDATE CASCADE diff --git a/projecttemplates/WebFormsRelyingParty/Code/OAuthAuthenticationModule.cs b/projecttemplates/WebFormsRelyingParty/Code/OAuthAuthenticationModule.cs new file mode 100644 index 0000000..426dce5 --- /dev/null +++ b/projecttemplates/WebFormsRelyingParty/Code/OAuthAuthenticationModule.cs @@ -0,0 +1,78 @@ +//----------------------------------------------------------------------- +// <copyright file="OAuthAuthenticationModule.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace WebFormsRelyingParty.Code { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Security.Principal; + using System.Web; + using System.Web.Security; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuth; + using DotNetOpenAuth.OAuth.ChannelElements; + using DotNetOpenAuth.OAuth.Messages; + + public class OAuthAuthenticationModule : IHttpModule { + private HttpApplication application; + + #region IHttpModule Members + + /// <summary> + /// Initializes a module and prepares it to handle requests. + /// </summary> + /// <param name="context">An <see cref="T:System.Web.HttpApplication"/> that provides access to the methods, properties, and events common to all application objects within an ASP.NET application</param> + public void Init(HttpApplication context) { + this.application = context; + this.application.AuthenticateRequest += this.context_AuthenticateRequest; + + // Register an event that allows us to override roles for OAuth requests. + var roleManager = (RoleManagerModule)this.application.Modules["RoleManager"]; + roleManager.GetRoles += this.roleManager_GetRoles; + } + + /// <summary> + /// Disposes of the resources (other than memory) used by the module that implements <see cref="T:System.Web.IHttpModule"/>. + /// </summary> + public void Dispose() { + } + + /// <summary> + /// Handles the AuthenticateRequest event of the HttpApplication. + /// </summary> + /// <param name="sender">The source of the event.</param> + /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param> + private void context_AuthenticateRequest(object sender, EventArgs e) { + // Don't read OAuth messages directed at the OAuth controller or else we'll fail nonce checks. + if (this.IsOAuthControllerRequest()) { + return; + } + + IDirectedProtocolMessage incomingMessage = OAuthServiceProvider.ServiceProvider.ReadRequest(new HttpRequestInfo(this.application.Context.Request)); + var authorization = incomingMessage as AccessProtectedResourceRequest; + if (authorization != null) { + this.application.Context.User = OAuthServiceProvider.ServiceProvider.CreatePrincipal(authorization); + } + } + + #endregion + + private bool IsOAuthControllerRequest() { + return string.Equals(this.application.Context.Request.Url.AbsolutePath, "/OAuth.ashx", StringComparison.OrdinalIgnoreCase); + } + + /// <summary> + /// Handles the GetRoles event of the roleManager control. + /// </summary> + /// <param name="sender">The source of the event.</param> + /// <param name="e">The <see cref="System.Web.Security.RoleManagerEventArgs"/> instance containing the event data.</param> + private void roleManager_GetRoles(object sender, RoleManagerEventArgs e) { + if (this.application.User is OAuthPrincipal) { + e.RolesPopulated = true; + } + } + } +} diff --git a/projecttemplates/WebFormsRelyingParty/Code/OAuthAuthorizationManager.cs b/projecttemplates/WebFormsRelyingParty/Code/OAuthAuthorizationManager.cs new file mode 100644 index 0000000..480e1b9 --- /dev/null +++ b/projecttemplates/WebFormsRelyingParty/Code/OAuthAuthorizationManager.cs @@ -0,0 +1,67 @@ +//----------------------------------------------------------------------- +// <copyright file="OAuthAuthorizationManager.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace WebFormsRelyingParty.Code { + using System; + using System.Collections.Generic; + using System.IdentityModel.Policy; + using System.Linq; + using System.Security.Principal; + using System.ServiceModel; + using System.ServiceModel.Channels; + using System.ServiceModel.Security; + using DotNetOpenAuth; + using DotNetOpenAuth.OAuth; + + /// <summary> + /// A WCF extension to authenticate incoming messages using OAuth. + /// </summary> + public class OAuthAuthorizationManager : ServiceAuthorizationManager { + public OAuthAuthorizationManager() { + } + + protected override bool CheckAccessCore(OperationContext operationContext) { + if (!base.CheckAccessCore(operationContext)) { + return false; + } + + HttpRequestMessageProperty httpDetails = operationContext.RequestContext.RequestMessage.Properties[HttpRequestMessageProperty.Name] as HttpRequestMessageProperty; + Uri requestUri = operationContext.RequestContext.RequestMessage.Properties["OriginalHttpRequestUri"] as Uri; + ServiceProvider sp = OAuthServiceProvider.ServiceProvider; + var auth = sp.ReadProtectedResourceAuthorization(httpDetails, requestUri); + if (auth != null) { + var accessToken = Global.DataContext.IssuedToken.OfType<IssuedAccessToken>().First(token => token.Token == auth.AccessToken); + + var principal = sp.CreatePrincipal(auth); + var policy = new OAuthPrincipalAuthorizationPolicy(principal); + var policies = new List<IAuthorizationPolicy> { + policy, + }; + + var securityContext = new ServiceSecurityContext(policies.AsReadOnly()); + if (operationContext.IncomingMessageProperties.Security != null) { + operationContext.IncomingMessageProperties.Security.ServiceSecurityContext = securityContext; + } else { + operationContext.IncomingMessageProperties.Security = new SecurityMessageProperty { + ServiceSecurityContext = securityContext, + }; + } + + securityContext.AuthorizationContext.Properties["Identities"] = new List<IIdentity> { + principal.Identity, + }; + + // Only allow this method call if the access token scope permits it. + string[] scopes = accessToken.Scope.Split('|'); + if (scopes.Contains(operationContext.IncomingMessageHeaders.Action)) { + return true; + } + } + + return false; + } + } +}
\ No newline at end of file diff --git a/projecttemplates/WebFormsRelyingParty/Code/OAuthConsumerTokenManager.cs b/projecttemplates/WebFormsRelyingParty/Code/OAuthConsumerTokenManager.cs new file mode 100644 index 0000000..107934b --- /dev/null +++ b/projecttemplates/WebFormsRelyingParty/Code/OAuthConsumerTokenManager.cs @@ -0,0 +1,48 @@ +//----------------------------------------------------------------------- +// <copyright file="OAuthConsumerTokenManager.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace WebFormsRelyingParty.Code { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Web; + using DotNetOpenAuth.OAuth.ChannelElements; + + public class OAuthConsumerTokenManager : OAuthTokenManager, IConsumerTokenManager { + /// <summary> + /// Initializes a new instance of the <see cref="OAuthConsumerTokenManager"/> class. + /// </summary> + /// <param name="consumerKey">The consumer key.</param> + /// <param name="consumerSecret">The consumer secret.</param> + public OAuthConsumerTokenManager(string consumerKey, string consumerSecret) { + if (String.IsNullOrEmpty(consumerKey)) { + throw new ArgumentNullException("consumerKey"); + } + if (consumerSecret == null) { + throw new ArgumentNullException("consumerSecret"); + } + + this.ConsumerKey = consumerKey; + this.ConsumerSecret = consumerSecret; + } + + #region IConsumerTokenManager Members + + /// <summary> + /// Gets the consumer key. + /// </summary> + /// <value>The consumer key.</value> + public string ConsumerKey { get; private set; } + + /// <summary> + /// Gets the consumer secret. + /// </summary> + /// <value>The consumer secret.</value> + public string ConsumerSecret { get; private set; } + + #endregion + } +} diff --git a/projecttemplates/WebFormsRelyingParty/Code/OAuthPrincipalAuthorizationPolicy.cs b/projecttemplates/WebFormsRelyingParty/Code/OAuthPrincipalAuthorizationPolicy.cs new file mode 100644 index 0000000..b2c9a2d --- /dev/null +++ b/projecttemplates/WebFormsRelyingParty/Code/OAuthPrincipalAuthorizationPolicy.cs @@ -0,0 +1,53 @@ +//----------------------------------------------------------------------- +// <copyright file="OAuthPrincipalAuthorizationPolicy.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace WebFormsRelyingParty.Code { + using System; + using System.Collections.Generic; + using System.IdentityModel.Claims; + using System.IdentityModel.Policy; + using System.Linq; + using System.Web; + using DotNetOpenAuth.OAuth.ChannelElements; + + public class OAuthPrincipalAuthorizationPolicy : IAuthorizationPolicy { + private readonly Guid uniqueId = Guid.NewGuid(); + private readonly OAuthPrincipal principal; + + /// <summary> + /// Initializes a new instance of the <see cref="OAuthPrincipalAuthorizationPolicy"/> class. + /// </summary> + /// <param name="principal">The principal.</param> + public OAuthPrincipalAuthorizationPolicy(OAuthPrincipal principal) { + this.principal = principal; + } + + #region IAuthorizationComponent Members + + /// <summary> + /// Gets a unique ID for this instance. + /// </summary> + public string Id { + get { return this.uniqueId.ToString(); } + } + + #endregion + + #region IAuthorizationPolicy Members + + public ClaimSet Issuer { + get { return ClaimSet.System; } + } + + public bool Evaluate(EvaluationContext evaluationContext, ref object state) { + evaluationContext.AddClaimSet(this, new DefaultClaimSet(Claim.CreateNameClaim(this.principal.Identity.Name))); + evaluationContext.Properties["Principal"] = this.principal; + return true; + } + + #endregion + } +}
\ No newline at end of file diff --git a/projecttemplates/WebFormsRelyingParty/Code/OAuthServiceProvider.cs b/projecttemplates/WebFormsRelyingParty/Code/OAuthServiceProvider.cs new file mode 100644 index 0000000..b914315 --- /dev/null +++ b/projecttemplates/WebFormsRelyingParty/Code/OAuthServiceProvider.cs @@ -0,0 +1,120 @@ +//----------------------------------------------------------------------- +// <copyright file="OAuthServiceProvider.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace WebFormsRelyingParty.Code { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Web; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuth; + using DotNetOpenAuth.OAuth.ChannelElements; + using DotNetOpenAuth.OAuth.Messages; + + public class OAuthServiceProvider { + private const string PendingAuthorizationRequestSessionKey = "PendingAuthorizationRequest"; + + /// <summary> + /// The shared service description for this web site. + /// </summary> + private static ServiceProviderDescription serviceDescription; + + private static OAuthServiceProviderTokenManager tokenManager; + + /// <summary> + /// The shared service provider object. + /// </summary> + private static ServiceProvider serviceProvider; + + /// <summary> + /// The lock to synchronize initialization of the <see cref="serviceProvider"/> field. + /// </summary> + private static object initializerLock = new object(); + + /// <summary> + /// Gets the service provider. + /// </summary> + /// <value>The service provider.</value> + public static ServiceProvider ServiceProvider { + get { + EnsureInitialized(); + return serviceProvider; + } + } + + /// <summary> + /// Gets the service description. + /// </summary> + /// <value>The service description.</value> + public static ServiceProviderDescription ServiceDescription { + get { + EnsureInitialized(); + return serviceDescription; + } + } + + public static UserAuthorizationRequest PendingAuthorizationRequest { + get { return HttpContext.Current.Session[PendingAuthorizationRequestSessionKey] as UserAuthorizationRequest; } + set { HttpContext.Current.Session[PendingAuthorizationRequestSessionKey] = value; } + } + + public static WebFormsRelyingParty.Consumer PendingAuthorizationConsumer { + get { + ITokenContainingMessage message = PendingAuthorizationRequest; + if (message == null) { + throw new InvalidOperationException(); + } + + return Global.DataContext.IssuedToken.OfType<IssuedRequestToken>().Include("Consumer").First(t => t.Token == message.Token).Consumer; + } + } + + public static void AuthorizePendingRequestToken() { + var pendingRequest = PendingAuthorizationRequest; + if (pendingRequest == null) { + throw new InvalidOperationException("No pending authorization request to authorize."); + } + + ITokenContainingMessage msg = pendingRequest; + var token = Global.DataContext.IssuedToken.OfType<IssuedRequestToken>().First(t => t.Token == msg.Token); + token.Authorize(); + + PendingAuthorizationRequest = null; + var response = serviceProvider.PrepareAuthorizationResponse(pendingRequest); + if (response != null) { + serviceProvider.Channel.Send(response); + } + } + + /// <summary> + /// Initializes the <see cref="serviceProvider"/> field if it has not yet been initialized. + /// </summary> + private static void EnsureInitialized() { + if (serviceProvider == null) { + lock (initializerLock) { + if (serviceDescription == null) { + var postEndpoint = new MessageReceivingEndpoint(new Uri(Utilities.ApplicationRoot, "OAuth.ashx"), HttpDeliveryMethods.PostRequest); + var getEndpoint = new MessageReceivingEndpoint(postEndpoint.Location, HttpDeliveryMethods.GetRequest); + serviceDescription = new ServiceProviderDescription { + TamperProtectionElements = new ITamperProtectionChannelBindingElement[] { new HmacSha1SigningBindingElement() }, + RequestTokenEndpoint = postEndpoint, + AccessTokenEndpoint = postEndpoint, + UserAuthorizationEndpoint = getEndpoint, + }; + } + + if (tokenManager == null) { + tokenManager = new OAuthServiceProviderTokenManager(); + } + + if (serviceProvider == null) { + serviceProvider = new ServiceProvider(serviceDescription, tokenManager); + } + } + } + } + } +} diff --git a/projecttemplates/WebFormsRelyingParty/Code/OAuthServiceProviderTokenManager.cs b/projecttemplates/WebFormsRelyingParty/Code/OAuthServiceProviderTokenManager.cs new file mode 100644 index 0000000..224a181 --- /dev/null +++ b/projecttemplates/WebFormsRelyingParty/Code/OAuthServiceProviderTokenManager.cs @@ -0,0 +1,112 @@ +//----------------------------------------------------------------------- +// <copyright file="OAuthServiceProviderTokenManager.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace WebFormsRelyingParty.Code { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Web; + using DotNetOpenAuth.OAuth.ChannelElements; + + public class OAuthServiceProviderTokenManager : OAuthTokenManager, IServiceProviderTokenManager { + /// <summary> + /// Initializes a new instance of the <see cref="OAuthServiceProviderTokenManager"/> class. + /// </summary> + public OAuthServiceProviderTokenManager() { + } + + #region IServiceProviderTokenManager Members + + /// <summary> + /// Gets the Consumer description for a given a Consumer Key. + /// </summary> + /// <param name="consumerKey">The Consumer Key.</param> + /// <returns> + /// A description of the consumer. Never null. + /// </returns> + /// <exception cref="KeyNotFoundException">Thrown if the consumer key cannot be found.</exception> + public IConsumerDescription GetConsumer(string consumerKey) { + try { + return Global.DataContext.Consumer.First(c => c.ConsumerKey == consumerKey); + } catch (InvalidOperationException) { + throw new KeyNotFoundException(); + } + } + + /// <summary> + /// Checks whether a given request token has already been authorized + /// by some user for use by the Consumer that requested it. + /// </summary> + /// <param name="requestToken">The Consumer's request token.</param> + /// <returns> + /// True if the request token has already been fully authorized by the user + /// who owns the relevant protected resources. False if the token has not yet + /// been authorized, has expired or does not exist. + /// </returns> + public bool IsRequestTokenAuthorized(string requestToken) { + return Global.DataContext.IssuedToken.OfType<IssuedRequestToken>().Any( + t => t.Token == requestToken && t.User != null); + } + + /// <summary> + /// Gets details on the named request token. + /// </summary> + /// <param name="token">The request token.</param> + /// <returns>A description of the token. Never null.</returns> + /// <exception cref="KeyNotFoundException">Thrown if the token cannot be found.</exception> + /// <remarks> + /// It is acceptable for implementations to find the token, see that it has expired, + /// delete it from the database and then throw <see cref="KeyNotFoundException"/>, + /// or alternatively it can return the expired token anyway and the OAuth channel will + /// log and throw the appropriate error. + /// </remarks> + public IServiceProviderRequestToken GetRequestToken(string token) { + try { + return Global.DataContext.IssuedToken.OfType<IssuedRequestToken>().First(tok => tok.Token == token); + } catch (InvalidOperationException) { + throw new KeyNotFoundException(); + } + } + + /// <summary> + /// Gets details on the named access token. + /// </summary> + /// <param name="token">The access token.</param> + /// <returns>A description of the token. Never null.</returns> + /// <exception cref="KeyNotFoundException">Thrown if the token cannot be found.</exception> + /// <remarks> + /// It is acceptable for implementations to find the token, see that it has expired, + /// delete it from the database and then throw <see cref="KeyNotFoundException"/>, + /// or alternatively it can return the expired token anyway and the OAuth channel will + /// log and throw the appropriate error. + /// </remarks> + public IServiceProviderAccessToken GetAccessToken(string token) { + try { + return Global.DataContext.IssuedToken.OfType<IssuedAccessToken>().First(tok => tok.Token == token); + } catch (InvalidOperationException) { + throw new KeyNotFoundException(); + } + } + + /// <summary> + /// Persists any changes made to the token. + /// </summary> + /// <param name="token">The token whose properties have been changed.</param> + /// <remarks> + /// This library will invoke this method after making a set + /// of changes to the token as part of a web request to give the host + /// the opportunity to persist those changes to a database. + /// Depending on the object persistence framework the host site uses, + /// this method MAY not need to do anything (if changes made to the token + /// will automatically be saved without any extra handling). + /// </remarks> + public void UpdateToken(IServiceProviderRequestToken token) { + Global.DataContext.SaveChanges(); + } + + #endregion + } +} diff --git a/projecttemplates/WebFormsRelyingParty/Code/OAuthTokenManager.cs b/projecttemplates/WebFormsRelyingParty/Code/OAuthTokenManager.cs new file mode 100644 index 0000000..ff757c9 --- /dev/null +++ b/projecttemplates/WebFormsRelyingParty/Code/OAuthTokenManager.cs @@ -0,0 +1,143 @@ +//----------------------------------------------------------------------- +// <copyright file="OAuthTokenManager.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace WebFormsRelyingParty.Code { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Security.Cryptography.X509Certificates; + using System.Web; + using DotNetOpenAuth.OAuth; + using DotNetOpenAuth.OAuth.ChannelElements; + using DotNetOpenAuth.OAuth.Messages; + + /// <summary> + /// The token manager this web site uses in its roles both as + /// a consumer and as a service provider. + /// </summary> + public class OAuthTokenManager : ITokenManager { + /// <summary> + /// Initializes a new instance of the <see cref="OAuthTokenManager"/> class. + /// </summary> + protected OAuthTokenManager() { + } + + #region ITokenManager Members + + /// <summary> + /// Gets the Token Secret given a request or access token. + /// </summary> + /// <param name="token">The request or access token.</param> + /// <returns> + /// The secret associated with the given token. + /// </returns> + /// <exception cref="ArgumentException">Thrown if the secret cannot be found for the given token.</exception> + public string GetTokenSecret(string token) { + try { + return Global.DataContext.IssuedToken.First(t => t.Token == token).TokenSecret; + } catch (InvalidOperationException) { + throw new ArgumentOutOfRangeException(); + } + } + + /// <summary> + /// Stores a newly generated unauthorized request token, secret, and optional + /// application-specific parameters for later recall. + /// </summary> + /// <param name="request">The request message that resulted in the generation of a new unauthorized request token.</param> + /// <param name="response">The response message that includes the unauthorized request token.</param> + /// <exception cref="ArgumentException">Thrown if the consumer key is not registered, or a required parameter was not found in the parameters collection.</exception> + /// <remarks> + /// Request tokens stored by this method SHOULD NOT associate any user account with this token. + /// It usually opens up security holes in your application to do so. Instead, you associate a user + /// account with access tokens (not request tokens) in the <see cref="ExpireRequestTokenAndStoreNewAccessToken"/> + /// method. + /// </remarks> + public void StoreNewRequestToken(UnauthorizedTokenRequest request, ITokenSecretContainingMessage response) { + Consumer consumer; + try { + consumer = Global.DataContext.Consumer.First(c => c.ConsumerKey == request.ConsumerKey); + } catch (InvalidOperationException) { + throw new ArgumentOutOfRangeException(); + } + + var token = new IssuedRequestToken { + Callback = request.Callback, + Consumer = consumer, + CreatedOn = DateTime.Now, + Token = response.Token, + TokenSecret = response.TokenSecret, + }; + string scope; + if (request.ExtraData.TryGetValue("scope", out scope)) { + token.Scope = scope; + } + Global.DataContext.AddToIssuedToken(token); + Global.DataContext.SaveChanges(); + } + + /// <summary> + /// Deletes a request token and its associated secret and stores a new access token and secret. + /// </summary> + /// <param name="consumerKey">The Consumer that is exchanging its request token for an access token.</param> + /// <param name="requestToken">The Consumer's request token that should be deleted/expired.</param> + /// <param name="accessToken">The new access token that is being issued to the Consumer.</param> + /// <param name="accessTokenSecret">The secret associated with the newly issued access token.</param> + /// <remarks> + /// <para> + /// Any scope of granted privileges associated with the request token from the + /// original call to <see cref="StoreNewRequestToken"/> should be carried over + /// to the new Access Token. + /// </para> + /// <para> + /// To associate a user account with the new access token, + /// <see cref="System.Web.HttpContext.User">HttpContext.Current.User</see> may be + /// useful in an ASP.NET web application within the implementation of this method. + /// Alternatively you may store the access token here without associating with a user account, + /// and wait until <see cref="WebConsumer.ProcessUserAuthorization()"/> or + /// <see cref="DesktopConsumer.ProcessUserAuthorization(string, string)"/> return the access + /// token to associate the access token with a user account at that point. + /// </para> + /// </remarks> + public void ExpireRequestTokenAndStoreNewAccessToken(string consumerKey, string requestToken, string accessToken, string accessTokenSecret) { + var requestTokenEntity = Global.DataContext.IssuedToken.OfType<IssuedRequestToken>() + .Include("User") + .First(t => t.Consumer.ConsumerKey == consumerKey && t.Token == requestToken); + + var accessTokenEntity = new IssuedAccessToken { + Token = accessToken, + TokenSecret = accessTokenSecret, + ExpirationDate = null, // currently, our access tokens don't expire + CreatedOn = DateTime.Now, + User = requestTokenEntity.User, + Scope = requestTokenEntity.Scope, + Consumer = requestTokenEntity.Consumer, + }; + + Global.DataContext.DeleteObject(requestTokenEntity); + Global.DataContext.AddToIssuedToken(accessTokenEntity); + Global.DataContext.SaveChanges(); + } + + /// <summary> + /// Classifies a token as a request token or an access token. + /// </summary> + /// <param name="token">The token to classify.</param> + /// <returns> + /// Request or Access token, or invalid if the token is not recognized. + /// </returns> + public TokenType GetTokenType(string token) { + IssuedToken tok = Global.DataContext.IssuedToken.FirstOrDefault(t => t.Token == token); + if (tok == null) { + return TokenType.InvalidToken; + } else { + return tok is IssuedAccessToken ? TokenType.AccessToken : TokenType.RequestToken; + } + } + + #endregion + } +} diff --git a/projecttemplates/WebFormsRelyingParty/Code/Utilities.cs b/projecttemplates/WebFormsRelyingParty/Code/Utilities.cs new file mode 100644 index 0000000..25d293e --- /dev/null +++ b/projecttemplates/WebFormsRelyingParty/Code/Utilities.cs @@ -0,0 +1,70 @@ +//----------------------------------------------------------------------- +// <copyright file="Utilities.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace WebFormsRelyingParty.Code { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Security.Cryptography; + using System.Web; + + public static class Utilities { + private const string CsrfCookieName = "CsrfCookie"; + private static readonly RandomNumberGenerator CryptoRandomDataGenerator = new RNGCryptoServiceProvider(); + + /// <summary> + /// Gets the full URI of the web application root. Guaranteed to end in a slash. + /// </summary> + public static Uri ApplicationRoot { + get { + string appRoot = HttpContext.Current.Request.ApplicationPath; + if (!appRoot.EndsWith("/", StringComparison.Ordinal)) { + appRoot += "/"; + } + + return new Uri(HttpContext.Current.Request.Url, appRoot); + } + } + + public static string SetCsrfCookie() { + // Generate an unpredictable secret that goes to the user agent and must come back + // with authorization to guarantee the user interacted with this page rather than + // being scripted by an evil Consumer. + byte[] randomData = new byte[8]; + CryptoRandomDataGenerator.GetBytes(randomData); + string secret = Convert.ToBase64String(randomData); + + // Send the secret down as a cookie... + var cookie = new HttpCookie(CsrfCookieName, secret) { + Path = HttpContext.Current.Request.Path, + HttpOnly = true, + Expires = DateTime.Now.AddMinutes(30), + }; + HttpContext.Current.Response.SetCookie(cookie); + + // ...and also return the secret so the caller can save it as a hidden form field. + return secret; + } + + public static void VerifyCsrfCookie(string secret) { + var cookie = HttpContext.Current.Request.Cookies[CsrfCookieName]; + if (cookie != null) { + if (cookie.Value == secret && !string.IsNullOrEmpty(secret)) { + // Valid CSRF check. Clear the cookie and return. + cookie.Expires = DateTime.Now.Subtract(TimeSpan.FromDays(1)); + cookie.Value = string.Empty; + if (HttpContext.Current.Request.Browser["supportsEmptyStringInCookieValue"] == "false") { + cookie.Value = "NoCookie"; + } + HttpContext.Current.Response.SetCookie(cookie); + return; + } + } + + throw new InvalidOperationException("Invalid CSRF check."); + } + } +} diff --git a/projecttemplates/WebFormsRelyingParty/Global.asax.cs b/projecttemplates/WebFormsRelyingParty/Global.asax.cs index 15a1047..8a14dfc 100644 --- a/projecttemplates/WebFormsRelyingParty/Global.asax.cs +++ b/projecttemplates/WebFormsRelyingParty/Global.asax.cs @@ -148,6 +148,7 @@ namespace WebFormsRelyingParty { if (DataContextTransactionSimple != null) { DataContextTransactionSimple.Rollback(); DataContextTransactionSimple.Dispose(); + DataContextTransactionSimple = null; } } diff --git a/projecttemplates/WebFormsRelyingParty/Members/OAuthAuthorize.aspx b/projecttemplates/WebFormsRelyingParty/Members/OAuthAuthorize.aspx new file mode 100644 index 0000000..349ac0a --- /dev/null +++ b/projecttemplates/WebFormsRelyingParty/Members/OAuthAuthorize.aspx @@ -0,0 +1,67 @@ +<%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" + CodeBehind="OAuthAuthorize.aspx.cs" Inherits="WebFormsRelyingParty.Members.OAuthAuthorize" %> + +<asp:Content ID="Content2" ContentPlaceHolderID="Body" runat="server"> + <h2> + Client authorization + </h2> + <asp:MultiView runat="server" ID="outerMultiView" ActiveViewIndex="0"> + <asp:View runat="server" ID="getPermissionView"> + <div style="background-color: Yellow"> + <b>Warning</b>: Never give your login credentials to another web site or application. + </div> + <p> + The + <asp:Label ID="consumerNameLabel" runat="server" Text="(app name)" /> + application is requesting to access the private data in your account here. Is that + alright with you? + </p> + <p> + If you grant access now, you can revoke it at any time by returning to this page. + </p> + <div style="display: none" id="responseButtonsDiv"> + <asp:Button ID="yesButton" runat="server" Text="Yes" OnClick="yesButton_Click" /> + <asp:Button ID="noButton" runat="server" Text="No" OnClick="noButton_Click" /> + <asp:HiddenField runat="server" ID="csrfCheck" EnableViewState="false" /> + </div> + <div id="javascriptDisabled"> + <b>Javascript appears to be disabled in your browser. </b>This page requires Javascript + to be enabled to better protect your security. + </div> + <asp:Panel runat="server" BackColor="Red" ForeColor="White" Font-Bold="true" Visible="false" ID="OAuth10ConsumerWarning"> + This website is registered with service_PROVIDER_DOMAIN_NAME to make authorization requests, but has not been configured to send requests securely. If you grant access but you did not initiate this request at consumer_DOMAIN_NAME, it may be possible for other users of consumer_DOMAIN_NAME to access your data. We recommend you deny access unless you are certain that you initiated this request directly with consumer_DOMAIN_NAME. + </asp:Panel> + + <script language="javascript" type="text/javascript"> + //<![CDATA[ + // we use HTML to hide the action buttons and Javascript to show them + // to protect against click-jacking in an iframe whose javascript is disabled. + document.getElementById('responseButtonsDiv').style.display = 'block'; + document.getElementById('javascriptDisabled').style.display = 'none'; + + // Frame busting code (to protect us from being hosted in an iframe). + // This protects us from click-jacking. + if (document.location !== window.top.location) { + window.top.location = document.location; + } + //]]> + </script> + + </asp:View> + <asp:View ID="authorizationGrantedView" runat="server"> + <p>Authorization has been granted.</p> + <asp:MultiView runat="server" ID="verifierMultiView" ActiveViewIndex="0"> + <asp:View ID="verificationCodeView" runat="server"> + <p>You must enter this verification code at the Consumer: <asp:Label runat="server" + ID="verificationCodeLabel" /> </p> + </asp:View> + <asp:View ID="noCallbackView" runat="server"> + <p>You may now close this window and return to the Consumer. </p> + </asp:View> + </asp:MultiView> + </asp:View> + <asp:View ID="authorizationDeniedView" runat="server"> + <p>Authorization has been denied. You're free to do whatever now. </p> + </asp:View> + </asp:MultiView> +</asp:Content> diff --git a/projecttemplates/WebFormsRelyingParty/Members/OAuthAuthorize.aspx.cs b/projecttemplates/WebFormsRelyingParty/Members/OAuthAuthorize.aspx.cs new file mode 100644 index 0000000..f9a520b --- /dev/null +++ b/projecttemplates/WebFormsRelyingParty/Members/OAuthAuthorize.aspx.cs @@ -0,0 +1,63 @@ +//----------------------------------------------------------------------- +// <copyright file="OAuthAuthorize.aspx.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace WebFormsRelyingParty.Members { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Web; + using System.Web.UI; + using System.Web.UI.WebControls; + using DotNetOpenAuth.OAuth; + using DotNetOpenAuth.OAuth.Messages; + using WebFormsRelyingParty.Code; + + public partial class OAuthAuthorize : System.Web.UI.Page { + protected void Page_Load(object sender, EventArgs e) { + if (!IsPostBack) { + var pendingRequest = OAuthServiceProvider.PendingAuthorizationRequest; + if (pendingRequest == null) { + Response.Redirect("~/"); + } + + this.csrfCheck.Value = Utilities.SetCsrfCookie(); + this.consumerNameLabel.Text = HttpUtility.HtmlEncode(OAuthServiceProvider.PendingAuthorizationConsumer.Name); + OAuth10ConsumerWarning.Visible = pendingRequest.IsUnsafeRequest; + } else { + Utilities.VerifyCsrfCookie(this.csrfCheck.Value); + } + } + + protected void yesButton_Click(object sender, EventArgs e) { + outerMultiView.SetActiveView(authorizationGrantedView); + + var consumer = OAuthServiceProvider.PendingAuthorizationConsumer; + var tokenManager = OAuthServiceProvider.ServiceProvider.TokenManager; + var pendingRequest = OAuthServiceProvider.PendingAuthorizationRequest; + ITokenContainingMessage requestTokenMessage = pendingRequest; + var requestToken = tokenManager.GetRequestToken(requestTokenMessage.Token); + + OAuthServiceProvider.AuthorizePendingRequestToken(); + + // The rest of this method only executes if we couldn't automatically + // redirect to the consumer. + if (pendingRequest.IsUnsafeRequest) { + this.verifierMultiView.SetActiveView(noCallbackView); + } else { + this.verifierMultiView.SetActiveView(verificationCodeView); + string verifier = ServiceProvider.CreateVerificationCode(consumer.VerificationCodeFormat, consumer.VerificationCodeLength); + this.verificationCodeLabel.Text = verifier; + requestToken.VerificationCode = verifier; + tokenManager.UpdateToken(requestToken); + } + } + + protected void noButton_Click(object sender, EventArgs e) { + outerMultiView.SetActiveView(authorizationDeniedView); + OAuthServiceProvider.PendingAuthorizationRequest = null; + } + } +} diff --git a/projecttemplates/WebFormsRelyingParty/Members/OAuthAuthorize.aspx.designer.cs b/projecttemplates/WebFormsRelyingParty/Members/OAuthAuthorize.aspx.designer.cs new file mode 100644 index 0000000..3b41f3c --- /dev/null +++ b/projecttemplates/WebFormsRelyingParty/Members/OAuthAuthorize.aspx.designer.cs @@ -0,0 +1,133 @@ +//------------------------------------------------------------------------------ +// <auto-generated> +// This code was generated by a tool. +// Runtime Version:2.0.50727.4927 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// </auto-generated> +//------------------------------------------------------------------------------ + +namespace WebFormsRelyingParty.Members { + + + public partial class OAuthAuthorize { + + /// <summary> + /// outerMultiView control. + /// </summary> + /// <remarks> + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// </remarks> + protected global::System.Web.UI.WebControls.MultiView outerMultiView; + + /// <summary> + /// getPermissionView control. + /// </summary> + /// <remarks> + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// </remarks> + protected global::System.Web.UI.WebControls.View getPermissionView; + + /// <summary> + /// consumerNameLabel control. + /// </summary> + /// <remarks> + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// </remarks> + protected global::System.Web.UI.WebControls.Label consumerNameLabel; + + /// <summary> + /// yesButton control. + /// </summary> + /// <remarks> + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// </remarks> + protected global::System.Web.UI.WebControls.Button yesButton; + + /// <summary> + /// noButton control. + /// </summary> + /// <remarks> + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// </remarks> + protected global::System.Web.UI.WebControls.Button noButton; + + /// <summary> + /// csrfCheck control. + /// </summary> + /// <remarks> + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// </remarks> + protected global::System.Web.UI.WebControls.HiddenField csrfCheck; + + /// <summary> + /// OAuth10ConsumerWarning control. + /// </summary> + /// <remarks> + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// </remarks> + protected global::System.Web.UI.WebControls.Panel OAuth10ConsumerWarning; + + /// <summary> + /// authorizationGrantedView control. + /// </summary> + /// <remarks> + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// </remarks> + protected global::System.Web.UI.WebControls.View authorizationGrantedView; + + /// <summary> + /// verifierMultiView control. + /// </summary> + /// <remarks> + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// </remarks> + protected global::System.Web.UI.WebControls.MultiView verifierMultiView; + + /// <summary> + /// verificationCodeView control. + /// </summary> + /// <remarks> + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// </remarks> + protected global::System.Web.UI.WebControls.View verificationCodeView; + + /// <summary> + /// verificationCodeLabel control. + /// </summary> + /// <remarks> + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// </remarks> + protected global::System.Web.UI.WebControls.Label verificationCodeLabel; + + /// <summary> + /// noCallbackView control. + /// </summary> + /// <remarks> + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// </remarks> + protected global::System.Web.UI.WebControls.View noCallbackView; + + /// <summary> + /// authorizationDeniedView control. + /// </summary> + /// <remarks> + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// </remarks> + protected global::System.Web.UI.WebControls.View authorizationDeniedView; + } +} diff --git a/projecttemplates/WebFormsRelyingParty/Members/Web.config b/projecttemplates/WebFormsRelyingParty/Members/Web.config index aafc323..f95a16d 100644 --- a/projecttemplates/WebFormsRelyingParty/Members/Web.config +++ b/projecttemplates/WebFormsRelyingParty/Members/Web.config @@ -15,4 +15,13 @@ <deny users="?" /> </authorization> </system.web> + + <!-- Protect certain user pages from delegated (OAuth) clients. --> + <location path="AccountInfo.aspx"> + <system.web> + <authorization> + <deny roles="delegated" /> + </authorization> + </system.web> + </location> </configuration> diff --git a/projecttemplates/WebFormsRelyingParty/Model.Consumer.cs b/projecttemplates/WebFormsRelyingParty/Model.Consumer.cs new file mode 100644 index 0000000..5076d6d --- /dev/null +++ b/projecttemplates/WebFormsRelyingParty/Model.Consumer.cs @@ -0,0 +1,40 @@ +//----------------------------------------------------------------------- +// <copyright file="Model.Consumer.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace WebFormsRelyingParty { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Security.Cryptography.X509Certificates; + using System.Web; + using DotNetOpenAuth.OAuth; + using DotNetOpenAuth.OAuth.ChannelElements; + + public partial class Consumer : IConsumerDescription { + public VerificationCodeFormat VerificationCodeFormat { + get { return (VerificationCodeFormat)this.VerificationCodeFormatAsInt; } + set { this.VerificationCodeFormatAsInt = (int)value; } + } + + public X509Certificate2 Certificate { + get { return this.X509CertificateAsBinary != null ? new X509Certificate2(this.X509CertificateAsBinary) : null; } + set { this.X509CertificateAsBinary = value != null ? value.RawData : null; } + } + + public Uri Callback { + get { return this.CallbackAsString != null ? new Uri(this.CallbackAsString) : null; } + set { this.CallbackAsString = value != null ? value.AbsoluteUri : null; } + } + + string IConsumerDescription.Secret { + get { return this.ConsumerSecret; } + } + + string IConsumerDescription.Key { + get { return this.ConsumerKey; } + } + } +} diff --git a/projecttemplates/WebFormsRelyingParty/Model.Designer.cs b/projecttemplates/WebFormsRelyingParty/Model.Designer.cs index 321d83b..d265efa 100644 --- a/projecttemplates/WebFormsRelyingParty/Model.Designer.cs +++ b/projecttemplates/WebFormsRelyingParty/Model.Designer.cs @@ -11,9 +11,11 @@ [assembly: global::System.Data.Objects.DataClasses.EdmSchemaAttribute()] [assembly: global::System.Data.Objects.DataClasses.EdmRelationshipAttribute("DatabaseModel", "UserRole", "Role", global::System.Data.Metadata.Edm.RelationshipMultiplicity.Many, typeof(WebFormsRelyingParty.Role), "User", global::System.Data.Metadata.Edm.RelationshipMultiplicity.Many, typeof(WebFormsRelyingParty.User))] [assembly: global::System.Data.Objects.DataClasses.EdmRelationshipAttribute("DatabaseModel", "UserAuthenticationToken", "User", global::System.Data.Metadata.Edm.RelationshipMultiplicity.One, typeof(WebFormsRelyingParty.User), "AuthenticationToken", global::System.Data.Metadata.Edm.RelationshipMultiplicity.Many, typeof(WebFormsRelyingParty.AuthenticationToken))] +[assembly: global::System.Data.Objects.DataClasses.EdmRelationshipAttribute("DatabaseModel", "FK_IssuedToken_Consumer", "Consumer", global::System.Data.Metadata.Edm.RelationshipMultiplicity.One, typeof(WebFormsRelyingParty.Consumer), "IssuedTokens", global::System.Data.Metadata.Edm.RelationshipMultiplicity.Many, typeof(WebFormsRelyingParty.IssuedToken))] +[assembly: global::System.Data.Objects.DataClasses.EdmRelationshipAttribute("DatabaseModel", "FK_IssuedToken_User", "User", global::System.Data.Metadata.Edm.RelationshipMultiplicity.ZeroOrOne, typeof(WebFormsRelyingParty.User), "IssuedTokens", global::System.Data.Metadata.Edm.RelationshipMultiplicity.Many, typeof(WebFormsRelyingParty.IssuedToken))] // Original file name: -// Generation date: 11/9/2009 7:05:56 AM +// Generation date: 11/11/2009 10:26:25 PM namespace WebFormsRelyingParty { @@ -93,6 +95,36 @@ namespace WebFormsRelyingParty } private global::System.Data.Objects.ObjectQuery<AuthenticationToken> _AuthenticationToken; /// <summary> + /// There are no comments for Consumer in the schema. + /// </summary> + public global::System.Data.Objects.ObjectQuery<Consumer> Consumer + { + get + { + if ((this._Consumer == null)) + { + this._Consumer = base.CreateQuery<Consumer>("[Consumer]"); + } + return this._Consumer; + } + } + private global::System.Data.Objects.ObjectQuery<Consumer> _Consumer; + /// <summary> + /// There are no comments for IssuedToken in the schema. + /// </summary> + public global::System.Data.Objects.ObjectQuery<IssuedToken> IssuedToken + { + get + { + if ((this._IssuedToken == null)) + { + this._IssuedToken = base.CreateQuery<IssuedToken>("[IssuedToken]"); + } + return this._IssuedToken; + } + } + private global::System.Data.Objects.ObjectQuery<IssuedToken> _IssuedToken; + /// <summary> /// There are no comments for Role in the schema. /// </summary> public void AddToRole(Role role) @@ -113,6 +145,20 @@ namespace WebFormsRelyingParty { base.AddObject("AuthenticationToken", authenticationToken); } + /// <summary> + /// There are no comments for Consumer in the schema. + /// </summary> + public void AddToConsumer(Consumer consumer) + { + base.AddObject("Consumer", consumer); + } + /// <summary> + /// There are no comments for IssuedToken in the schema. + /// </summary> + public void AddToIssuedToken(IssuedToken issuedToken) + { + base.AddObject("IssuedToken", issuedToken); + } } /// <summary> /// There are no comments for DatabaseModel.AuthenticationToken in the schema. @@ -515,5 +561,608 @@ namespace WebFormsRelyingParty } } } + /// <summary> + /// There are no comments for IssuedToken in the schema. + /// </summary> + [global::System.Data.Objects.DataClasses.EdmRelationshipNavigationPropertyAttribute("DatabaseModel", "FK_IssuedToken_User", "IssuedTokens")] + [global::System.Xml.Serialization.XmlIgnoreAttribute()] + [global::System.Xml.Serialization.SoapIgnoreAttribute()] + [global::System.Runtime.Serialization.DataMemberAttribute()] + public global::System.Data.Objects.DataClasses.EntityCollection<IssuedToken> IssuedToken + { + get + { + return ((global::System.Data.Objects.DataClasses.IEntityWithRelationships)(this)).RelationshipManager.GetRelatedCollection<IssuedToken>("DatabaseModel.FK_IssuedToken_User", "IssuedTokens"); + } + set + { + if ((value != null)) + { + ((global::System.Data.Objects.DataClasses.IEntityWithRelationships)(this)).RelationshipManager.InitializeRelatedCollection<IssuedToken>("DatabaseModel.FK_IssuedToken_User", "IssuedTokens", value); + } + } + } + } + /// <summary> + /// There are no comments for DatabaseModel.Consumer in the schema. + /// </summary> + /// <KeyProperties> + /// ConsumerId + /// </KeyProperties> + [global::System.Data.Objects.DataClasses.EdmEntityTypeAttribute(NamespaceName="DatabaseModel", Name="Consumer")] + [global::System.Runtime.Serialization.DataContractAttribute(IsReference=true)] + [global::System.Serializable()] + public partial class Consumer : global::System.Data.Objects.DataClasses.EntityObject + { + /// <summary> + /// Create a new Consumer object. + /// </summary> + /// <param name="consumerKey">Initial value of ConsumerKey.</param> + /// <param name="verificationCodeLength">Initial value of VerificationCodeLength.</param> + /// <param name="consumerId">Initial value of ConsumerId.</param> + public static Consumer CreateConsumer(string consumerKey, int verificationCodeLength, int consumerId) + { + Consumer consumer = new Consumer(); + consumer.ConsumerKey = consumerKey; + consumer.VerificationCodeLength = verificationCodeLength; + consumer.ConsumerId = consumerId; + return consumer; + } + /// <summary> + /// There are no comments for Property ConsumerKey in the schema. + /// </summary> + [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute(IsNullable=false)] + [global::System.Runtime.Serialization.DataMemberAttribute()] + public string ConsumerKey + { + get + { + return this._ConsumerKey; + } + set + { + this.OnConsumerKeyChanging(value); + this.ReportPropertyChanging("ConsumerKey"); + this._ConsumerKey = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value, false); + this.ReportPropertyChanged("ConsumerKey"); + this.OnConsumerKeyChanged(); + } + } + private string _ConsumerKey; + partial void OnConsumerKeyChanging(string value); + partial void OnConsumerKeyChanged(); + /// <summary> + /// There are no comments for Property ConsumerSecret in the schema. + /// </summary> + [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute()] + [global::System.Runtime.Serialization.DataMemberAttribute()] + public string ConsumerSecret + { + get + { + return this._ConsumerSecret; + } + set + { + this.OnConsumerSecretChanging(value); + this.ReportPropertyChanging("ConsumerSecret"); + this._ConsumerSecret = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value, true); + this.ReportPropertyChanged("ConsumerSecret"); + this.OnConsumerSecretChanged(); + } + } + private string _ConsumerSecret; + partial void OnConsumerSecretChanging(string value); + partial void OnConsumerSecretChanged(); + /// <summary> + /// There are no comments for Property X509CertificateAsBinary in the schema. + /// </summary> + [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute()] + [global::System.Runtime.Serialization.DataMemberAttribute()] + private byte[] X509CertificateAsBinary + { + get + { + return global::System.Data.Objects.DataClasses.StructuralObject.GetValidValue(this._X509CertificateAsBinary); + } + set + { + this.OnX509CertificateAsBinaryChanging(value); + this.ReportPropertyChanging("X509CertificateAsBinary"); + this._X509CertificateAsBinary = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value, true); + this.ReportPropertyChanged("X509CertificateAsBinary"); + this.OnX509CertificateAsBinaryChanged(); + } + } + private byte[] _X509CertificateAsBinary; + partial void OnX509CertificateAsBinaryChanging(byte[] value); + partial void OnX509CertificateAsBinaryChanged(); + /// <summary> + /// There are no comments for Property CallbackAsString in the schema. + /// </summary> + [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute()] + [global::System.Runtime.Serialization.DataMemberAttribute()] + public string CallbackAsString + { + get + { + return this._CallbackAsString; + } + set + { + this.OnCallbackAsStringChanging(value); + this.ReportPropertyChanging("CallbackAsString"); + this._CallbackAsString = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value, true); + this.ReportPropertyChanged("CallbackAsString"); + this.OnCallbackAsStringChanged(); + } + } + private string _CallbackAsString; + partial void OnCallbackAsStringChanging(string value); + partial void OnCallbackAsStringChanged(); + /// <summary> + /// There are no comments for Property VerificationCodeFormatAsInt in the schema. + /// </summary> + [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute(IsNullable=false)] + [global::System.Runtime.Serialization.DataMemberAttribute()] + private int VerificationCodeFormatAsInt + { + get + { + return this._VerificationCodeFormatAsInt; + } + set + { + this.OnVerificationCodeFormatAsIntChanging(value); + this.ReportPropertyChanging("VerificationCodeFormatAsInt"); + this._VerificationCodeFormatAsInt = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value); + this.ReportPropertyChanged("VerificationCodeFormatAsInt"); + this.OnVerificationCodeFormatAsIntChanged(); + } + } + private int _VerificationCodeFormatAsInt; + partial void OnVerificationCodeFormatAsIntChanging(int value); + partial void OnVerificationCodeFormatAsIntChanged(); + /// <summary> + /// There are no comments for Property VerificationCodeLength in the schema. + /// </summary> + [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute(IsNullable=false)] + [global::System.Runtime.Serialization.DataMemberAttribute()] + public int VerificationCodeLength + { + get + { + return this._VerificationCodeLength; + } + set + { + this.OnVerificationCodeLengthChanging(value); + this.ReportPropertyChanging("VerificationCodeLength"); + this._VerificationCodeLength = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value); + this.ReportPropertyChanged("VerificationCodeLength"); + this.OnVerificationCodeLengthChanged(); + } + } + private int _VerificationCodeLength; + partial void OnVerificationCodeLengthChanging(int value); + partial void OnVerificationCodeLengthChanged(); + /// <summary> + /// There are no comments for Property ConsumerId in the schema. + /// </summary> + [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute(EntityKeyProperty=true, IsNullable=false)] + [global::System.Runtime.Serialization.DataMemberAttribute()] + public int ConsumerId + { + get + { + return this._ConsumerId; + } + set + { + this.OnConsumerIdChanging(value); + this.ReportPropertyChanging("ConsumerId"); + this._ConsumerId = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value); + this.ReportPropertyChanged("ConsumerId"); + this.OnConsumerIdChanged(); + } + } + private int _ConsumerId; + partial void OnConsumerIdChanging(int value); + partial void OnConsumerIdChanged(); + /// <summary> + /// There are no comments for Property Name in the schema. + /// </summary> + [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute()] + [global::System.Runtime.Serialization.DataMemberAttribute()] + public string Name + { + get + { + return this._Name; + } + set + { + this.OnNameChanging(value); + this.ReportPropertyChanging("Name"); + this._Name = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value, true); + this.ReportPropertyChanged("Name"); + this.OnNameChanged(); + } + } + private string _Name; + partial void OnNameChanging(string value); + partial void OnNameChanged(); + /// <summary> + /// There are no comments for IssuedToken in the schema. + /// </summary> + [global::System.Data.Objects.DataClasses.EdmRelationshipNavigationPropertyAttribute("DatabaseModel", "FK_IssuedToken_Consumer", "IssuedTokens")] + [global::System.Xml.Serialization.XmlIgnoreAttribute()] + [global::System.Xml.Serialization.SoapIgnoreAttribute()] + [global::System.Runtime.Serialization.DataMemberAttribute()] + public global::System.Data.Objects.DataClasses.EntityCollection<IssuedToken> IssuedToken + { + get + { + return ((global::System.Data.Objects.DataClasses.IEntityWithRelationships)(this)).RelationshipManager.GetRelatedCollection<IssuedToken>("DatabaseModel.FK_IssuedToken_Consumer", "IssuedTokens"); + } + set + { + if ((value != null)) + { + ((global::System.Data.Objects.DataClasses.IEntityWithRelationships)(this)).RelationshipManager.InitializeRelatedCollection<IssuedToken>("DatabaseModel.FK_IssuedToken_Consumer", "IssuedTokens", value); + } + } + } + } + /// <summary> + /// There are no comments for DatabaseModel.IssuedToken in the schema. + /// </summary> + /// <KeyProperties> + /// TokenId + /// </KeyProperties> + [global::System.Data.Objects.DataClasses.EdmEntityTypeAttribute(NamespaceName="DatabaseModel", Name="IssuedToken")] + [global::System.Runtime.Serialization.DataContractAttribute(IsReference=true)] + [global::System.Serializable()] + [global::System.Runtime.Serialization.KnownTypeAttribute(typeof(global::WebFormsRelyingParty.IssuedRequestToken))] + [global::System.Runtime.Serialization.KnownTypeAttribute(typeof(global::WebFormsRelyingParty.IssuedAccessToken))] + public abstract partial class IssuedToken : global::System.Data.Objects.DataClasses.EntityObject + { + /// <summary> + /// There are no comments for Property TokenId in the schema. + /// </summary> + [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute(EntityKeyProperty=true, IsNullable=false)] + [global::System.Runtime.Serialization.DataMemberAttribute()] + public int TokenId + { + get + { + return this._TokenId; + } + set + { + this.OnTokenIdChanging(value); + this.ReportPropertyChanging("TokenId"); + this._TokenId = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value); + this.ReportPropertyChanged("TokenId"); + this.OnTokenIdChanged(); + } + } + private int _TokenId; + partial void OnTokenIdChanging(int value); + partial void OnTokenIdChanged(); + /// <summary> + /// There are no comments for Property Token in the schema. + /// </summary> + [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute(IsNullable=false)] + [global::System.Runtime.Serialization.DataMemberAttribute()] + public string Token + { + get + { + return this._Token; + } + set + { + this.OnTokenChanging(value); + this.ReportPropertyChanging("Token"); + this._Token = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value, false); + this.ReportPropertyChanged("Token"); + this.OnTokenChanged(); + } + } + private string _Token; + partial void OnTokenChanging(string value); + partial void OnTokenChanged(); + /// <summary> + /// There are no comments for Property TokenSecret in the schema. + /// </summary> + [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute(IsNullable=false)] + [global::System.Runtime.Serialization.DataMemberAttribute()] + public string TokenSecret + { + get + { + return this._TokenSecret; + } + set + { + this.OnTokenSecretChanging(value); + this.ReportPropertyChanging("TokenSecret"); + this._TokenSecret = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value, false); + this.ReportPropertyChanged("TokenSecret"); + this.OnTokenSecretChanged(); + } + } + private string _TokenSecret; + partial void OnTokenSecretChanging(string value); + partial void OnTokenSecretChanged(); + /// <summary> + /// There are no comments for Property CreatedOn in the schema. + /// </summary> + [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute(IsNullable=false)] + [global::System.Runtime.Serialization.DataMemberAttribute()] + public global::System.DateTime CreatedOn + { + get + { + return this._CreatedOn; + } + set + { + this.OnCreatedOnChanging(value); + this.ReportPropertyChanging("CreatedOn"); + this._CreatedOn = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value); + this.ReportPropertyChanged("CreatedOn"); + this.OnCreatedOnChanged(); + } + } + private global::System.DateTime _CreatedOn; + partial void OnCreatedOnChanging(global::System.DateTime value); + partial void OnCreatedOnChanged(); + /// <summary> + /// There are no comments for Property Scope in the schema. + /// </summary> + [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute()] + [global::System.Runtime.Serialization.DataMemberAttribute()] + public string Scope + { + get + { + return this._Scope; + } + set + { + this.OnScopeChanging(value); + this.ReportPropertyChanging("Scope"); + this._Scope = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value, true); + this.ReportPropertyChanged("Scope"); + this.OnScopeChanged(); + } + } + private string _Scope; + partial void OnScopeChanging(string value); + partial void OnScopeChanged(); + /// <summary> + /// There are no comments for Consumer in the schema. + /// </summary> + [global::System.Data.Objects.DataClasses.EdmRelationshipNavigationPropertyAttribute("DatabaseModel", "FK_IssuedToken_Consumer", "Consumer")] + [global::System.Xml.Serialization.XmlIgnoreAttribute()] + [global::System.Xml.Serialization.SoapIgnoreAttribute()] + [global::System.Runtime.Serialization.DataMemberAttribute()] + public Consumer Consumer + { + get + { + return ((global::System.Data.Objects.DataClasses.IEntityWithRelationships)(this)).RelationshipManager.GetRelatedReference<Consumer>("DatabaseModel.FK_IssuedToken_Consumer", "Consumer").Value; + } + set + { + ((global::System.Data.Objects.DataClasses.IEntityWithRelationships)(this)).RelationshipManager.GetRelatedReference<Consumer>("DatabaseModel.FK_IssuedToken_Consumer", "Consumer").Value = value; + } + } + /// <summary> + /// There are no comments for Consumer in the schema. + /// </summary> + [global::System.ComponentModel.BrowsableAttribute(false)] + [global::System.Runtime.Serialization.DataMemberAttribute()] + public global::System.Data.Objects.DataClasses.EntityReference<Consumer> ConsumerReference + { + get + { + return ((global::System.Data.Objects.DataClasses.IEntityWithRelationships)(this)).RelationshipManager.GetRelatedReference<Consumer>("DatabaseModel.FK_IssuedToken_Consumer", "Consumer"); + } + set + { + if ((value != null)) + { + ((global::System.Data.Objects.DataClasses.IEntityWithRelationships)(this)).RelationshipManager.InitializeRelatedReference<Consumer>("DatabaseModel.FK_IssuedToken_Consumer", "Consumer", value); + } + } + } + /// <summary> + /// There are no comments for User in the schema. + /// </summary> + [global::System.Data.Objects.DataClasses.EdmRelationshipNavigationPropertyAttribute("DatabaseModel", "FK_IssuedToken_User", "User")] + [global::System.Xml.Serialization.XmlIgnoreAttribute()] + [global::System.Xml.Serialization.SoapIgnoreAttribute()] + [global::System.Runtime.Serialization.DataMemberAttribute()] + public User User + { + get + { + return ((global::System.Data.Objects.DataClasses.IEntityWithRelationships)(this)).RelationshipManager.GetRelatedReference<User>("DatabaseModel.FK_IssuedToken_User", "User").Value; + } + set + { + ((global::System.Data.Objects.DataClasses.IEntityWithRelationships)(this)).RelationshipManager.GetRelatedReference<User>("DatabaseModel.FK_IssuedToken_User", "User").Value = value; + } + } + /// <summary> + /// There are no comments for User in the schema. + /// </summary> + [global::System.ComponentModel.BrowsableAttribute(false)] + [global::System.Runtime.Serialization.DataMemberAttribute()] + public global::System.Data.Objects.DataClasses.EntityReference<User> UserReference + { + get + { + return ((global::System.Data.Objects.DataClasses.IEntityWithRelationships)(this)).RelationshipManager.GetRelatedReference<User>("DatabaseModel.FK_IssuedToken_User", "User"); + } + set + { + if ((value != null)) + { + ((global::System.Data.Objects.DataClasses.IEntityWithRelationships)(this)).RelationshipManager.InitializeRelatedReference<User>("DatabaseModel.FK_IssuedToken_User", "User", value); + } + } + } + } + /// <summary> + /// There are no comments for DatabaseModel.IssuedRequestToken in the schema. + /// </summary> + /// <KeyProperties> + /// TokenId + /// </KeyProperties> + [global::System.Data.Objects.DataClasses.EdmEntityTypeAttribute(NamespaceName="DatabaseModel", Name="IssuedRequestToken")] + [global::System.Runtime.Serialization.DataContractAttribute(IsReference=true)] + [global::System.Serializable()] + public partial class IssuedRequestToken : IssuedToken + { + /// <summary> + /// Create a new IssuedRequestToken object. + /// </summary> + /// <param name="tokenId">Initial value of TokenId.</param> + /// <param name="token">Initial value of Token.</param> + /// <param name="tokenSecret">Initial value of TokenSecret.</param> + /// <param name="createdOn">Initial value of CreatedOn.</param> + public static IssuedRequestToken CreateIssuedRequestToken(int tokenId, string token, string tokenSecret, global::System.DateTime createdOn) + { + IssuedRequestToken issuedRequestToken = new IssuedRequestToken(); + issuedRequestToken.TokenId = tokenId; + issuedRequestToken.Token = token; + issuedRequestToken.TokenSecret = tokenSecret; + issuedRequestToken.CreatedOn = createdOn; + return issuedRequestToken; + } + /// <summary> + /// There are no comments for Property ConsumerVersionAsString in the schema. + /// </summary> + [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute(IsNullable=false)] + [global::System.Runtime.Serialization.DataMemberAttribute()] + private string ConsumerVersionAsString + { + get + { + return this._ConsumerVersionAsString; + } + set + { + this.OnConsumerVersionAsStringChanging(value); + this.ReportPropertyChanging("ConsumerVersionAsString"); + this._ConsumerVersionAsString = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value, false); + this.ReportPropertyChanged("ConsumerVersionAsString"); + this.OnConsumerVersionAsStringChanged(); + } + } + private string _ConsumerVersionAsString; + partial void OnConsumerVersionAsStringChanging(string value); + partial void OnConsumerVersionAsStringChanged(); + /// <summary> + /// There are no comments for Property VerificationCode in the schema. + /// </summary> + [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute()] + [global::System.Runtime.Serialization.DataMemberAttribute()] + public string VerificationCode + { + get + { + return this._VerificationCode; + } + set + { + this.OnVerificationCodeChanging(value); + this.ReportPropertyChanging("VerificationCode"); + this._VerificationCode = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value, true); + this.ReportPropertyChanged("VerificationCode"); + this.OnVerificationCodeChanged(); + } + } + private string _VerificationCode; + partial void OnVerificationCodeChanging(string value); + partial void OnVerificationCodeChanged(); + /// <summary> + /// There are no comments for Property CallbackAsString in the schema. + /// </summary> + [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute()] + [global::System.Runtime.Serialization.DataMemberAttribute()] + private string CallbackAsString + { + get + { + return this._CallbackAsString; + } + set + { + this.OnCallbackAsStringChanging(value); + this.ReportPropertyChanging("CallbackAsString"); + this._CallbackAsString = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value, true); + this.ReportPropertyChanged("CallbackAsString"); + this.OnCallbackAsStringChanged(); + } + } + private string _CallbackAsString; + partial void OnCallbackAsStringChanging(string value); + partial void OnCallbackAsStringChanged(); + } + /// <summary> + /// There are no comments for DatabaseModel.IssuedAccessToken in the schema. + /// </summary> + /// <KeyProperties> + /// TokenId + /// </KeyProperties> + [global::System.Data.Objects.DataClasses.EdmEntityTypeAttribute(NamespaceName="DatabaseModel", Name="IssuedAccessToken")] + [global::System.Runtime.Serialization.DataContractAttribute(IsReference=true)] + [global::System.Serializable()] + public partial class IssuedAccessToken : IssuedToken + { + /// <summary> + /// Create a new IssuedAccessToken object. + /// </summary> + /// <param name="tokenId">Initial value of TokenId.</param> + /// <param name="token">Initial value of Token.</param> + /// <param name="tokenSecret">Initial value of TokenSecret.</param> + /// <param name="createdOn">Initial value of CreatedOn.</param> + public static IssuedAccessToken CreateIssuedAccessToken(int tokenId, string token, string tokenSecret, global::System.DateTime createdOn) + { + IssuedAccessToken issuedAccessToken = new IssuedAccessToken(); + issuedAccessToken.TokenId = tokenId; + issuedAccessToken.Token = token; + issuedAccessToken.TokenSecret = tokenSecret; + issuedAccessToken.CreatedOn = createdOn; + return issuedAccessToken; + } + /// <summary> + /// There are no comments for Property ExpirationDate in the schema. + /// </summary> + [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute()] + [global::System.Runtime.Serialization.DataMemberAttribute()] + public global::System.Nullable<global::System.DateTime> ExpirationDate + { + get + { + return this._ExpirationDate; + } + set + { + this.OnExpirationDateChanging(value); + this.ReportPropertyChanging("ExpirationDate"); + this._ExpirationDate = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value); + this.ReportPropertyChanged("ExpirationDate"); + this.OnExpirationDateChanged(); + } + } + private global::System.Nullable<global::System.DateTime> _ExpirationDate; + partial void OnExpirationDateChanging(global::System.Nullable<global::System.DateTime> value); + partial void OnExpirationDateChanged(); } } diff --git a/projecttemplates/WebFormsRelyingParty/Model.IssuedAccessToken.cs b/projecttemplates/WebFormsRelyingParty/Model.IssuedAccessToken.cs new file mode 100644 index 0000000..ab064c3 --- /dev/null +++ b/projecttemplates/WebFormsRelyingParty/Model.IssuedAccessToken.cs @@ -0,0 +1,58 @@ +//----------------------------------------------------------------------- +// <copyright file="Model.IssuedAccessToken.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace WebFormsRelyingParty { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Web; + using DotNetOpenAuth.OAuth.ChannelElements; + + public partial class IssuedAccessToken : IServiceProviderAccessToken { + /// <summary> + /// Gets the roles that the OAuth principal should belong to. + /// </summary> + /// <value> + /// The roles that the user belongs to, or a subset of these according to the rights + /// granted when the user authorized the request token. + /// </value> + string[] IServiceProviderAccessToken.Roles { + get { + List<string> roles = new List<string>(); + + // Include the roles the user who authorized this OAuth token has. + roles.AddRange(this.User.Roles.Select(r => r.Name)); + + // Always add an extra role to indicate this is an OAuth-authorized request. + // This allows us to deny access to account management pages to OAuth requests. + roles.Add("delegated"); + + return roles.ToArray(); + } + } + + /// <summary> + /// Gets the username of the principal that will be impersonated by this access token. + /// </summary> + /// <value> + /// The name of the user who authorized the OAuth request token originally. + /// </value> + string IServiceProviderAccessToken.Username { + get { + // We don't really have the concept of a single username, but we + // can use any of the authentication tokens instead since that + // is what the rest of the web site expects. + if (!this.UserReference.IsLoaded) { + this.UserReference.Load(); + } + if (!this.User.AuthenticationTokens.IsLoaded) { + this.User.AuthenticationTokens.Load(); + } + return this.User.AuthenticationTokens.First().ClaimedIdentifier; + } + } + } +} diff --git a/projecttemplates/WebFormsRelyingParty/Model.IssuedRequestToken.cs b/projecttemplates/WebFormsRelyingParty/Model.IssuedRequestToken.cs new file mode 100644 index 0000000..1352e54 --- /dev/null +++ b/projecttemplates/WebFormsRelyingParty/Model.IssuedRequestToken.cs @@ -0,0 +1,56 @@ +//----------------------------------------------------------------------- +// <copyright file="Model.IssuedRequestToken.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace WebFormsRelyingParty { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Web; + using DotNetOpenAuth.OAuth.ChannelElements; + + public partial class IssuedRequestToken : IServiceProviderRequestToken { + /// <summary> + /// Gets or sets the callback associated specifically with this token, if any. + /// </summary> + /// <value> + /// The callback URI; or <c>null</c> if no callback was specifically assigned to this token. + /// </value> + public Uri Callback { + get { return this.CallbackAsString != null ? new Uri(this.CallbackAsString) : null; } + set { this.CallbackAsString = value != null ? value.AbsoluteUri : null; } + } + + /// <summary> + /// Gets or sets the version of the Consumer that requested this token. + /// </summary> + /// <remarks> + /// This property is used to determine whether a <see cref="VerificationCode"/> must be + /// generated when the user authorizes the Consumer or not. + /// </remarks> + Version IServiceProviderRequestToken.ConsumerVersion { + get { return this.ConsumerVersionAsString != null ? new Version(this.ConsumerVersionAsString) : null; } + set { this.ConsumerVersionAsString = value != null ? value.ToString() : null; } + } + + /// <summary> + /// Gets the consumer key that requested this token. + /// </summary> + string IServiceProviderRequestToken.ConsumerKey { + get { return this.Consumer.ConsumerKey; } + } + + /// <summary> + /// Authorizes this request token to allow exchange for an access token. + /// </summary> + /// <remarks> + /// Call this method when the user has completed web-based authorization. + /// </remarks> + public void Authorize() { + this.User = Global.LoggedInUser; + Global.DataContext.SaveChanges(); + } + } +} diff --git a/projecttemplates/WebFormsRelyingParty/Model.User.cs b/projecttemplates/WebFormsRelyingParty/Model.User.cs index b2ea2f4..1493603 100644 --- a/projecttemplates/WebFormsRelyingParty/Model.User.cs +++ b/projecttemplates/WebFormsRelyingParty/Model.User.cs @@ -1,4 +1,10 @@ -namespace WebFormsRelyingParty { +//----------------------------------------------------------------------- +// <copyright file="Model.User.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace WebFormsRelyingParty { using System; using System.Collections.Generic; using System.Linq; diff --git a/projecttemplates/WebFormsRelyingParty/Model.edmx b/projecttemplates/WebFormsRelyingParty/Model.edmx index af72a25..f37aa6c 100644 --- a/projecttemplates/WebFormsRelyingParty/Model.edmx +++ b/projecttemplates/WebFormsRelyingParty/Model.edmx @@ -7,6 +7,8 @@ <Schema Namespace="DatabaseModel.Store" Alias="Self" Provider="System.Data.SqlClient" ProviderManifestToken="2005" xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator" xmlns="http://schemas.microsoft.com/ado/2006/04/edm/ssdl"> <EntityContainer Name="DatabaseModelStoreContainer"> <EntitySet Name="AuthenticationToken" EntityType="DatabaseModel.Store.AuthenticationToken" store:Type="Tables" Schema="dbo" /> + <EntitySet Name="Consumer" EntityType="DatabaseModel.Store.Consumer" store:Type="Tables" Schema="dbo" /> + <EntitySet Name="IssuedToken" EntityType="DatabaseModel.Store.IssuedToken" store:Type="Tables" Schema="dbo" /> <EntitySet Name="Role" EntityType="DatabaseModel.Store.Role" store:Type="Tables" Schema="dbo" /> <EntitySet Name="User" EntityType="DatabaseModel.Store.User" store:Type="Tables" Schema="dbo" /> <EntitySet Name="UserRole" EntityType="DatabaseModel.Store.UserRole" store:Type="Tables" Schema="dbo" /> @@ -14,6 +16,14 @@ <End Role="User" EntitySet="User" /> <End Role="AuthenticationToken" EntitySet="AuthenticationToken" /> </AssociationSet> + <AssociationSet Name="FK_IssuedToken_Consumer" Association="DatabaseModel.Store.FK_IssuedToken_Consumer"> + <End Role="Consumer" EntitySet="Consumer" /> + <End Role="IssuedToken" EntitySet="IssuedToken" /> + </AssociationSet> + <AssociationSet Name="FK_IssuedToken_User" Association="DatabaseModel.Store.FK_IssuedToken_User"> + <End Role="User" EntitySet="User" /> + <End Role="IssuedToken" EntitySet="IssuedToken" /> + </AssociationSet> <AssociationSet Name="FK_UserRole_Role" Association="DatabaseModel.Store.FK_UserRole_Role"> <End Role="Role" EntitySet="Role" /> <End Role="UserRole" EntitySet="UserRole" /> @@ -32,6 +42,36 @@ <Property Name="OpenIdClaimedIdentifier" Type="nvarchar" Nullable="false" MaxLength="250" /> <Property Name="OpenIdFriendlyIdentifier" Type="nvarchar" MaxLength="250" /> </EntityType> + <EntityType Name="Consumer"> + <Key> + <PropertyRef Name="ConsumerId" /> + </Key> + <Property Name="ConsumerId" Type="int" Nullable="false" StoreGeneratedPattern="Identity" /> + <Property Name="ConsumerKey" Type="nvarchar" Nullable="false" MaxLength="255" /> + <Property Name="ConsumerSecret" Type="nvarchar" MaxLength="255" /> + <Property Name="X509Certificate" Type="image" /> + <Property Name="Callback" Type="nvarchar" MaxLength="2048" /> + <Property Name="VerificationCodeFormat" Type="int" Nullable="false" /> + <Property Name="VerificationCodeLength" Type="int" Nullable="false" /> + <Property Name="Name" Type="nvarchar" MaxLength="50" /> + </EntityType> + <EntityType Name="IssuedToken"> + <Key> + <PropertyRef Name="TokenId" /> + </Key> + <Property Name="TokenId" Type="int" Nullable="false" StoreGeneratedPattern="Identity" /> + <Property Name="ConsumerId" Type="int" Nullable="false" /> + <Property Name="UserId" Type="int" /> + <Property Name="Token" Type="nvarchar" Nullable="false" MaxLength="255" /> + <Property Name="TokenSecret" Type="nvarchar" Nullable="false" MaxLength="255" /> + <Property Name="CreatedOn" Type="datetime" Nullable="false" /> + <Property Name="Callback" Type="nvarchar" MaxLength="2048" /> + <Property Name="VerificationCode" Type="nvarchar" MaxLength="255" /> + <Property Name="ConsumerVersion" Type="varchar" MaxLength="10" /> + <Property Name="ExpirationDate" Type="datetime" /> + <Property Name="IsAccessToken" Type="bit" Nullable="false" /> + <Property Name="Scope" Type="nvarchar" MaxLength="255" /> + </EntityType> <EntityType Name="Role"> <Key> <PropertyRef Name="Id" /> @@ -71,6 +111,34 @@ </Dependent> </ReferentialConstraint> </Association> + <Association Name="FK_IssuedToken_Consumer"> + <End Role="Consumer" Type="DatabaseModel.Store.Consumer" Multiplicity="1"> + <OnDelete Action="Cascade" /> + </End> + <End Role="IssuedToken" Type="DatabaseModel.Store.IssuedToken" Multiplicity="*" /> + <ReferentialConstraint> + <Principal Role="Consumer"> + <PropertyRef Name="ConsumerId" /> + </Principal> + <Dependent Role="IssuedToken"> + <PropertyRef Name="ConsumerId" /> + </Dependent> + </ReferentialConstraint> + </Association> + <Association Name="FK_IssuedToken_User"> + <End Role="User" Type="DatabaseModel.Store.User" Multiplicity="0..1"> + <OnDelete Action="Cascade" /> + </End> + <End Role="IssuedToken" Type="DatabaseModel.Store.IssuedToken" Multiplicity="*" /> + <ReferentialConstraint> + <Principal Role="User"> + <PropertyRef Name="Id" /> + </Principal> + <Dependent Role="IssuedToken"> + <PropertyRef Name="UserId" /> + </Dependent> + </ReferentialConstraint> + </Association> <Association Name="FK_UserRole_Role"> <End Role="Role" Type="DatabaseModel.Store.Role" Multiplicity="1"> <OnDelete Action="Cascade" /> @@ -114,6 +182,14 @@ <AssociationSet Name="UserAuthenticationToken" Association="DatabaseModel.UserAuthenticationToken"> <End Role="User" EntitySet="User" /> <End Role="AuthenticationToken" EntitySet="AuthenticationToken" /></AssociationSet> + <EntitySet Name="Consumer" EntityType="DatabaseModel.Consumer" /> + <EntitySet Name="IssuedToken" EntityType="DatabaseModel.IssuedToken" /> + <AssociationSet Name="FK_IssuedToken_Consumer" Association="DatabaseModel.FK_IssuedToken_Consumer"> + <End Role="Consumer" EntitySet="Consumer" /> + <End Role="IssuedTokens" EntitySet="IssuedToken" /></AssociationSet> + <AssociationSet Name="FK_IssuedToken_User" Association="DatabaseModel.FK_IssuedToken_User"> + <End Role="User" EntitySet="User" /> + <End Role="IssuedTokens" EntitySet="IssuedToken" /></AssociationSet> </EntityContainer> <EntityType Name="AuthenticationToken" Abstract="false"> <Key> @@ -145,14 +221,48 @@ <Property Name="EmailAddressVerified" Type="Boolean" Nullable="false" > <Documentation> <Summary>A value indicating whether the email address has been verified as actually owned by this user.</Summary></Documentation></Property> - </EntityType> + <NavigationProperty Name="IssuedToken" Relationship="DatabaseModel.FK_IssuedToken_User" FromRole="User" ToRole="IssuedTokens" /></EntityType> <Association Name="UserRole"> <End Role="Role" Type="DatabaseModel.Role" Multiplicity="*" /> <End Role="User" Type="DatabaseModel.User" Multiplicity="*" /> </Association> <Association Name="UserAuthenticationToken"> <End Type="DatabaseModel.User" Role="User" Multiplicity="1" /> - <End Type="DatabaseModel.AuthenticationToken" Role="AuthenticationToken" Multiplicity="*" /></Association></Schema> + <End Type="DatabaseModel.AuthenticationToken" Role="AuthenticationToken" Multiplicity="*" /></Association> + <EntityType Name="Consumer"> + <Key> + <PropertyRef Name="ConsumerId" /></Key> + <Property Name="ConsumerKey" Type="String" Nullable="false" /> + <Property Name="ConsumerSecret" Type="String" Nullable="true" /> + <Property Name="X509CertificateAsBinary" Type="Binary" Nullable="true" a:SetterAccess="Private" xmlns:a="http://schemas.microsoft.com/ado/2006/04/codegeneration" a:GetterAccess="Private" /> + <Property Name="CallbackAsString" Type="String" Nullable="true" /> + <Property Name="VerificationCodeFormatAsInt" Type="Int32" Nullable="false" a:GetterAccess="Private" xmlns:a="http://schemas.microsoft.com/ado/2006/04/codegeneration" a:SetterAccess="Private" /> + <Property Name="VerificationCodeLength" Type="Int32" Nullable="false" /> + <Property Name="ConsumerId" Type="Int32" Nullable="false" /> + <NavigationProperty Name="IssuedToken" Relationship="DatabaseModel.FK_IssuedToken_Consumer" FromRole="Consumer" ToRole="IssuedTokens" /> + <Property Name="Name" Type="String" Nullable="true" /></EntityType> + <EntityType Name="IssuedToken" Abstract="true"> + <Key> + <PropertyRef Name="TokenId" /></Key> + <Property Name="TokenId" Type="Int32" Nullable="false" /> + <Property Name="Token" Type="String" Nullable="false" /> + <Property Name="TokenSecret" Type="String" Nullable="false" /> + <Property Name="CreatedOn" Type="DateTime" Nullable="false" /> + <NavigationProperty Name="Consumer" Relationship="DatabaseModel.FK_IssuedToken_Consumer" FromRole="IssuedTokens" ToRole="Consumer" /> + <NavigationProperty Name="User" Relationship="DatabaseModel.FK_IssuedToken_User" FromRole="IssuedTokens" ToRole="User" /> + <Property Name="Scope" Type="String" Nullable="true" /></EntityType> + <Association Name="FK_IssuedToken_Consumer"> + <End Type="DatabaseModel.Consumer" Role="Consumer" Multiplicity="1" /> + <End Type="DatabaseModel.IssuedToken" Role="IssuedTokens" Multiplicity="*" /></Association> + <Association Name="FK_IssuedToken_User"> + <End Type="DatabaseModel.User" Role="User" Multiplicity="0..1" /> + <End Type="DatabaseModel.IssuedToken" Role="IssuedTokens" Multiplicity="*" /></Association> + <EntityType Name="IssuedRequestToken" BaseType="DatabaseModel.IssuedToken"> + <Property Name="ConsumerVersionAsString" Type="String" Nullable="false" a:GetterAccess="Private" xmlns:a="http://schemas.microsoft.com/ado/2006/04/codegeneration" a:SetterAccess="Private" /> + <Property Name="VerificationCode" Type="String" Nullable="true" /> + <Property Name="CallbackAsString" Type="String" Nullable="true" a:GetterAccess="Private" xmlns:a="http://schemas.microsoft.com/ado/2006/04/codegeneration" a:SetterAccess="Private" /></EntityType> + <EntityType Name="IssuedAccessToken" BaseType="DatabaseModel.IssuedToken"> + <Property Name="ExpirationDate" Type="DateTime" Nullable="true" /></EntityType></Schema> </edmx:ConceptualModels> <!-- C-S mapping content --> <edmx:Mappings> @@ -198,7 +308,48 @@ <ScalarProperty Name="Id" ColumnName="Id" /></EndProperty> <EndProperty Name="User"> <ScalarProperty Name="Id" ColumnName="UserId" /></EndProperty></AssociationSetMapping> - </EntityContainerMapping> + <EntitySetMapping Name="Consumer"> + <EntityTypeMapping TypeName="IsTypeOf(DatabaseModel.Consumer)"> + <MappingFragment StoreEntitySet="Consumer"> + <ScalarProperty Name="Name" ColumnName="Name" /> + <ScalarProperty Name="ConsumerId" ColumnName="ConsumerId" /> + <ScalarProperty Name="VerificationCodeLength" ColumnName="VerificationCodeLength" /> + <ScalarProperty Name="VerificationCodeFormatAsInt" ColumnName="VerificationCodeFormat" /> + <ScalarProperty Name="CallbackAsString" ColumnName="Callback" /> + <ScalarProperty Name="X509CertificateAsBinary" ColumnName="X509Certificate" /> + <ScalarProperty Name="ConsumerSecret" ColumnName="ConsumerSecret" /> + <ScalarProperty Name="ConsumerKey" ColumnName="ConsumerKey" /></MappingFragment></EntityTypeMapping></EntitySetMapping> + <EntitySetMapping Name="IssuedToken"> + <EntityTypeMapping TypeName="IsTypeOf(DatabaseModel.IssuedToken)"> + <MappingFragment StoreEntitySet="IssuedToken"> + <ScalarProperty Name="Scope" ColumnName="Scope" /> + <ScalarProperty Name="CreatedOn" ColumnName="CreatedOn" /> + <ScalarProperty Name="TokenSecret" ColumnName="TokenSecret" /> + <ScalarProperty Name="Token" ColumnName="Token" /> + <ScalarProperty Name="TokenId" ColumnName="TokenId" /></MappingFragment></EntityTypeMapping> + <EntityTypeMapping TypeName="IsTypeOf(DatabaseModel.IssuedRequestToken)"> + <MappingFragment StoreEntitySet="IssuedToken" > + <ScalarProperty Name="CallbackAsString" ColumnName="Callback" /> + <ScalarProperty Name="ConsumerVersionAsString" ColumnName="ConsumerVersion" /> + <ScalarProperty Name="VerificationCode" ColumnName="VerificationCode" /> + <ScalarProperty Name="TokenId" ColumnName="TokenId" /> + <Condition ColumnName="IsAccessToken" Value="0" /></MappingFragment></EntityTypeMapping> + <EntityTypeMapping TypeName="IsTypeOf(DatabaseModel.IssuedAccessToken)"> + <MappingFragment StoreEntitySet="IssuedToken" > + <ScalarProperty Name="ExpirationDate" ColumnName="ExpirationDate" /> + <ScalarProperty Name="TokenId" ColumnName="TokenId" /> + <Condition ColumnName="IsAccessToken" Value="1" /></MappingFragment></EntityTypeMapping></EntitySetMapping> + <AssociationSetMapping Name="FK_IssuedToken_Consumer" TypeName="DatabaseModel.FK_IssuedToken_Consumer" StoreEntitySet="IssuedToken"> + <EndProperty Name="IssuedTokens"> + <ScalarProperty Name="TokenId" ColumnName="TokenId" /></EndProperty> + <EndProperty Name="Consumer"> + <ScalarProperty Name="ConsumerId" ColumnName="ConsumerId" /></EndProperty></AssociationSetMapping> + <AssociationSetMapping Name="FK_IssuedToken_User" TypeName="DatabaseModel.FK_IssuedToken_User" StoreEntitySet="IssuedToken"> + <EndProperty Name="IssuedTokens"> + <ScalarProperty Name="TokenId" ColumnName="TokenId" /></EndProperty> + <EndProperty Name="User"> + <ScalarProperty Name="Id" ColumnName="UserId" /></EndProperty> + <Condition ColumnName="UserId" IsNull="false" /></AssociationSetMapping></EntityContainerMapping> </Mapping> </edmx:Mappings> </edmx:Runtime> @@ -228,6 +379,26 @@ <ConnectorPoint PointX="6.5625" PointY="2.9129850260416665" /></InheritanceConnector> <AssociationConnector Association="DatabaseModel.UserAuthenticationToken"> <ConnectorPoint PointX="4.625" PointY="2.0189925130208337" /> - <ConnectorPoint PointX="5.25" PointY="2.0189925130208337" /></AssociationConnector></Diagram></edmx:Diagrams> + <ConnectorPoint PointX="5.25" PointY="2.0189925130208337" /></AssociationConnector> + <EntityTypeShape EntityType="DatabaseModel.Consumer" Width="2.125" PointX="0.5" PointY="3.625" Height="2.1725878906249996" /> + <EntityTypeShape EntityType="DatabaseModel.IssuedToken" Width="2" PointX="4.5" PointY="3.625" Height="2.1725878906249996" /> + <AssociationConnector Association="DatabaseModel.FK_IssuedToken_Consumer" ManuallyRouted="false" > + <ConnectorPoint PointX="2.625" PointY="5.359375" /> + <ConnectorPoint PointX="4.5" PointY="5.359375" /> + </AssociationConnector> + <AssociationConnector Association="DatabaseModel.FK_IssuedToken_User" > + <ConnectorPoint PointX="3.6874995" PointY="3.4321907552083331" /> + <ConnectorPoint PointX="3.6874995" PointY="4.005208333333333" /> + <ConnectorPoint PointX="4.5" PointY="4.005208333333333" /> + </AssociationConnector> + <EntityTypeShape EntityType="DatabaseModel.IssuedRequestToken" Width="2" PointX="4.25" PointY="6.25" Height="1.5956835937499996" /> + <EntityTypeShape EntityType="DatabaseModel.IssuedAccessToken" Width="1.625" PointX="6.5" PointY="6.25" Height="1.2110807291666657" /> + <InheritanceConnector EntityType="DatabaseModel.IssuedRequestToken"> + <ConnectorPoint PointX="5.375" PointY="5.797587890625" /> + <ConnectorPoint PointX="5.375" PointY="6.25" /></InheritanceConnector> + <InheritanceConnector EntityType="DatabaseModel.IssuedAccessToken"> + <ConnectorPoint PointX="6.5" PointY="4.7112939453125" /> + <ConnectorPoint PointX="7.34375" PointY="4.7112939453125" /> + <ConnectorPoint PointX="7.34375" PointY="6.25" /></InheritanceConnector></Diagram></edmx:Diagrams> </edmx:Designer> </edmx:Edmx>
\ No newline at end of file diff --git a/projecttemplates/WebFormsRelyingParty/OAuth.ashx b/projecttemplates/WebFormsRelyingParty/OAuth.ashx new file mode 100644 index 0000000..6176757 --- /dev/null +++ b/projecttemplates/WebFormsRelyingParty/OAuth.ashx @@ -0,0 +1 @@ +<%@ WebHandler Language="C#" CodeBehind="OAuth.ashx.cs" Class="WebFormsRelyingParty.OAuth" %> diff --git a/projecttemplates/WebFormsRelyingParty/OAuth.ashx.cs b/projecttemplates/WebFormsRelyingParty/OAuth.ashx.cs new file mode 100644 index 0000000..274b5da --- /dev/null +++ b/projecttemplates/WebFormsRelyingParty/OAuth.ashx.cs @@ -0,0 +1,66 @@ +//----------------------------------------------------------------------- +// <copyright file="OAuth.ashx.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace WebFormsRelyingParty { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Web; + using System.Web.SessionState; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuth; + using DotNetOpenAuth.OAuth.Messages; + using WebFormsRelyingParty.Code; + + /// <summary> + /// Responds to incoming OAuth Service Provider messages. + /// </summary> + public class OAuth : IHttpHandler, IRequiresSessionState { + /// <summary> + /// Initializes a new instance of the <see cref="OAuth"/> class. + /// </summary> + public OAuth() { + } + + /// <summary> + /// Gets a value indicating whether another request can use the <see cref="T:System.Web.IHttpHandler"/> instance. + /// </summary> + /// <returns> + /// true if the <see cref="T:System.Web.IHttpHandler"/> instance is reusable; otherwise, false. + /// </returns> + public bool IsReusable { + get { return true; } + } + + /// <summary> + /// Enables processing of HTTP Web requests by a custom HttpHandler that implements the <see cref="T:System.Web.IHttpHandler"/> interface. + /// </summary> + /// <param name="context">An <see cref="T:System.Web.HttpContext"/> object that provides references to the intrinsic server objects (for example, Request, Response, Session, and Server) used to service HTTP requests.</param> + public void ProcessRequest(HttpContext context) { + var serviceProvider = OAuthServiceProvider.ServiceProvider; + var requestMessage = serviceProvider.ReadRequest(new HttpRequestInfo(context.Request)); + + UnauthorizedTokenRequest unauthorizedTokenRequestMessage; + AuthorizedTokenRequest authorizedTokenRequestMessage; + UserAuthorizationRequest userAuthorizationRequest; + if ((unauthorizedTokenRequestMessage = requestMessage as UnauthorizedTokenRequest) != null) { + var response = serviceProvider.PrepareUnauthorizedTokenMessage(unauthorizedTokenRequestMessage); + serviceProvider.Channel.Send(response); + } else if ((authorizedTokenRequestMessage = requestMessage as AuthorizedTokenRequest) != null) { + var response = serviceProvider.PrepareAccessTokenMessage(authorizedTokenRequestMessage); + serviceProvider.Channel.Send(response); + } else if ((userAuthorizationRequest = requestMessage as UserAuthorizationRequest) != null) { + // This is a browser opening to allow the user to authorize a request token, + // so redirect to the authorization page, which will automatically redirect + // to have the user log in if necessary. + OAuthServiceProvider.PendingAuthorizationRequest = userAuthorizationRequest; + HttpContext.Current.Response.Redirect("~/Members/OAuthAuthorize.aspx"); + } else { + throw new InvalidOperationException(); + } + } + } +} diff --git a/projecttemplates/WebFormsRelyingParty/Web.config b/projecttemplates/WebFormsRelyingParty/Web.config index 3c7aaae..b38a25a 100644 --- a/projecttemplates/WebFormsRelyingParty/Web.config +++ b/projecttemplates/WebFormsRelyingParty/Web.config @@ -34,6 +34,13 @@ </system.net> <!-- this is an optional configuration section where aspects of dotnetopenauth can be customized --> <dotNetOpenAuth> + <messaging> + <untrustedWebRequest> + <whitelistHosts> + <add name="localhost" /> + </whitelistHosts> + </untrustedWebRequest> + </messaging> <openid> <relyingParty> <behaviors> @@ -122,6 +129,7 @@ </httpHandlers> <httpModules> <add name="ScriptModule" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" /> + <add name="OAuthAuthenticationModule" type="WebFormsRelyingParty.Code.OAuthAuthenticationModule" /> </httpModules> <roleManager enabled="true" defaultProvider="Database"> <providers> @@ -146,6 +154,7 @@ <modules> <remove name="ScriptModule" /> <add name="ScriptModule" preCondition="managedHandler" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" /> + <add name="OAuthAuthenticationModule" type="WebFormsRelyingParty.Code.OAuthAuthenticationModule" /> </modules> <handlers> <remove name="WebServiceHandlerFactory-Integrated" /> @@ -169,6 +178,21 @@ </dependentAssembly> </assemblyBinding> </runtime> + <system.serviceModel> + <behaviors> + <serviceBehaviors> + <behavior name="DataApiBehavior"> + <serviceMetadata httpGetEnabled="true" /> + <serviceDebug includeExceptionDetailInFaults="true" /> + <serviceAuthorization serviceAuthorizationManagerType="OAuthAuthorizationManager, __code" principalPermissionMode="Custom" /> + </behavior> + </serviceBehaviors> + </behaviors> + <services> + <!--<service behaviorConfiguration="DataApiBehavior" name="DataApi"> + </service>--> + </services> + </system.serviceModel> <location path="default.aspx"> <system.web> <authorization> diff --git a/projecttemplates/WebFormsRelyingParty/WebFormsRelyingParty.csproj b/projecttemplates/WebFormsRelyingParty/WebFormsRelyingParty.csproj index 43bdef4..4f6cdc6 100644 --- a/projecttemplates/WebFormsRelyingParty/WebFormsRelyingParty.csproj +++ b/projecttemplates/WebFormsRelyingParty/WebFormsRelyingParty.csproj @@ -87,7 +87,25 @@ <Content Include="Web.config" /> </ItemGroup> <ItemGroup> + <Compile Include="Code\OAuthAuthenticationModule.cs" /> + <Compile Include="Code\OAuthAuthorizationManager.cs" /> + <Compile Include="Code\OAuthConsumerTokenManager.cs" /> + <Compile Include="Code\OAuthPrincipalAuthorizationPolicy.cs" /> + <Compile Include="Code\OAuthServiceProvider.cs" /> + <Compile Include="Code\OAuthServiceProviderTokenManager.cs" /> + <Compile Include="Code\OAuthTokenManager.cs" /> <Compile Include="Code\Policies.cs" /> + <Compile Include="Code\Utilities.cs" /> + <Compile Include="Members\OAuthAuthorize.aspx.cs"> + <DependentUpon>OAuthAuthorize.aspx</DependentUpon> + <SubType>ASPXCodeBehind</SubType> + </Compile> + <Compile Include="Members\OAuthAuthorize.aspx.designer.cs"> + <DependentUpon>OAuthAuthorize.aspx</DependentUpon> + </Compile> + <Compile Include="Model.IssuedRequestToken.cs" /> + <Compile Include="Model.IssuedAccessToken.cs" /> + <Compile Include="Model.Consumer.cs" /> <Compile Include="Model.User.cs" /> <Compile Include="LoginFrame.aspx.cs"> <DependentUpon>LoginFrame.aspx</DependentUpon> @@ -154,6 +172,9 @@ <DesignTime>True</DesignTime> <DependentUpon>Model.edmx</DependentUpon> </Compile> + <Compile Include="OAuth.ashx.cs"> + <DependentUpon>OAuth.ashx</DependentUpon> + </Compile> <Compile Include="Properties\AssemblyInfo.cs" /> <EntityDeploy Include="Model.edmx"> <Generator>EntityModelCodeGenerator</Generator> @@ -245,6 +266,9 @@ </ItemGroup> <ItemGroup> <None Include="Admin\CreateDatabase.sql" /> + <Content Include="bin\Microsoft.Contracts.dll" /> + <Content Include="Members\OAuthAuthorize.aspx" /> + <Content Include="OAuth.ashx" /> </ItemGroup> <ItemGroup> <ProjectReference Include="..\..\src\DotNetOpenAuth\DotNetOpenAuth.csproj"> diff --git a/samples/OAuthConsumerWpf/MainWindow.xaml b/samples/OAuthConsumerWpf/MainWindow.xaml index e948bd2..c59175c 100644 --- a/samples/OAuthConsumerWpf/MainWindow.xaml +++ b/samples/OAuthConsumerWpf/MainWindow.xaml @@ -72,5 +72,66 @@ <Label Grid.Row="3" Grid.Column="1" Name="wcfFavoriteSites" /> </Grid> </TabItem> + <TabItem Header="Generic"> + <Grid> + <Grid.RowDefinitions> + <RowDefinition Height="auto" /> + <RowDefinition Height="auto" /> + <RowDefinition Height="auto" /> + <RowDefinition Height="auto" /> + <RowDefinition Height="auto" /> + <RowDefinition Height="auto" /> + <RowDefinition Height="auto" /> + <RowDefinition Height="auto" /> + <RowDefinition Height="*" /> + </Grid.RowDefinitions> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="auto" /> + <ColumnDefinition Width="*" /> + <ColumnDefinition Width="auto" /> + </Grid.ColumnDefinitions> + <Label Grid.Row="0">Request Token URL</Label> + <TextBox Grid.Column="1" x:Name="requestTokenUrlBox" /> + <ComboBox Grid.Column="2" x:Name="requestTokenHttpMethod" SelectedIndex="1"> + <ComboBox.Items> + <ComboBoxItem>GET</ComboBoxItem> + <ComboBoxItem>POST</ComboBoxItem> + </ComboBox.Items> + </ComboBox> + <Label Grid.Row="1">Authorize URL</Label> + <TextBox Grid.Row="1" Grid.Column="1" x:Name="authorizeUrlBox" /> + <Label Grid.Row="1" Grid.Column="2">GET</Label> + <Label Grid.Row="2">Access Token URL</Label> + <TextBox Grid.Row="2" Grid.Column="1" x:Name="accessTokenUrlBox" /> + <ComboBox Grid.Row="2" Grid.Column="2" x:Name="accessTokenHttpMethod" SelectedIndex="1"> + <ComboBox.Items> + <ComboBoxItem>GET</ComboBoxItem> + <ComboBoxItem>POST</ComboBoxItem> + </ComboBox.Items> + </ComboBox> + <Label Grid.Row="3">Resource URL</Label> + <TextBox Grid.Row="3" Grid.Column="1" x:Name="resourceUrlBox" /> + <ComboBox Grid.Row="3" Grid.Column="2" x:Name="resourceHttpMethodList" SelectedIndex="0"> + <ComboBox.Items> + <ComboBoxItem>GET w/ header</ComboBoxItem> + <ComboBoxItem>GET w/ querystring</ComboBoxItem> + <ComboBoxItem>POST</ComboBoxItem> + </ComboBox.Items> + </ComboBox> + <Label Grid.Row="4">Consumer key</Label> + <TextBox Grid.Row="4" Grid.Column="1" x:Name="consumerKeyBox" Grid.ColumnSpan="2"/> + <Label Grid.Row="5">Consumer secret</Label> + <TextBox Grid.Row="5" Grid.Column="1" x:Name="consumerSecretBox" Grid.ColumnSpan="2"/> + <Label Grid.Row="6">OAuth version</Label> + <ComboBox Grid.Row="6" Grid.Column="1" SelectedIndex="1" x:Name="oauthVersion"> + <ComboBox.Items> + <ComboBoxItem>1.0</ComboBoxItem> + <ComboBoxItem>1.0a</ComboBoxItem> + </ComboBox.Items> + </ComboBox> + <Button Grid.Row="7" Grid.Column="1" x:Name="beginButton" Click="beginButton_Click">Begin</Button> + <TextBox Grid.Column="0" Grid.Row="8" Grid.ColumnSpan="3" Name="resultsBox" IsReadOnly="True" /> + </Grid> + </TabItem> </TabControl> </Window> diff --git a/samples/OAuthConsumerWpf/MainWindow.xaml.cs b/samples/OAuthConsumerWpf/MainWindow.xaml.cs index ebbeffc..93d77ea 100644 --- a/samples/OAuthConsumerWpf/MainWindow.xaml.cs +++ b/samples/OAuthConsumerWpf/MainWindow.xaml.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Configuration; + using System.Diagnostics; using System.Linq; using System.Net; using System.Security.Cryptography.X509Certificates; @@ -125,6 +126,7 @@ Authorize auth = new Authorize( this.wcf, (DesktopConsumer consumer, out string requestToken) => consumer.RequestUserAuthorization(requestArgs, null, out requestToken)); + auth.Owner = this; bool? result = auth.ShowDialog(); if (result.HasValue && result.Value) { this.wcfAccessToken = auth.AccessToken; @@ -149,5 +151,52 @@ return predicate(client); } } + + private void beginButton_Click(object sender, RoutedEventArgs e) { + try { + var service = new ServiceProviderDescription { + RequestTokenEndpoint = new MessageReceivingEndpoint(requestTokenUrlBox.Text, requestTokenHttpMethod.SelectedIndex == 0 ? HttpDeliveryMethods.GetRequest : HttpDeliveryMethods.PostRequest), + UserAuthorizationEndpoint = new MessageReceivingEndpoint(authorizeUrlBox.Text, HttpDeliveryMethods.GetRequest), + AccessTokenEndpoint = new MessageReceivingEndpoint(accessTokenUrlBox.Text, accessTokenHttpMethod.SelectedIndex == 0 ? HttpDeliveryMethods.GetRequest : HttpDeliveryMethods.PostRequest), + TamperProtectionElements = new ITamperProtectionChannelBindingElement[] { new HmacSha1SigningBindingElement() }, + ProtocolVersion = oauthVersion.SelectedIndex == 0 ? ProtocolVersion.V10 : ProtocolVersion.V10a, + }; + var tokenManager = new InMemoryTokenManager(); + tokenManager.ConsumerKey = consumerKeyBox.Text; + tokenManager.ConsumerSecret = consumerSecretBox.Text; + + var consumer = new DesktopConsumer(service, tokenManager); + string accessToken; + if (service.ProtocolVersion == ProtocolVersion.V10) { + string requestToken; + Uri authorizeUrl = consumer.RequestUserAuthorization(null, null, out requestToken); + Process.Start(authorizeUrl.AbsoluteUri); + MessageBox.Show("Click OK when you've authorized the app."); + var authorizationResponse = consumer.ProcessUserAuthorization(requestToken); + accessToken = authorizationResponse.AccessToken; + } else { + var authorizePopup = new Authorize( + consumer, + (DesktopConsumer c, out string requestToken) => c.RequestUserAuthorization(null, null, out requestToken)); + authorizePopup.Owner = this; + bool? result = authorizePopup.ShowDialog(); + if (result.HasValue && result.Value) { + accessToken = authorizePopup.AccessToken; + } else { + return; + } + } + HttpDeliveryMethods resourceHttpMethod = resourceHttpMethodList.SelectedIndex < 2 ? HttpDeliveryMethods.GetRequest : HttpDeliveryMethods.PostRequest; + if (resourceHttpMethodList.SelectedIndex == 1) { + resourceHttpMethod |= HttpDeliveryMethods.AuthorizationHeaderRequest; + } + var resourceEndpoint = new MessageReceivingEndpoint(resourceUrlBox.Text, resourceHttpMethod); + using (IncomingWebResponse resourceResponse = consumer.PrepareAuthorizedRequestAndSend(resourceEndpoint, accessToken)) { + resultsBox.Text = resourceResponse.GetResponseReader().ReadToEnd(); + } + } catch (DotNetOpenAuth.Messaging.ProtocolException ex) { + MessageBox.Show(ex.Message); + } + } } } diff --git a/samples/OAuthServiceProvider/App_Code/Global.cs b/samples/OAuthServiceProvider/App_Code/Global.cs index b343dcd..10b3cba 100644 --- a/samples/OAuthServiceProvider/App_Code/Global.cs +++ b/samples/OAuthServiceProvider/App_Code/Global.cs @@ -92,7 +92,15 @@ public class Global : HttpApplication { private void Application_Start(object sender, EventArgs e) { log4net.Config.XmlConfigurator.Configure(); Logger.Info("Sample starting..."); - Constants.WebRootUrl = new Uri(HttpContext.Current.Request.Url, "/"); + string appPath = HttpContext.Current.Request.ApplicationPath; + if (!appPath.EndsWith("/")) { + appPath += "/"; + } + + // This will break in IIS Integrated Pipeline mode, since applications + // start before the first incoming request context is available. + // TODO: fix this. + Constants.WebRootUrl = new Uri(HttpContext.Current.Request.Url, appPath); var tokenManager = new DatabaseTokenManager(); Global.TokenManager = tokenManager; } |