summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndrew Arnott <andrewarnott@gmail.com>2010-07-14 22:05:04 -0700
committerAndrew Arnott <andrewarnott@gmail.com>2010-07-14 22:05:04 -0700
commit9b4fc3a38cbd1b427c25b906a5562ec5586f9340 (patch)
tree40244fa2bfa767bf8bfcfbf19690a8d63fb2be05
parent1882f59229ee85cf2b9cf66cfd2ada1cc27520f7 (diff)
downloadDotNetOpenAuth-9b4fc3a38cbd1b427c25b906a5562ec5586f9340.zip
DotNetOpenAuth-9b4fc3a38cbd1b427c25b906a5562ec5586f9340.tar.gz
DotNetOpenAuth-9b4fc3a38cbd1b427c25b906a5562ec5586f9340.tar.bz2
Lots of work toward OAuth 2.0 in project templates and OAuthConsumerWpf sample.
The WebFormsRelyingParty now works with the sample WPF OAuth client in a modified user-agent mode.
-rw-r--r--projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/Client.table.sql4
-rw-r--r--projecttemplates/RelyingPartyLogic/CreateDatabase.sql2
-rw-r--r--projecttemplates/RelyingPartyLogic/Model.Designer.cs10
-rw-r--r--projecttemplates/RelyingPartyLogic/Model.edmx4
-rw-r--r--projecttemplates/RelyingPartyLogic/OAuthAuthenticationModule.cs2
-rw-r--r--projecttemplates/RelyingPartyLogic/OAuthAuthorizationManager.cs2
-rw-r--r--projecttemplates/RelyingPartyLogic/OAuthAuthorizationServer.cs67
-rw-r--r--projecttemplates/RelyingPartyLogic/RelyingPartyLogic.csproj1
-rw-r--r--projecttemplates/RelyingPartyLogic/SpecialAccessTokenAnalyzer.cs36
-rw-r--r--projecttemplates/WebFormsRelyingParty/Members/AccountInfo.aspx4
-rw-r--r--projecttemplates/WebFormsRelyingParty/Members/OAuthAuthorize.aspx101
-rw-r--r--projecttemplates/WebFormsRelyingParty/Members/OAuthAuthorize.aspx.cs28
-rw-r--r--projecttemplates/WebFormsRelyingParty/Members/OAuthAuthorize.aspx.designer.cs71
-rw-r--r--projecttemplates/WebFormsRelyingParty/Members/Web.config2
-rw-r--r--samples/OAuthConsumerWpf/Authorize2.xaml.cs5
-rw-r--r--samples/OAuthConsumerWpf/MainWindow.xaml21
-rw-r--r--samples/OAuthConsumerWpf/MainWindow.xaml.cs10
-rw-r--r--src/DotNetOpenAuth/Messaging/MessagingUtilities.cs9
-rw-r--r--src/DotNetOpenAuth/OAuth2/ChannelElements/ITokenCarryingRequest.cs2
-rw-r--r--src/DotNetOpenAuth/OAuth2/IConsumerDescription.cs3
-rw-r--r--src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationSuccessResponseBase.cs2
-rw-r--r--src/DotNetOpenAuth/OAuth2/OAuthUtilities.cs47
-rw-r--r--src/DotNetOpenAuth/OAuth2/StandardAccessTokenAnalyzer.cs2
-rw-r--r--src/DotNetOpenAuth/OAuth2/UserAgentClient.cs9
-rw-r--r--src/DotNetOpenAuth/OAuth2/WebServerAuthorizationServer.cs8
25 files changed, 276 insertions, 176 deletions
diff --git a/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/Client.table.sql b/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/Client.table.sql
index 7da9c5e..8dc2f64 100644
--- a/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/Client.table.sql
+++ b/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/Client.table.sql
@@ -3,8 +3,10 @@
[ClientIdentifier] VARCHAR (255) COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL,
[ClientSecret] VARCHAR (255) COLLATE SQL_Latin1_General_CP1_CS_AS NULL,
[Callback] VARCHAR (2048) NULL,
- [Name] NVARCHAR (50) NULL
+ [Name] NVARCHAR (50) NOT NULL
);
+
+
diff --git a/projecttemplates/RelyingPartyLogic/CreateDatabase.sql b/projecttemplates/RelyingPartyLogic/CreateDatabase.sql
index 99df5e5..5c82398 100644
--- a/projecttemplates/RelyingPartyLogic/CreateDatabase.sql
+++ b/projecttemplates/RelyingPartyLogic/CreateDatabase.sql
@@ -195,7 +195,7 @@ CREATE TABLE [dbo].[Client] (
[ClientIdentifier] VARCHAR (255) COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL,
[ClientSecret] VARCHAR (255) COLLATE SQL_Latin1_General_CP1_CS_AS NULL,
[Callback] VARCHAR (2048) NULL,
- [Name] NVARCHAR (50) NULL
+ [Name] NVARCHAR (50) NOT NULL
);
diff --git a/projecttemplates/RelyingPartyLogic/Model.Designer.cs b/projecttemplates/RelyingPartyLogic/Model.Designer.cs
index 564bde5..8884760 100644
--- a/projecttemplates/RelyingPartyLogic/Model.Designer.cs
+++ b/projecttemplates/RelyingPartyLogic/Model.Designer.cs
@@ -15,7 +15,7 @@
[assembly: global::System.Data.Objects.DataClasses.EdmRelationshipAttribute("DatabaseModel", "FK_IssuedToken_User", "User", global::System.Data.Metadata.Edm.RelationshipMultiplicity.One, typeof(RelyingPartyLogic.User), "ClientAuthorization", global::System.Data.Metadata.Edm.RelationshipMultiplicity.Many, typeof(RelyingPartyLogic.ClientAuthorization))]
// Original file name:
-// Generation date: 7/14/2010 7:00:56 AM
+// Generation date: 7/14/2010 9:35:17 PM
namespace RelyingPartyLogic
{
@@ -1190,12 +1190,14 @@ namespace RelyingPartyLogic
/// </summary>
/// <param name="clientId">Initial value of ClientId.</param>
/// <param name="clientIdentifier">Initial value of ClientIdentifier.</param>
+ /// <param name="name">Initial value of Name.</param>
[global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")]
- public static Client CreateClient(int clientId, string clientIdentifier)
+ public static Client CreateClient(int clientId, string clientIdentifier, string name)
{
Client client = new Client();
client.ClientId = clientId;
client.ClientIdentifier = clientIdentifier;
+ client.Name = name;
return client;
}
/// <summary>
@@ -1309,7 +1311,7 @@ namespace RelyingPartyLogic
/// <summary>
/// There are no comments for property Name in the schema.
/// </summary>
- [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute()]
+ [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute(IsNullable=false)]
[global::System.Runtime.Serialization.DataMemberAttribute()]
[global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")]
public string Name
@@ -1322,7 +1324,7 @@ namespace RelyingPartyLogic
{
this.OnNameChanging(value);
this.ReportPropertyChanging("Name");
- this._Name = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value, true);
+ this._Name = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value, false);
this.ReportPropertyChanged("Name");
this.OnNameChanged();
}
diff --git a/projecttemplates/RelyingPartyLogic/Model.edmx b/projecttemplates/RelyingPartyLogic/Model.edmx
index 2123935..a003493 100644
--- a/projecttemplates/RelyingPartyLogic/Model.edmx
+++ b/projecttemplates/RelyingPartyLogic/Model.edmx
@@ -55,7 +55,7 @@
<Property Name="ClientIdentifier" Type="varchar" Nullable="false" MaxLength="255" />
<Property Name="ClientSecret" Type="varchar" MaxLength="255" />
<Property Name="Callback" Type="varchar" MaxLength="2048" />
- <Property Name="Name" Type="nvarchar" MaxLength="50" />
+ <Property Name="Name" Type="nvarchar" Nullable="false" MaxLength="50" />
</EntityType>
<EntityType Name="ClientAuthorization">
<Key>
@@ -284,7 +284,7 @@
<Property Type="String" Name="ClientIdentifier" Nullable="false" MaxLength="255" FixedLength="false" Unicode="true" />
<Property Type="String" Name="ClientSecret" MaxLength="255" FixedLength="false" Unicode="true" />
<Property Type="String" Name="CallbackAsString" MaxLength="2048" FixedLength="false" Unicode="true" />
- <Property Type="String" Name="Name" MaxLength="50" FixedLength="false" Unicode="true" />
+ <Property Type="String" Name="Name" MaxLength="50" FixedLength="false" Unicode="true" Nullable="false" />
<NavigationProperty Name="ClientAuthorizations" Relationship="DatabaseModel.FK_IssuedToken_Consumer" FromRole="Client" ToRole="ClientAuthorization" />
</EntityType>
<EntityType Name="ClientAuthorization">
diff --git a/projecttemplates/RelyingPartyLogic/OAuthAuthenticationModule.cs b/projecttemplates/RelyingPartyLogic/OAuthAuthenticationModule.cs
index 3700b65..c0685bc 100644
--- a/projecttemplates/RelyingPartyLogic/OAuthAuthenticationModule.cs
+++ b/projecttemplates/RelyingPartyLogic/OAuthAuthenticationModule.cs
@@ -49,7 +49,7 @@ namespace RelyingPartyLogic {
return;
}
- var tokenAnalyzer = new StandardAccessTokenAnalyzer(OAuthAuthorizationServer.AsymmetricKey, OAuthAuthorizationServer.AsymmetricKey);
+ var tokenAnalyzer = new SpecialAccessTokenAnalyzer(OAuthAuthorizationServer.AsymmetricKey, OAuthAuthorizationServer.AsymmetricKey);
var resourceServer = new ResourceServer(tokenAnalyzer);
IPrincipal principal;
diff --git a/projecttemplates/RelyingPartyLogic/OAuthAuthorizationManager.cs b/projecttemplates/RelyingPartyLogic/OAuthAuthorizationManager.cs
index f4e27a4..6ac2977 100644
--- a/projecttemplates/RelyingPartyLogic/OAuthAuthorizationManager.cs
+++ b/projecttemplates/RelyingPartyLogic/OAuthAuthorizationManager.cs
@@ -32,7 +32,7 @@ namespace RelyingPartyLogic {
var httpDetails = operationContext.RequestContext.RequestMessage.Properties[HttpRequestMessageProperty.Name] as HttpRequestMessageProperty;
var requestUri = operationContext.RequestContext.RequestMessage.Properties["OriginalHttpRequestUri"] as Uri;
- var tokenAnalyzer = new StandardAccessTokenAnalyzer(OAuthAuthorizationServer.AsymmetricKey, OAuthAuthorizationServer.AsymmetricKey);
+ var tokenAnalyzer = new SpecialAccessTokenAnalyzer(OAuthAuthorizationServer.AsymmetricKey, OAuthAuthorizationServer.AsymmetricKey);
var resourceServer = new ResourceServer(tokenAnalyzer);
try {
diff --git a/projecttemplates/RelyingPartyLogic/OAuthAuthorizationServer.cs b/projecttemplates/RelyingPartyLogic/OAuthAuthorizationServer.cs
index af3dba5..2b207f9 100644
--- a/projecttemplates/RelyingPartyLogic/OAuthAuthorizationServer.cs
+++ b/projecttemplates/RelyingPartyLogic/OAuthAuthorizationServer.cs
@@ -10,9 +10,12 @@ namespace RelyingPartyLogic {
using System.Linq;
using System.Security.Cryptography;
using System.Text;
+ using System.Web;
using DotNetOpenAuth.Messaging.Bindings;
using DotNetOpenAuth.OAuth2;
+ using DotNetOpenAuth.OAuth2.ChannelElements;
+ using DotNetOpenAuth.OAuth2.Messages;
/// <summary>
/// Provides OAuth 2.0 authorization server information to DotNetOpenAuth.
@@ -82,7 +85,11 @@ namespace RelyingPartyLogic {
/// <returns>The client registration. Never null.</returns>
/// <exception cref="ArgumentException">Thrown when no client with the given identifier is registered with this authorization server.</exception>
public IConsumerDescription GetClient(string clientIdentifier) {
- return Database.DataContext.Clients.First(c => c.ClientIdentifier == clientIdentifier);
+ try {
+ return Database.DataContext.Clients.First(c => c.ClientIdentifier == clientIdentifier);
+ } catch (InvalidOperationException ex) {
+ throw new ArgumentOutOfRangeException("No client by that identifier.", ex);
+ }
}
/// <summary>
@@ -107,11 +114,63 @@ namespace RelyingPartyLogic {
/// security in the event the user was revoking access in order to sever authorization on a stolen
/// account or piece of hardware in which the tokens were stored. </para>
/// </remarks>
- public bool IsAuthorizationValid(DotNetOpenAuth.OAuth2.ChannelElements.IAuthorizationDescription authorization) {
- // We don't support revoking tokens yet.
- return true;
+ public bool IsAuthorizationValid(IAuthorizationDescription authorization) {
+ return this.IsAuthorizationValid(authorization.Scope, authorization.ClientIdentifier, authorization.UtcIssued, authorization.User);
}
#endregion
+
+ public bool CanBeAutoApproved(EndUserAuthorizationRequest authorizationRequest) {
+ if (authorizationRequest == null) {
+ throw new ArgumentNullException("authorizationRequest");
+ }
+
+ // NEVER issue an auto-approval to a client that would end up getting an access token immediately
+ // (without a client secret), as that would allow ANY client to spoof an approved client's identity
+ // and obtain unauthorized access to user data.
+ if (authorizationRequest.ResponseType == EndUserAuthorizationResponseType.AuthorizationCode) {
+ // Never issue auto-approval if the client secret is blank, since that too makes it easy to spoof
+ // a client's identity and obtain unauthorized access.
+ var requestingClient = Database.DataContext.Clients.First(c => c.ClientIdentifier == authorizationRequest.ClientIdentifier);
+ if (!string.IsNullOrEmpty(requestingClient.ClientSecret)) {
+ return this.IsAuthorizationValid(
+ authorizationRequest.Scope,
+ authorizationRequest.ClientIdentifier,
+ DateTime.UtcNow,
+ HttpContext.Current.User.Identity.Name);
+ }
+ }
+
+ // Default to not auto-approving.
+ return false;
+ }
+
+ private bool IsAuthorizationValid(string requestedScope, string clientIdentifier, DateTime issuedUtc, string username)
+ {
+ var stringCompare = StringComparer.Ordinal;
+ var requestedScopes = OAuthUtilities.BreakUpScopes(requestedScope, stringCompare);
+
+ var grantedScopeStrings = from auth in Database.DataContext.ClientAuthorizations
+ where
+ auth.Client.ClientIdentifier == clientIdentifier &&
+ auth.CreatedOnUtc <= issuedUtc &&
+ auth.User.AuthenticationTokens.Any(token => token.ClaimedIdentifier == username)
+ select auth.Scope;
+
+ if (!grantedScopeStrings.Any()) {
+ // No granted authorizations prior to the issuance of this token, so it must have been revoked.
+ // Even if later authorizations restore this client's ability to call in, we can't allow
+ // access tokens issued before the re-authorization because the revoked authorization should
+ // effectively and permanently revoke all access and refresh tokens.
+ return false;
+ }
+
+ var grantedScopes = new HashSet<string>(stringCompare);
+ foreach (string scope in grantedScopeStrings) {
+ grantedScopes.UnionWith(OAuthUtilities.BreakUpScopes(scope, stringCompare));
+ }
+
+ return requestedScopes.IsSubsetOf(grantedScopes);
+ }
}
}
diff --git a/projecttemplates/RelyingPartyLogic/RelyingPartyLogic.csproj b/projecttemplates/RelyingPartyLogic/RelyingPartyLogic.csproj
index 21215b0..06dee41 100644
--- a/projecttemplates/RelyingPartyLogic/RelyingPartyLogic.csproj
+++ b/projecttemplates/RelyingPartyLogic/RelyingPartyLogic.csproj
@@ -127,6 +127,7 @@
<Compile Include="Policies.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RelyingPartyApplicationDbStore.cs" />
+ <Compile Include="SpecialAccessTokenAnalyzer.cs" />
<Compile Include="Utilities.cs" />
</ItemGroup>
<ItemGroup>
diff --git a/projecttemplates/RelyingPartyLogic/SpecialAccessTokenAnalyzer.cs b/projecttemplates/RelyingPartyLogic/SpecialAccessTokenAnalyzer.cs
new file mode 100644
index 0000000..f189433
--- /dev/null
+++ b/projecttemplates/RelyingPartyLogic/SpecialAccessTokenAnalyzer.cs
@@ -0,0 +1,36 @@
+//-----------------------------------------------------------------------
+// <copyright file="SpecialAccessTokenAnalyzer.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace RelyingPartyLogic {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Security.Cryptography;
+ using System.Text;
+
+ using DotNetOpenAuth.OAuth2;
+
+ internal class SpecialAccessTokenAnalyzer : StandardAccessTokenAnalyzer {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SpecialAccessTokenAnalyzer"/> class.
+ /// </summary>
+ /// <param name="authorizationServerPublicSigningKey">The authorization server public signing key.</param>
+ /// <param name="resourceServerPrivateEncryptionKey">The resource server private encryption key.</param>
+ internal SpecialAccessTokenAnalyzer(RSAParameters authorizationServerPublicSigningKey, RSAParameters resourceServerPrivateEncryptionKey)
+ : base(authorizationServerPublicSigningKey, resourceServerPrivateEncryptionKey) {
+ }
+
+ public override bool TryValidateAccessToken(DotNetOpenAuth.Messaging.IDirectedProtocolMessage message, string accessToken, out string user, out string scope) {
+ bool result = base.TryValidateAccessToken(message, accessToken, out user, out scope);
+ if (result) {
+ // Ensure that clients coming in this way always belong to the oauth_client role.
+ scope += " " + "oauth_client";
+ }
+
+ return result;
+ }
+ }
+}
diff --git a/projecttemplates/WebFormsRelyingParty/Members/AccountInfo.aspx b/projecttemplates/WebFormsRelyingParty/Members/AccountInfo.aspx
index 86263aa..458d624 100644
--- a/projecttemplates/WebFormsRelyingParty/Members/AccountInfo.aspx
+++ b/projecttemplates/WebFormsRelyingParty/Members/AccountInfo.aspx
@@ -91,7 +91,9 @@
<li>
<asp:Label runat="server" Text='<%# HttpUtility.HtmlEncode(Eval("Client.Name").ToString()) %>' />
-
- <asp:Label ID="Label1" runat="server" Text='<%# HttpUtility.HtmlEncode(Eval("CreatedOn").ToString()) %>' ForeColor="Gray" />
+ <asp:Label ID="Label2" runat="server" Text='<%# HttpUtility.HtmlEncode((string)Eval("Scope")) %>' ForeColor="Gray" />
+ -
+ <asp:Label ID="Label1" runat="server" Text='<%# HttpUtility.HtmlEncode(Eval("CreatedOnUtc").ToString()) %>' ForeColor="Gray" />
-
<asp:LinkButton ID="revokeLink" runat="server" Text="revoke" OnCommand="revokeToken_Command"
CommandName="revokeToken" CommandArgument='<%# Eval("AuthorizationId") %>' />
diff --git a/projecttemplates/WebFormsRelyingParty/Members/OAuthAuthorize.aspx b/projecttemplates/WebFormsRelyingParty/Members/OAuthAuthorize.aspx
index 7e07323..9ec00a8 100644
--- a/projecttemplates/WebFormsRelyingParty/Members/OAuthAuthorize.aspx
+++ b/projecttemplates/WebFormsRelyingParty/Members/OAuthAuthorize.aspx
@@ -5,68 +5,45 @@
<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 <a href="AccountInfo.aspx"
- target="_blank">your account page</a>.
- </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>
+ <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>
+ <b>Requested access: </b>
+ <asp:Label runat="server" ID="scopeLabel" />
+ </p>
+ <p>
+ If you grant access now, you can revoke it at any time by returning to <a href="AccountInfo.aspx"
+ target="_blank">your account page</a>.
+ </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>
- <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';
+ <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>
+ // 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/WebFormsRelyingParty/Members/OAuthAuthorize.aspx.cs b/projecttemplates/WebFormsRelyingParty/Members/OAuthAuthorize.aspx.cs
index c7355c3..2a95b89 100644
--- a/projecttemplates/WebFormsRelyingParty/Members/OAuthAuthorize.aspx.cs
+++ b/projecttemplates/WebFormsRelyingParty/Members/OAuthAuthorize.aspx.cs
@@ -7,17 +7,16 @@
namespace WebFormsRelyingParty.Members {
using System;
using System.Collections.Generic;
+ using System.Globalization;
using System.Linq;
using System.Net;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
-
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.OAuth;
using DotNetOpenAuth.OAuth.Messages;
using DotNetOpenAuth.OAuth2.Messages;
-
using RelyingPartyLogic;
public partial class OAuthAuthorize : System.Web.UI.Page {
@@ -27,8 +26,8 @@ namespace WebFormsRelyingParty.Members {
// We'll mask that on postback it's a POST when looking up the authorization details so that the GET-only
// message can be picked up.
var requestInfo = this.IsPostBack
- ? new HttpRequestInfo("GET", this.Request.Url, this.Request.RawUrl, new WebHeaderCollection(), null)
- : null;
+ ? new HttpRequestInfo("GET", this.Request.Url, this.Request.RawUrl, new WebHeaderCollection(), null)
+ : null;
this.pendingRequest = OAuthServiceProvider.AuthorizationServer.ReadAuthorizationRequest(requestInfo);
if (this.pendingRequest == null) {
Response.Redirect("AccountInfo.aspx");
@@ -38,27 +37,30 @@ namespace WebFormsRelyingParty.Members {
this.csrfCheck.Value = Code.SiteUtilities.SetCsrfCookie();
var requestingClient = Database.DataContext.Clients.First(c => c.ClientIdentifier == this.pendingRequest.ClientIdentifier);
this.consumerNameLabel.Text = HttpUtility.HtmlEncode(requestingClient.Name);
+ this.scopeLabel.Text = HttpUtility.HtmlEncode(this.pendingRequest.Scope);
+
+ // Consider auto-approving if safe to do so.
+ if (((OAuthAuthorizationServer)OAuthServiceProvider.AuthorizationServer.AuthorizationServer).CanBeAutoApproved(this.pendingRequest)) {
+ OAuthServiceProvider.AuthorizationServer.ApproveAuthorizationRequest(this.pendingRequest, HttpContext.Current.User.Identity.Name);
+ }
} else {
Code.SiteUtilities.VerifyCsrfCookie(this.csrfCheck.Value);
}
}
protected void yesButton_Click(object sender, EventArgs e) {
- this.outerMultiView.SetActiveView(this.authorizationGrantedView);
-
var requestingClient = Database.DataContext.Clients.First(c => c.ClientIdentifier == this.pendingRequest.ClientIdentifier);
Database.LoggedInUser.ClientAuthorizations.Add(
- new ClientAuthorization
- {
- Client = requestingClient,
- Scope = this.pendingRequest.Scope,
- User = Database.LoggedInUser,
- });
+ new ClientAuthorization {
+ Client = requestingClient,
+ Scope = this.pendingRequest.Scope,
+ User = Database.LoggedInUser,
+ CreatedOnUtc = DateTime.UtcNow.CutToSecond(),
+ });
OAuthServiceProvider.AuthorizationServer.ApproveAuthorizationRequest(this.pendingRequest, HttpContext.Current.User.Identity.Name);
}
protected void noButton_Click(object sender, EventArgs e) {
- this.outerMultiView.SetActiveView(this.authorizationDeniedView);
OAuthServiceProvider.AuthorizationServer.RejectAuthorizationRequest(this.pendingRequest);
}
}
diff --git a/projecttemplates/WebFormsRelyingParty/Members/OAuthAuthorize.aspx.designer.cs b/projecttemplates/WebFormsRelyingParty/Members/OAuthAuthorize.aspx.designer.cs
index 19947de..d243c81 100644
--- a/projecttemplates/WebFormsRelyingParty/Members/OAuthAuthorize.aspx.designer.cs
+++ b/projecttemplates/WebFormsRelyingParty/Members/OAuthAuthorize.aspx.designer.cs
@@ -13,31 +13,22 @@ 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.
+ /// 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.View getPermissionView;
+ protected global::System.Web.UI.WebControls.Label consumerNameLabel;
/// <summary>
- /// consumerNameLabel control.
+ /// scopeLabel 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;
+ protected global::System.Web.UI.WebControls.Label scopeLabel;
/// <summary>
/// yesButton control.
@@ -65,59 +56,5 @@ namespace WebFormsRelyingParty.Members {
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::System.Web.UI.WebControls.HiddenField csrfCheck;
-
- /// <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 f95a16d..4ab44bc 100644
--- a/projecttemplates/WebFormsRelyingParty/Members/Web.config
+++ b/projecttemplates/WebFormsRelyingParty/Members/Web.config
@@ -20,7 +20,7 @@
<location path="AccountInfo.aspx">
<system.web>
<authorization>
- <deny roles="delegated" />
+ <deny roles="oauth_client" />
</authorization>
</system.web>
</location>
diff --git a/samples/OAuthConsumerWpf/Authorize2.xaml.cs b/samples/OAuthConsumerWpf/Authorize2.xaml.cs
index e257315..8cf9f6f 100644
--- a/samples/OAuthConsumerWpf/Authorize2.xaml.cs
+++ b/samples/OAuthConsumerWpf/Authorize2.xaml.cs
@@ -22,13 +22,14 @@
public partial class Authorize2 : Window {
private UserAgentClient client;
- internal Authorize2(UserAgentClient client) {
+ internal Authorize2(UserAgentClient client, IAuthorizationState authorizationState) {
Contract.Requires(client != null, "client");
+ Contract.Requires(authorizationState != null, "authorizationState");
InitializeComponent();
this.client = client;
- this.Authorization = new AuthorizationState();
+ this.Authorization = authorizationState;
Uri authorizationUrl = this.client.RequestUserAuthorization(this.Authorization);
this.webBrowser.Navigate(authorizationUrl.AbsoluteUri); // use AbsoluteUri to workaround bug in WebBrowser that calls Uri.ToString instead of Uri.AbsoluteUri leading to escaping errors.
}
diff --git a/samples/OAuthConsumerWpf/MainWindow.xaml b/samples/OAuthConsumerWpf/MainWindow.xaml
index 2305227..40b63e7 100644
--- a/samples/OAuthConsumerWpf/MainWindow.xaml
+++ b/samples/OAuthConsumerWpf/MainWindow.xaml
@@ -144,6 +144,7 @@
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
+ <RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
@@ -152,10 +153,10 @@
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
<Label Grid.Row="1" TabIndex="202">Token Endpoint URL</Label>
- <TextBox Grid.Row="1" Grid.Column="1" x:Name="wrapTokenUrlBox" Text="https://graph.facebook.com/oauth/access_token" TabIndex="203" />
+ <TextBox Grid.Row="1" Grid.Column="1" x:Name="wrapTokenUrlBox" Text="http://localhost:54189/OAuthTokenEndpoint.ashx" TabIndex="203" />
<Label Grid.Row="1" Grid.Column="2" TabIndex="204">POST</Label>
<Label Grid.Row="2" TabIndex="205">User Authorization URL</Label>
- <TextBox Grid.Row="2" Grid.Column="1" x:Name="wrapAuthorizationUrlBox" Text="https://graph.facebook.com/oauth/authorize?display=popup" TabIndex="206" />
+ <TextBox Grid.Row="2" Grid.Column="1" x:Name="wrapAuthorizationUrlBox" Text="http://localhost:54189/Members/OAuthAuthorize.aspx" TabIndex="206" />
<Label Grid.Row="2" Grid.Column="2" TabIndex="207">GET</Label>
<Label Grid.Row="0" TabIndex="200">Grant Type</Label>
<ComboBox Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="2" x:Name="flowBox" SelectedIndex="0" TabIndex="201">
@@ -166,7 +167,7 @@
</ComboBox.Items>
</ComboBox>
<Label Grid.Row="3" TabIndex="207">Resource URL</Label>
- <TextBox Grid.Row="3" Grid.Column="1" x:Name="wrapResourceUrlBox" Text="https://graph.facebook.com/me" TabIndex="208" />
+ <TextBox Grid.Row="3" Grid.Column="1" x:Name="wrapResourceUrlBox" Text="http://localhost:54189/Members/" TabIndex="208" />
<ComboBox Grid.Row="3" Grid.Column="2" x:Name="wrapResourceHttpMethodList" SelectedIndex="0" TabIndex="209">
<ComboBox.Items>
<ComboBoxItem>GET w/ header</ComboBoxItem>
@@ -175,17 +176,19 @@
</ComboBox.Items>
</ComboBox>
<Label Grid.Row="4" TabIndex="210">Client Identifier</Label>
- <TextBox Grid.Row="4" Grid.Column="1" x:Name="wrapClientIdentifierBox" Grid.ColumnSpan="2" Text="367207604173" TabIndex="211" />
+ <TextBox Grid.Row="4" Grid.Column="1" x:Name="wrapClientIdentifierBox" Grid.ColumnSpan="2" Text="a" TabIndex="211" />
<Label Grid.Row="5" TabIndex="212">Client Secret</Label>
- <TextBox Grid.Row="5" Grid.Column="1" x:Name="wrapClientSecretBox" Grid.ColumnSpan="2" Text="1df77e64055c4d7d3583cefdf2bc62d7" TabIndex="213" />
- <Label Grid.Row="6" TabIndex="214">OAuth 2.0 version</Label>
- <ComboBox Grid.Row="6" Grid.Column="1" SelectedIndex="0" x:Name="wrapVersion" TabIndex="215">
+ <TextBox Grid.Row="5" Grid.Column="1" x:Name="wrapClientSecretBox" Grid.ColumnSpan="2" Text="b" TabIndex="213" />
+ <Label Grid.Row="6" TabIndex="214">Scope</Label>
+ <TextBox Grid.Row="6" Grid.Column="1" x:Name="wrapScopeBox" TabIndex="215" Text="some scope" />
+ <Label Grid.Row="7" TabIndex="216">OAuth 2.0 version</Label>
+ <ComboBox Grid.Row="7" Grid.Column="1" SelectedIndex="0" x:Name="wrapVersion" TabIndex="217">
<ComboBox.Items>
<ComboBoxItem>2.0 DRAFT 9</ComboBoxItem>
</ComboBox.Items>
</ComboBox>
- <Button Grid.Row="7" Grid.Column="1" x:Name="wrapBeginButton" Click="wrapBeginButton_Click" TabIndex="216">Begin</Button>
- <TextBox Grid.Column="0" Grid.Row="8" Grid.ColumnSpan="3" Name="wrapResultsBox" IsReadOnly="True" />
+ <Button Grid.Row="8" Grid.Column="1" x:Name="wrapBeginButton" Click="wrapBeginButton_Click" TabIndex="218">Begin</Button>
+ <TextBox Grid.Column="0" Grid.Row="9" Grid.ColumnSpan="3" Name="wrapResultsBox" IsReadOnly="True" TabIndex="219"/>
</Grid>
</TabItem>
</TabControl>
diff --git a/samples/OAuthConsumerWpf/MainWindow.xaml.cs b/samples/OAuthConsumerWpf/MainWindow.xaml.cs
index d698ce0..46a5f06 100644
--- a/samples/OAuthConsumerWpf/MainWindow.xaml.cs
+++ b/samples/OAuthConsumerWpf/MainWindow.xaml.cs
@@ -29,7 +29,11 @@
using DotNetOpenAuth.OAuth;
using DotNetOpenAuth.OAuth.ChannelElements;
using DotNetOpenAuth.Samples.OAuthConsumerWpf.WcfSampleService;
+
+ using OAuth2;
+
using OAuth2 = DotNetOpenAuth.OAuth2;
+ using ProtocolVersion = DotNetOpenAuth.OAuth.ProtocolVersion;
/// <summary>
/// Interaction logic for MainWindow.xaml
@@ -213,8 +217,10 @@
////var client = new DotNetOpenAuth.OAuth2.WebAppClient(authServer);
////client.PrepareRequestUserAuthorization();
var client = new OAuth2.UserAgentClient(authServer, wrapClientIdentifierBox.Text);
+ client.ClientSecret = wrapClientSecretBox.Text;
- var authorizePopup = new Authorize2(client);
+ var authorization = new AuthorizationState { Scope = wrapScopeBox.Text };
+ var authorizePopup = new Authorize2(client, authorization);
authorizePopup.Owner = this;
bool? result = authorizePopup.ShowDialog();
if (result.HasValue && result.Value) {
@@ -237,6 +243,8 @@
}
} catch (DotNetOpenAuth.Messaging.ProtocolException ex) {
MessageBox.Show(this, ex.Message);
+ } catch (WebException ex) {
+ MessageBox.Show(this, ex.Message);
}
}
}
diff --git a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs b/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs
index 60fbdd8..0f44711 100644
--- a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs
+++ b/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs
@@ -209,6 +209,15 @@ namespace DotNetOpenAuth.Messaging {
}
/// <summary>
+ /// Cuts off precision beyond a second on a DateTime value.
+ /// </summary>
+ /// <param name="value">The value.</param>
+ /// <returns>A DateTime with a 0 millisecond component.</returns>
+ public static DateTime CutToSecond(this DateTime value) {
+ return value - TimeSpan.FromMilliseconds(value.Millisecond);
+ }
+
+ /// <summary>
/// Strips any and all URI query parameters that serve as parts of a message.
/// </summary>
/// <param name="uri">The URI that may contain query parameters to remove.</param>
diff --git a/src/DotNetOpenAuth/OAuth2/ChannelElements/ITokenCarryingRequest.cs b/src/DotNetOpenAuth/OAuth2/ChannelElements/ITokenCarryingRequest.cs
index 0f4d84f..4c8d33f 100644
--- a/src/DotNetOpenAuth/OAuth2/ChannelElements/ITokenCarryingRequest.cs
+++ b/src/DotNetOpenAuth/OAuth2/ChannelElements/ITokenCarryingRequest.cs
@@ -5,6 +5,8 @@
//-----------------------------------------------------------------------
namespace DotNetOpenAuth.OAuth2.ChannelElements {
+ using System.Security.Cryptography;
+
using Messaging;
/// <summary>
diff --git a/src/DotNetOpenAuth/OAuth2/IConsumerDescription.cs b/src/DotNetOpenAuth/OAuth2/IConsumerDescription.cs
index bfb3ecc..4e2bc4f 100644
--- a/src/DotNetOpenAuth/OAuth2/IConsumerDescription.cs
+++ b/src/DotNetOpenAuth/OAuth2/IConsumerDescription.cs
@@ -6,14 +6,13 @@
namespace DotNetOpenAuth.OAuth2 {
using System;
- using System.Security.Cryptography.X509Certificates;
/// <summary>
/// A description of a client from an Authorization Server's point of view.
/// </summary>
public interface IConsumerDescription {
/// <summary>
- /// Gets the consumer secret.
+ /// Gets the client secret.
/// </summary>
string Secret { get; }
diff --git a/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationSuccessResponseBase.cs b/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationSuccessResponseBase.cs
index 62cad53..a02c050 100644
--- a/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationSuccessResponseBase.cs
+++ b/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationSuccessResponseBase.cs
@@ -7,6 +7,8 @@
namespace DotNetOpenAuth.OAuth2.Messages {
using System;
using System.Diagnostics.Contracts;
+ using System.Security.Cryptography;
+
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.OAuth2.ChannelElements;
diff --git a/src/DotNetOpenAuth/OAuth2/OAuthUtilities.cs b/src/DotNetOpenAuth/OAuth2/OAuthUtilities.cs
index ccc7e8d..fa1e661 100644
--- a/src/DotNetOpenAuth/OAuth2/OAuthUtilities.cs
+++ b/src/DotNetOpenAuth/OAuth2/OAuthUtilities.cs
@@ -17,7 +17,52 @@ namespace DotNetOpenAuth.OAuth2 {
/// <summary>
/// Some common utility methods for OAuth 2.0.
/// </summary>
- internal static class OAuthUtilities {
+ public static class OAuthUtilities {
+ /// <summary>
+ /// The delimiter between scope elements.
+ /// </summary>
+ private static char[] scopeDelimiter = new char[] { ' ' };
+
+ /// <summary>
+ /// Determines whether one given scope is a subset of another scope.
+ /// </summary>
+ /// <param name="requestedScope">The requested scope, which may be a subset of <paramref name="grantedScope"/>.</param>
+ /// <param name="grantedScope">The granted scope, the suspected superset.</param>
+ /// <returns>
+ /// <c>true</c> if all the elements that appear in <paramref name="requestedScope"/> also appear in <paramref name="grantedScope"/>;
+ /// <c>false</c> otherwise.
+ /// </returns>
+ public static bool IsScopeSubset(string requestedScope, string grantedScope) {
+ if (string.IsNullOrEmpty(requestedScope)) {
+ return true;
+ }
+
+ if (string.IsNullOrEmpty(grantedScope)) {
+ return false;
+ }
+
+ var requestedScopes = new HashSet<string>(requestedScope.Split(scopeDelimiter, StringSplitOptions.RemoveEmptyEntries));
+ var grantedScopes = new HashSet<string>(grantedScope.Split(scopeDelimiter, StringSplitOptions.RemoveEmptyEntries));
+ return requestedScopes.IsSubsetOf(grantedScopes);
+ }
+
+ /// <summary>
+ /// Identifies individual scope elements
+ /// </summary>
+ /// <param name="scope">The scope.</param>
+ /// <param name="scopeComparer">The scope comparer, allowing scopes to be case sensitive or insensitive.
+ /// Usually <see cref="StringComparer.Ordinal"/> or <see cref="StringComparer.OrdinalIgnoreCase"/>.</param>
+ /// <returns></returns>
+ public static HashSet<string> BreakUpScopes(string scope, StringComparer scopeComparer) {
+ Contract.Requires<ArgumentNullException>(scopeComparer != null, "scopeComparer");
+
+ if (string.IsNullOrEmpty(scope)) {
+ return new HashSet<string>();
+ }
+
+ return new HashSet<string>(scope.Split(scopeDelimiter, StringSplitOptions.RemoveEmptyEntries), scopeComparer);
+ }
+
/// <summary>
/// Authorizes an HTTP request using an OAuth 2.0 access token in an HTTP Authorization header.
/// </summary>
diff --git a/src/DotNetOpenAuth/OAuth2/StandardAccessTokenAnalyzer.cs b/src/DotNetOpenAuth/OAuth2/StandardAccessTokenAnalyzer.cs
index f292af5..5e0ea94 100644
--- a/src/DotNetOpenAuth/OAuth2/StandardAccessTokenAnalyzer.cs
+++ b/src/DotNetOpenAuth/OAuth2/StandardAccessTokenAnalyzer.cs
@@ -50,7 +50,7 @@ namespace DotNetOpenAuth.OAuth2 {
/// This method also responsible to throw a <see cref="ProtocolException"/> or return
/// <c>false</c> when the access token is expired, invalid, or from an untrusted authorization server.
/// </remarks>
- public bool TryValidateAccessToken(IDirectedProtocolMessage message, string accessToken, out string user, out string scope) {
+ public virtual bool TryValidateAccessToken(IDirectedProtocolMessage message, string accessToken, out string user, out string scope) {
var accessTokenFormatter = AccessToken.CreateFormatter(this.AuthorizationServerPublicSigningKey, this.ResourceServerPrivateEncryptionKey);
var token = accessTokenFormatter.Deserialize(message, accessToken);
user = token.User;
diff --git a/src/DotNetOpenAuth/OAuth2/UserAgentClient.cs b/src/DotNetOpenAuth/OAuth2/UserAgentClient.cs
index f7e1a9f..db73cd9 100644
--- a/src/DotNetOpenAuth/OAuth2/UserAgentClient.cs
+++ b/src/DotNetOpenAuth/OAuth2/UserAgentClient.cs
@@ -36,6 +36,12 @@ namespace DotNetOpenAuth.OAuth2 {
Contract.Requires<ArgumentNullException>(authorizationEndpoint != null, "authorizationEndpoint");
}
+ // TODO: remove this. user agent clients can't keep secrets.
+ public new string ClientSecret {
+ get { return base.ClientSecret; }
+ set { base.ClientSecret = value; }
+ }
+
/// <summary>
/// Generates a URL that the user's browser can be directed to in order to authorize
/// this client to access protected data at some resource server.
@@ -65,7 +71,8 @@ namespace DotNetOpenAuth.OAuth2 {
ClientIdentifier = this.ClientIdentifier,
Scope = authorization.Scope,
Callback = authorization.Callback,
- ResponseType = EndUserAuthorizationResponseType.AccessToken,
+ // TODO: bring back ResponseType = AccessToken, since user agents can't keep secrets, thus can't process authorization codes.
+ //ResponseType = EndUserAuthorizationResponseType.AccessToken,
};
return this.Channel.PrepareResponse(request).GetDirectUriRequest(this.Channel);
diff --git a/src/DotNetOpenAuth/OAuth2/WebServerAuthorizationServer.cs b/src/DotNetOpenAuth/OAuth2/WebServerAuthorizationServer.cs
index 8800efd..681f062 100644
--- a/src/DotNetOpenAuth/OAuth2/WebServerAuthorizationServer.cs
+++ b/src/DotNetOpenAuth/OAuth2/WebServerAuthorizationServer.cs
@@ -45,10 +45,16 @@ namespace DotNetOpenAuth.OAuth2 {
return message;
}
- public void ApproveAuthorizationRequest(EndUserAuthorizationRequest authorizationRequest, string username, Uri callback = null) {
+ public void ApproveAuthorizationRequest(EndUserAuthorizationRequest authorizationRequest, string username, string scope = null, Uri callback = null) {
Contract.Requires<ArgumentNullException>(authorizationRequest != null, "authorizationRequest");
var response = this.PrepareApproveAuthorizationRequest(authorizationRequest, username, callback);
+
+ // Customize the approved scope if the authorization server has decided to do so.
+ if (scope != null) {
+ response.Scope = scope;
+ }
+
this.Channel.Send(response);
}