diff options
author | Andrew Arnott <andrewarnott@gmail.com> | 2010-06-02 22:39:46 -0700 |
---|---|---|
committer | Andrew Arnott <andrewarnott@gmail.com> | 2010-06-02 22:39:46 -0700 |
commit | ccbec1badf616062cf9cb102ae4a0b2d835610b0 (patch) | |
tree | 11037cf8c3edbbd444bdccfc2efe6dad82027bd6 | |
parent | 9db9a3768ce97f565ff01ca355be295d9775c4ef (diff) | |
download | DotNetOpenAuth-ccbec1badf616062cf9cb102ae4a0b2d835610b0.zip DotNetOpenAuth-ccbec1badf616062cf9cb102ae4a0b2d835610b0.tar.gz DotNetOpenAuth-ccbec1badf616062cf9cb102ae4a0b2d835610b0.tar.bz2 |
OAuth 2.0 web flow now works, client, auth server, and resource server, in the sample!
Yay.
11 files changed, 180 insertions, 30 deletions
diff --git a/samples/OAuthConsumer/SampleWcf.aspx b/samples/OAuthConsumer/SampleWcf.aspx index fb318ce..fcec089 100644 --- a/samples/OAuthConsumer/SampleWcf.aspx +++ b/samples/OAuthConsumer/SampleWcf.aspx @@ -1,4 +1,4 @@ -<%@ Page Title="" Language="C#" MasterPageFile="~/MasterPage.master" AutoEventWireup="true" Inherits="OAuthConsumer.SampleWcf" Codebehind="SampleWcf.aspx.cs" %> +<%@ Page Title="OAuth 1.0a consumer" Language="C#" MasterPageFile="~/MasterPage.master" AutoEventWireup="true" Inherits="OAuthConsumer.SampleWcf" Codebehind="SampleWcf.aspx.cs" %> <asp:Content ID="Content2" ContentPlaceHolderID="Body" runat="Server"> <fieldset title="Authorization"> diff --git a/samples/OAuthConsumer/SampleWcf.aspx.cs b/samples/OAuthConsumer/SampleWcf.aspx.cs index 489e294..74c6e6a 100644 --- a/samples/OAuthConsumer/SampleWcf.aspx.cs +++ b/samples/OAuthConsumer/SampleWcf.aspx.cs @@ -39,10 +39,10 @@ string[] scopes = (from item in this.scopeList.Items.OfType<ListItem>() where item.Selected select item.Value).ToArray(); - string scope = string.Join("|", scopes); + string scope = string.Join(" ", scopes); var requestParams = new Dictionary<string, string> { - { "scope", scope }, - }; + { "scope", scope }, + }; var response = consumer.PrepareRequestUserAuthorization(callback.Uri, requestParams, null); consumer.Channel.Send(response); } @@ -83,7 +83,7 @@ WebConsumer consumer = this.CreateConsumer(); WebRequest httpRequest = consumer.PrepareAuthorizedRequest(serviceEndpoint, accessToken); - HttpRequestMessageProperty httpDetails = new HttpRequestMessageProperty(); + var httpDetails = new HttpRequestMessageProperty(); httpDetails.Headers[HttpRequestHeader.Authorization] = httpRequest.Headers[HttpRequestHeader.Authorization]; using (OperationContextScope scope = new OperationContextScope(client.InnerChannel)) { OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = httpDetails; diff --git a/samples/OAuthConsumer/SampleWcf2.aspx b/samples/OAuthConsumer/SampleWcf2.aspx index e8d672b..797a2fc 100644 --- a/samples/OAuthConsumer/SampleWcf2.aspx +++ b/samples/OAuthConsumer/SampleWcf2.aspx @@ -1,4 +1,4 @@ -<%@ Page Title="" Language="C#" MasterPageFile="~/MasterPage.master" AutoEventWireup="true" Inherits="OAuthConsumer.SampleWcf2" Codebehind="SampleWcf2.aspx.cs" %> +<%@ Page Title="OAuth 2.0 client (web server flow)" Language="C#" MasterPageFile="~/MasterPage.master" AutoEventWireup="true" Inherits="OAuthConsumer.SampleWcf2" Codebehind="SampleWcf2.aspx.cs" %> <asp:Content ID="Content2" ContentPlaceHolderID="Body" runat="Server"> <fieldset title="Authorization"> @@ -10,4 +10,14 @@ <asp:Button ID="getAuthorizationButton" runat="server" Text="Get Authorization" OnClick="getAuthorizationButton_Click" /> <asp:Label ID="authorizationLabel" runat="server" /> </fieldset> + <br /> + <asp:Button ID="getNameButton" runat="server" Text="Get Name" OnClick="getNameButton_Click" /> + <asp:Label ID="nameLabel" runat="server" /> + <br /> + <asp:Button ID="getAgeButton" runat="server" Text="Get Age" OnClick="getAgeButton_Click" /> + <asp:Label ID="ageLabel" runat="server" /> + <br /> + <asp:Button ID="getFavoriteSites" runat="server" Text="Get Favorite Sites" + onclick="getFavoriteSites_Click" /> + <asp:Label ID="favoriteSitesLabel" runat="server" /> </asp:Content>
\ No newline at end of file diff --git a/samples/OAuthConsumer/SampleWcf2.aspx.cs b/samples/OAuthConsumer/SampleWcf2.aspx.cs index 09ebd12..fa7a102 100644 --- a/samples/OAuthConsumer/SampleWcf2.aspx.cs +++ b/samples/OAuthConsumer/SampleWcf2.aspx.cs @@ -1,16 +1,27 @@ namespace OAuthConsumer { using System; using System.Collections.Generic; + using System.Globalization; using System.Linq; + using System.Net; + using System.ServiceModel; + using System.ServiceModel.Channels; + using System.ServiceModel.Security; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using DotNetOpenAuth.ApplicationBlock; using DotNetOpenAuth.OAuthWrap; + using OAuthConsumer.SampleServiceProvider; public partial class SampleWcf2 : System.Web.UI.Page { private static InMemoryClientTokenManager TokenManager = new InMemoryClientTokenManager(); + private static IAuthorizationState Authorization { + get { return (AuthorizationState)HttpContext.Current.Session["Authorization"]; } + set { HttpContext.Current.Session["Authorization"] = value; } + } + protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { @@ -18,7 +29,7 @@ var authorization = client.ProcessUserAuthorization(); if (authorization != null) { - Response.Write("Obtained access token: " + authorization.AccessToken); + Authorization = authorization; } } } @@ -27,7 +38,7 @@ string[] scopes = (from item in this.scopeList.Items.OfType<ListItem>() where item.Selected select item.Value).ToArray(); - string scope = string.Join("|", scopes); + string scope = string.Join(" ", scopes); var client = CreateClient(); string clientState; @@ -36,13 +47,56 @@ client.Channel.Send(response); } + protected void getNameButton_Click(object sender, EventArgs e) { + try { + this.nameLabel.Text = CallService(client => client.GetName()); + } catch (SecurityAccessDeniedException) { + this.nameLabel.Text = "Access denied!"; + } + } + + protected void getAgeButton_Click(object sender, EventArgs e) { + try { + int? age = CallService(client => client.GetAge()); + this.ageLabel.Text = age.HasValue ? age.Value.ToString(CultureInfo.CurrentCulture) : "not available"; + } catch (SecurityAccessDeniedException) { + this.ageLabel.Text = "Access denied!"; + } + } + + protected void getFavoriteSites_Click(object sender, EventArgs e) { + try { + string[] favoriteSites = CallService(client => client.GetFavoriteSites()); + this.favoriteSitesLabel.Text = string.Join(", ", favoriteSites); + } catch (SecurityAccessDeniedException) { + this.favoriteSitesLabel.Text = "Access denied!"; + } + } + + private T CallService<T>(Func<DataApiClient, T> predicate) { + DataApiClient client = new DataApiClient(); + //var serviceEndpoint = new MessageReceivingEndpoint(client.Endpoint.Address.Uri, HttpDeliveryMethods.AuthorizationHeaderRequest | HttpDeliveryMethods.PostRequest); + if (Authorization == null) { + throw new InvalidOperationException("No access token!"); + } + + var httpRequest = (HttpWebRequest)WebRequest.Create(client.Endpoint.Address.Uri); + WebAppClient.AuthorizeRequest(httpRequest, Authorization); + + var httpDetails = new HttpRequestMessageProperty(); + httpDetails.Headers[HttpRequestHeader.Authorization] = httpRequest.Headers[HttpRequestHeader.Authorization]; + using (OperationContextScope scope = new OperationContextScope(client.InnerChannel)) { + OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = httpDetails; + return predicate(client); + } + } + private static WebAppClient CreateClient() { var authServerDescription = new AuthorizationServerDescription { TokenEndpoint = new Uri("http://localhost:65169/OAuth2.ashx/token"), AuthorizationEndpoint = new Uri("http://localhost:65169/OAuth2.ashx/auth"), }; - var client = new WebAppClient(authServerDescription) - { + var client = new WebAppClient(authServerDescription) { ClientIdentifier = "sampleconsumer", ClientSecret = "samplesecret", TokenManager = TokenManager, diff --git a/samples/OAuthConsumer/SampleWcf2.aspx.designer.cs b/samples/OAuthConsumer/SampleWcf2.aspx.designer.cs index 3046936..f42efff 100644 --- a/samples/OAuthConsumer/SampleWcf2.aspx.designer.cs +++ b/samples/OAuthConsumer/SampleWcf2.aspx.designer.cs @@ -38,5 +38,59 @@ namespace OAuthConsumer { /// To modify move field declaration from designer file to code-behind file. /// </remarks> protected global::System.Web.UI.WebControls.Label authorizationLabel; + + /// <summary> + /// getNameButton 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 getNameButton; + + /// <summary> + /// nameLabel 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 nameLabel; + + /// <summary> + /// getAgeButton 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 getAgeButton; + + /// <summary> + /// ageLabel 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 ageLabel; + + /// <summary> + /// getFavoriteSites 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 getFavoriteSites; + + /// <summary> + /// favoriteSitesLabel 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 favoriteSitesLabel; } } diff --git a/samples/OAuthServiceProvider/Code/OAuth2AuthorizationServer.cs b/samples/OAuthServiceProvider/Code/OAuth2AuthorizationServer.cs index ed70a15..75778f5 100644 --- a/samples/OAuthServiceProvider/Code/OAuth2AuthorizationServer.cs +++ b/samples/OAuthServiceProvider/Code/OAuth2AuthorizationServer.cs @@ -13,7 +13,7 @@ internal class OAuth2AuthorizationServer : IAuthorizationServer { private static readonly byte[] secret; - private static readonly RSAParameters asymmetricKey; + internal static readonly RSAParameters asymmetricKey; private readonly INonceStore nonceStore = new DatabaseNonceStore(); diff --git a/samples/OAuthServiceProvider/Code/OAuthAuthorizationManager.cs b/samples/OAuthServiceProvider/Code/OAuthAuthorizationManager.cs index b12e85a..a48288a 100644 --- a/samples/OAuthServiceProvider/Code/OAuthAuthorizationManager.cs +++ b/samples/OAuthServiceProvider/Code/OAuthAuthorizationManager.cs @@ -9,6 +9,8 @@ using System.ServiceModel.Security; using DotNetOpenAuth; using DotNetOpenAuth.OAuth; + using DotNetOpenAuth.OAuthWrap; + using DotNetOpenAuth.OAuth.ChannelElements; /// <summary> /// A WCF extension to authenticate incoming messages using OAuth. @@ -24,13 +26,10 @@ HttpRequestMessageProperty httpDetails = operationContext.RequestContext.RequestMessage.Properties[HttpRequestMessageProperty.Name] as HttpRequestMessageProperty; Uri requestUri = operationContext.RequestContext.RequestMessage.Properties["OriginalHttpRequestUri"] as Uri; - ServiceProvider sp = Constants.CreateServiceProvider(); - try { - var auth = sp.ReadProtectedResourceAuthorization(httpDetails, requestUri); - if (auth != null) { - var accessToken = Global.DataContext.OAuthTokens.Single(token => token.Token == auth.AccessToken); - var principal = sp.CreatePrincipal(auth); + try { + var principal = VerifyOAuth2(httpDetails, requestUri); + if (principal != null) { var policy = new OAuthPrincipalAuthorizationPolicy(principal); var policies = new List<IAuthorizationPolicy> { policy, @@ -46,14 +45,13 @@ } securityContext.AuthorizationContext.Properties["Identities"] = new List<IIdentity> { - principal.Identity, - }; + 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 principal.IsInRole(operationContext.IncomingMessageHeaders.Action); + } else { + return false; } } catch (ProtocolException ex) { Global.Logger.Error("Error processing OAuth messages.", ex); @@ -61,5 +59,36 @@ return false; } + + private OAuthPrincipal VerifyOAuth1(HttpRequestMessageProperty httpDetails, Uri requestUri) { + ServiceProvider sp = Constants.CreateServiceProvider(); + var auth = sp.ReadProtectedResourceAuthorization(httpDetails, requestUri); + if (auth != null) { + var accessToken = Global.DataContext.OAuthTokens.Single(token => token.Token == auth.AccessToken); + var principal = sp.CreatePrincipal(auth); + return principal; + } + + return null; + } + + private OAuthPrincipal VerifyOAuth2(HttpRequestMessageProperty httpDetails, Uri requestUri) { + // for this sample where the auth server and resource server are the same site, + // we use the same public/private key. + var resourceServer = new ResourceServer( + new StandardAccessTokenAnalyzer( + OAuth2AuthorizationServer.asymmetricKey, + OAuth2AuthorizationServer.asymmetricKey)); + + string username, scope; + var error = resourceServer.VerifyAccess(new DotNetOpenAuth.Messaging.HttpRequestInfo(httpDetails, requestUri), out username, out scope); + if (error == null) { + string[] scopes = scope.Split(new char[] { ' ' }); + var principal = new OAuthPrincipal(username, scopes); + return principal; + } else { + return null; + } + } } }
\ No newline at end of file diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthPrincipal.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthPrincipal.cs index 025ef09..48fe813 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthPrincipal.cs +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthPrincipal.cs @@ -51,7 +51,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// </summary> /// <param name="username">The username.</param> /// <param name="roles">The roles this user belongs to.</param> - internal OAuthPrincipal(string username, string[] roles) + public OAuthPrincipal(string username, string[] roles) : this(new OAuthIdentity(username), roles) { } diff --git a/src/DotNetOpenAuth/OAuthWrap/Messages/AccessProtectedResourceRequest.cs b/src/DotNetOpenAuth/OAuthWrap/Messages/AccessProtectedResourceRequest.cs index 1f2efaa..dfdacb7 100644 --- a/src/DotNetOpenAuth/OAuthWrap/Messages/AccessProtectedResourceRequest.cs +++ b/src/DotNetOpenAuth/OAuthWrap/Messages/AccessProtectedResourceRequest.cs @@ -18,7 +18,7 @@ namespace DotNetOpenAuth.OAuthWrap.Messages { /// </summary> /// <param name="version">The version.</param> /// <param name="recipient">The recipient.</param> - internal AccessProtectedResourceRequest(Version version, Uri recipient) + internal AccessProtectedResourceRequest(Uri recipient, Version version) : base(version, MessageTransport.Direct, recipient) { } diff --git a/src/DotNetOpenAuth/OAuthWrap/StandardAccessTokenAnalyzer.cs b/src/DotNetOpenAuth/OAuthWrap/StandardAccessTokenAnalyzer.cs index 5eaa1e5..3797cba 100644 --- a/src/DotNetOpenAuth/OAuthWrap/StandardAccessTokenAnalyzer.cs +++ b/src/DotNetOpenAuth/OAuthWrap/StandardAccessTokenAnalyzer.cs @@ -14,13 +14,15 @@ namespace DotNetOpenAuth.OAuthWrap { using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OAuthWrap.ChannelElements; - internal class StandardAccessTokenAnalyzer : IAccessTokenAnalyzer { - internal StandardAccessTokenAnalyzer() { + public class StandardAccessTokenAnalyzer : IAccessTokenAnalyzer { + public StandardAccessTokenAnalyzer(RSAParameters authorizationServerPublicSigningKey, RSAParameters resourceServerPrivateEncryptionKey) { + this.AuthorizationServerPublicSigningKey = authorizationServerPublicSigningKey; + this.ResourceServerPrivateEncryptionKey = resourceServerPrivateEncryptionKey; } - internal RSAParameters AuthorizationServerPublicSigningKey { get; set; } + public RSAParameters AuthorizationServerPublicSigningKey { get; private set; } - internal RSAParameters ResourceServerPrivateEncryptionKey { get; set; } + public RSAParameters ResourceServerPrivateEncryptionKey { get; private set; } public bool TryValidateAccessToken(string accessToken, out string user, out string scope) { var token = AccessToken.Decode(this.AuthorizationServerPublicSigningKey, this.ResourceServerPrivateEncryptionKey, accessToken); diff --git a/src/DotNetOpenAuth/OAuthWrap/WebAppClient.cs b/src/DotNetOpenAuth/OAuthWrap/WebAppClient.cs index 1847886..e351198 100644 --- a/src/DotNetOpenAuth/OAuthWrap/WebAppClient.cs +++ b/src/DotNetOpenAuth/OAuthWrap/WebAppClient.cs @@ -52,7 +52,8 @@ namespace DotNetOpenAuth.OAuthWrap { Contract.Ensures(Contract.Result<WebAppRequest>().Callback == authorization.Callback); if (authorization.Callback == null) { - authorization.Callback = this.Channel.GetRequestFromContext().UrlBeforeRewriting; + authorization.Callback = this.Channel.GetRequestFromContext().UrlBeforeRewriting + .StripMessagePartsFromQueryString(this.Channel.MessageDescriptions.Get(typeof(WebAppSuccessResponse), Protocol.Default.Version)); authorization.SaveChanges(); } |