diff options
author | Andrew Arnott <andrewarnott@gmail.com> | 2009-12-30 13:42:16 -0800 |
---|---|---|
committer | Andrew Arnott <andrewarnott@gmail.com> | 2009-12-30 13:42:16 -0800 |
commit | 1b7a82f0bc74373cae8ab66b1cc12195c5553a18 (patch) | |
tree | e5b6f96e027b97eb53f6c41a6472da37dc28feb6 | |
parent | 815ca5309b5af651e8ecf9583c31a2f86d02a1a9 (diff) | |
download | DotNetOpenAuth-1b7a82f0bc74373cae8ab66b1cc12195c5553a18.zip DotNetOpenAuth-1b7a82f0bc74373cae8ab66b1cc12195c5553a18.tar.gz DotNetOpenAuth-1b7a82f0bc74373cae8ab66b1cc12195c5553a18.tar.bz2 |
Added (untested!) OAuth SP support.
9 files changed, 259 insertions, 6 deletions
diff --git a/projecttemplates/MvcRelyingParty/Controllers/AccountController.cs b/projecttemplates/MvcRelyingParty/Controllers/AccountController.cs index cd954b9..cde7bbb 100644 --- a/projecttemplates/MvcRelyingParty/Controllers/AccountController.cs +++ b/projecttemplates/MvcRelyingParty/Controllers/AccountController.cs @@ -9,6 +9,8 @@ using System.Web.Security; using System.Web.UI; using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuth; + using DotNetOpenAuth.OAuth.Messages; using DotNetOpenAuth.OpenId; using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; using DotNetOpenAuth.OpenId.RelyingParty; @@ -152,7 +154,7 @@ [Authorize] public ActionResult Edit() { - return View(GetModel()); + return View(GetAccountInfoModel()); } /// <summary> @@ -175,10 +177,56 @@ Database.LoggedInUser.EmailAddressVerified = false; } - return PartialView("EditFields", GetModel()); + return PartialView("EditFields", GetAccountInfoModel()); } - private static AccountInfoModel GetModel() { + [Authorize] + public ActionResult Authorize() { + if (OAuthServiceProvider.PendingAuthorizationRequest == null) { + return RedirectToAction("Edit"); + } + + var model = new AccountAuthorizeModel { + ConsumerApp = OAuthServiceProvider.PendingAuthorizationConsumer.Name, + IsUnsafeRequest = OAuthServiceProvider.PendingAuthorizationRequest.IsUnsafeRequest, + }; + + return View(model); + } + + [Authorize, AcceptVerbs(HttpVerbs.Post), ValidateAntiForgeryToken] + public ActionResult Authorize(bool isApproved) { + if (isApproved) { + var consumer = OAuthServiceProvider.PendingAuthorizationConsumer; + var tokenManager = OAuthServiceProvider.ServiceProvider.TokenManager; + var pendingRequest = OAuthServiceProvider.PendingAuthorizationRequest; + ITokenContainingMessage requestTokenMessage = pendingRequest; + var requestToken = tokenManager.GetRequestToken(requestTokenMessage.Token); + + var response = OAuthServiceProvider.AuthorizePendingRequestTokenAsWebResponse(); + if (response != null) { + // The consumer provided a callback URL that can take care of everything else. + return response.AsActionResult(); + } + + var model = new AccountAuthorizeModel { + ConsumerApp = consumer.Name, + }; + + if (!pendingRequest.IsUnsafeRequest) { + model.VerificationCode = ServiceProvider.CreateVerificationCode(consumer.VerificationCodeFormat, consumer.VerificationCodeLength); + requestToken.VerificationCode = model.VerificationCode; + tokenManager.UpdateToken(requestToken); + } + + return View("AuthorizeApproved"); + } else { + OAuthServiceProvider.PendingAuthorizationRequest = null; + return View("AuthorizeDenied"); + } + } + + private static AccountInfoModel GetAccountInfoModel() { var model = new AccountInfoModel { FirstName = Database.LoggedInUser.FirstName, LastName = Database.LoggedInUser.LastName, diff --git a/projecttemplates/MvcRelyingParty/Models/AccountAuthorizeModel.cs b/projecttemplates/MvcRelyingParty/Models/AccountAuthorizeModel.cs new file mode 100644 index 0000000..0fbd9f4 --- /dev/null +++ b/projecttemplates/MvcRelyingParty/Models/AccountAuthorizeModel.cs @@ -0,0 +1,14 @@ +namespace MvcRelyingParty.Models { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Web; + + public class AccountAuthorizeModel { + public string ConsumerApp { get; set; } + + public bool IsUnsafeRequest { get; set; } + + public string VerificationCode { get; set; } + } +} diff --git a/projecttemplates/MvcRelyingParty/MvcRelyingParty.csproj b/projecttemplates/MvcRelyingParty/MvcRelyingParty.csproj index ad6f475..ad26174 100644 --- a/projecttemplates/MvcRelyingParty/MvcRelyingParty.csproj +++ b/projecttemplates/MvcRelyingParty/MvcRelyingParty.csproj @@ -74,7 +74,11 @@ <Compile Include="Global.asax.cs"> <DependentUpon>Global.asax</DependentUpon> </Compile> + <Compile Include="Models\AccountAuthorizeModel.cs" /> <Compile Include="Models\AccountInfoModel.cs" /> + <Compile Include="OAuth.ashx.cs"> + <DependentUpon>OAuth.ashx</DependentUpon> + </Compile> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Setup.aspx.cs"> <DependentUpon>Setup.aspx</DependentUpon> @@ -121,6 +125,12 @@ </ProjectReference> </ItemGroup> <ItemGroup> + <Content Include="OAuth.ashx" /> + <Content Include="Views\Account\Authorize.aspx" /> + <Content Include="Views\Account\AuthorizeApproved.aspx" /> + <Content Include="Views\Account\AuthorizeDenied.aspx" /> + </ItemGroup> + <ItemGroup> <Folder Include="App_Data\" /> </ItemGroup> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> diff --git a/projecttemplates/MvcRelyingParty/OAuth.ashx b/projecttemplates/MvcRelyingParty/OAuth.ashx new file mode 100644 index 0000000..81b3d52 --- /dev/null +++ b/projecttemplates/MvcRelyingParty/OAuth.ashx @@ -0,0 +1 @@ +<%@ WebHandler Language="C#" CodeBehind="OAuth.ashx.cs" Class="MvcRelyingParty.OAuth" %> diff --git a/projecttemplates/MvcRelyingParty/OAuth.ashx.cs b/projecttemplates/MvcRelyingParty/OAuth.ashx.cs new file mode 100644 index 0000000..b9051c1 --- /dev/null +++ b/projecttemplates/MvcRelyingParty/OAuth.ashx.cs @@ -0,0 +1,66 @@ +//----------------------------------------------------------------------- +// <copyright file="OAuth.ashx.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace MvcRelyingParty { + 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 RelyingPartyLogic; + + /// <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("~/Account/Authorize"); + } else { + throw new InvalidOperationException(); + } + } + } +} diff --git a/projecttemplates/MvcRelyingParty/Views/Account/Authorize.aspx b/projecttemplates/MvcRelyingParty/Views/Account/Authorize.aspx new file mode 100644 index 0000000..f1653c6 --- /dev/null +++ b/projecttemplates/MvcRelyingParty/Views/Account/Authorize.aspx @@ -0,0 +1,63 @@ +<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MvcRelyingParty.Models.AccountAuthorizeModel>" %> + +<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server"> + Authorize +</asp:Content> +<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server"> + <h2> + Authorize + </h2> + <div style="background-color: Yellow"> + <b>Warning</b>: Never give your login credentials to another web site or application. + </div> + <p> + The + <%= Html.Encode(Model.ConsumerApp) %> + 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 + <%= Html.ActionLink("your account page", "Edit") %>. + </p> + <% using (Html.BeginForm()) { %> + <%= Html.AntiForgeryToken() %> + <%= Html.Hidden("IsApproved") %> + <div style="display: none" id="responseButtonsDiv"> + <input type="submit" value="Yes" onclick="document.getElementsByName("IsApproved")[0].value = true; return true;" /> + <input type="submit" value="No" onclick="document.getElementsByName("IsApproved")[0].value = false; return true;" /> + </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> + <% if (Model.IsUnsafeRequest) { %> + <div style="background-color: red; color: white; font-weight: bold"> + This website is registered with + <asp:Label runat="server" ID="serviceProviderDomainNameLabel" /> + 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 + <%= Html.Encode(Model.ConsumerApp) %>, it may be possible for other users of + <%= Html.Encode(Model.ConsumerApp) %> + to access your data. We recommend you deny access unless you are certain that you + initiated this request directly with + <%= Html.Encode(Model.ConsumerApp) %>. + <% } %> + + <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:Content> diff --git a/projecttemplates/MvcRelyingParty/Views/Account/AuthorizeApproved.aspx b/projecttemplates/MvcRelyingParty/Views/Account/AuthorizeApproved.aspx new file mode 100644 index 0000000..a2d91b0 --- /dev/null +++ b/projecttemplates/MvcRelyingParty/Views/Account/AuthorizeApproved.aspx @@ -0,0 +1,24 @@ +<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MvcRelyingParty.Models.AccountAuthorizeModel>" %> + +<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server"> + Authorized +</asp:Content> +<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server"> + <h2> + Authorized + </h2> + <p> + Authorization has been granted. + </p> + <% if (!string.IsNullOrEmpty(Model.VerificationCode)) { %> + <p> + You must enter this verification code at the Consumer: <b> + <%= Html.Encode(Model.VerificationCode)%> + </b> + </p> + <% } else { %> + <p> + You may now close this window and return to the Consumer. + </p> + <% } %> +</asp:Content> diff --git a/projecttemplates/MvcRelyingParty/Views/Account/AuthorizeDenied.aspx b/projecttemplates/MvcRelyingParty/Views/Account/AuthorizeDenied.aspx new file mode 100644 index 0000000..99bfb2a --- /dev/null +++ b/projecttemplates/MvcRelyingParty/Views/Account/AuthorizeDenied.aspx @@ -0,0 +1,13 @@ +<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MvcRelyingParty.Models.AccountAuthorizeModel>" %> + +<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server"> + AuthorizeDenied +</asp:Content> +<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server"> + <h2> + AuthorizeDenied + </h2> + <p> + Authorization has been denied. You're free to do whatever now. + </p> +</asp:Content> diff --git a/projecttemplates/RelyingPartyLogic/OAuthServiceProvider.cs b/projecttemplates/RelyingPartyLogic/OAuthServiceProvider.cs index 1880d80..807da2d 100644 --- a/projecttemplates/RelyingPartyLogic/OAuthServiceProvider.cs +++ b/projecttemplates/RelyingPartyLogic/OAuthServiceProvider.cs @@ -73,6 +73,22 @@ namespace RelyingPartyLogic { } public static void AuthorizePendingRequestToken() { + var response = AuthorizePendingRequestTokenAndGetResponse(); + if (response != null) { + serviceProvider.Channel.Send(response); + } + } + + public static OutgoingWebResponse AuthorizePendingRequestTokenAsWebResponse() { + var response = AuthorizePendingRequestTokenAndGetResponse(); + if (response != null) { + return serviceProvider.Channel.PrepareResponse(response); + } else { + return null; + } + } + + private static UserAuthorizationResponse AuthorizePendingRequestTokenAndGetResponse() { var pendingRequest = PendingAuthorizationRequest; if (pendingRequest == null) { throw new InvalidOperationException("No pending authorization request to authorize."); @@ -84,9 +100,7 @@ namespace RelyingPartyLogic { PendingAuthorizationRequest = null; var response = serviceProvider.PrepareAuthorizationResponse(pendingRequest); - if (response != null) { - serviceProvider.Channel.Send(response); - } + return response; } /// <summary> |