summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/DotNetOpenAuth/DotNetOpenAuth.csproj1
-rw-r--r--src/DotNetOpenAuth/Messaging/MessagingUtilities.cs30
-rw-r--r--src/DotNetOpenAuth/OAuth2/AuthorizationServer.cs6
-rw-r--r--src/DotNetOpenAuth/OAuth2/AuthorizationServerBase.cs2
-rw-r--r--src/DotNetOpenAuth/OAuth2/AuthorizationState.cs11
-rw-r--r--src/DotNetOpenAuth/OAuth2/ChannelElements/AccessRequestBindingElement.cs2
-rw-r--r--src/DotNetOpenAuth/OAuth2/ChannelElements/AccessToken.cs2
-rw-r--r--src/DotNetOpenAuth/OAuth2/ChannelElements/AuthorizationCode.cs7
-rw-r--r--src/DotNetOpenAuth/OAuth2/ChannelElements/AuthorizationDataBag.cs5
-rw-r--r--src/DotNetOpenAuth/OAuth2/ChannelElements/IAuthorizationDescription.cs9
-rw-r--r--src/DotNetOpenAuth/OAuth2/ChannelElements/RefreshToken.cs2
-rw-r--r--src/DotNetOpenAuth/OAuth2/ChannelElements/ScopeEncoder.cs51
-rw-r--r--src/DotNetOpenAuth/OAuth2/ClientBase.cs12
-rw-r--r--src/DotNetOpenAuth/OAuth2/IAccessTokenAnalyzer.cs4
-rw-r--r--src/DotNetOpenAuth/OAuth2/IAuthorizationState.cs3
-rw-r--r--src/DotNetOpenAuth/OAuth2/Messages/AccessTokenRequestBase.cs5
-rw-r--r--src/DotNetOpenAuth/OAuth2/Messages/AccessTokenSuccessResponse.cs6
-rw-r--r--src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationRequest.cs6
-rw-r--r--src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationSuccessResponseBase.cs9
-rw-r--r--src/DotNetOpenAuth/OAuth2/OAuthStrings.Designer.cs9
-rw-r--r--src/DotNetOpenAuth/OAuth2/OAuthStrings.resx3
-rw-r--r--src/DotNetOpenAuth/OAuth2/OAuthUtilities.cs13
-rw-r--r--src/DotNetOpenAuth/OAuth2/ResourceServer.cs9
-rw-r--r--src/DotNetOpenAuth/OAuth2/StandardAccessTokenAnalyzer.cs5
-rw-r--r--src/DotNetOpenAuth/OAuth2/UserAgentClient.cs6
-rw-r--r--src/DotNetOpenAuth/OAuth2/WebServerClient.cs8
26 files changed, 174 insertions, 52 deletions
diff --git a/src/DotNetOpenAuth/DotNetOpenAuth.csproj b/src/DotNetOpenAuth/DotNetOpenAuth.csproj
index 8f33c8d..1287b61 100644
--- a/src/DotNetOpenAuth/DotNetOpenAuth.csproj
+++ b/src/DotNetOpenAuth/DotNetOpenAuth.csproj
@@ -355,6 +355,7 @@ http://opensource.org/licenses/ms-pl.html
<Compile Include="OAuth2\ChannelElements\IDataBagFormatter.cs" />
<Compile Include="OAuth2\ChannelElements\OAuth2ChannelBase.cs" />
<Compile Include="OAuth2\ChannelElements\OAuth2ClientChannel.cs" />
+ <Compile Include="OAuth2\ChannelElements\ScopeEncoder.cs" />
<Compile Include="OAuth2\ChannelElements\UriStyleMessageFormatter.cs" />
<Compile Include="OAuth2\ChannelElements\IAuthorizationDescription.cs" />
<Compile Include="OAuth2\ChannelElements\ITokenCarryingRequest.cs" />
diff --git a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs b/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs
index d652051..1860770 100644
--- a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs
+++ b/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs
@@ -234,6 +234,36 @@ namespace DotNetOpenAuth.Messaging {
}
/// <summary>
+ /// Adds a set of values to a collection.
+ /// </summary>
+ /// <typeparam name="T">The type of value kept in the collection.</typeparam>
+ /// <param name="collection">The collection to add to.</param>
+ /// <param name="values">The values to add to the collection.</param>
+ public static void AddRange<T>(this ICollection<T> collection, IEnumerable<T> values) {
+ Contract.Requires<ArgumentNullException>(collection != null, "collection");
+ Contract.Requires<ArgumentNullException>(values != null, "values");
+
+ foreach (var value in values) {
+ collection.Add(value);
+ }
+ }
+
+ /// <summary>
+ /// Clears any existing elements in a collection and fills the collection with a given set of values.
+ /// </summary>
+ /// <typeparam name="T">The type of value kept in the collection.</typeparam>
+ /// <param name="collection">The collection to modify.</param>
+ /// <param name="values">The new values to fill the collection.</param>
+ internal static void ResetContents<T>(this ICollection<T> collection, IEnumerable<T> values) {
+ Contract.Requires<ArgumentNullException>(collection != null, "collection");
+
+ collection.Clear();
+ if (values != null) {
+ AddRange(collection, values);
+ }
+ }
+
+ /// <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/AuthorizationServer.cs b/src/DotNetOpenAuth/OAuth2/AuthorizationServer.cs
index ce4f6bb..37be337 100644
--- a/src/DotNetOpenAuth/OAuth2/AuthorizationServer.cs
+++ b/src/DotNetOpenAuth/OAuth2/AuthorizationServer.cs
@@ -45,14 +45,14 @@ namespace DotNetOpenAuth.OAuth2 {
return message;
}
- public void ApproveAuthorizationRequest(EndUserAuthorizationRequest authorizationRequest, string username, string scope = null, Uri callback = null) {
+ public void ApproveAuthorizationRequest(EndUserAuthorizationRequest authorizationRequest, string username, IEnumerable<string> scopes = 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;
+ if (scopes != null) {
+ response.Scope.ResetContents(scopes);
}
this.Channel.Send(response);
diff --git a/src/DotNetOpenAuth/OAuth2/AuthorizationServerBase.cs b/src/DotNetOpenAuth/OAuth2/AuthorizationServerBase.cs
index 93790b6..9078326 100644
--- a/src/DotNetOpenAuth/OAuth2/AuthorizationServerBase.cs
+++ b/src/DotNetOpenAuth/OAuth2/AuthorizationServerBase.cs
@@ -66,10 +66,10 @@ namespace DotNetOpenAuth.OAuth2 {
var accessToken = new AccessToken(tokenRequest.AuthorizationDescription, accessTokenLifetime);
var response = new AccessTokenSuccessResponse(request) {
- Scope = tokenRequest.AuthorizationDescription.Scope,
AccessToken = accessTokenFormatter.Serialize(accessToken),
Lifetime = accessToken.Lifetime,
};
+ response.Scope.ResetContents(tokenRequest.AuthorizationDescription.Scope);
if (includeRefreshToken) {
var refreshTokenFormatter = RefreshToken.CreateFormatter(this.AuthorizationServer.Secret);
diff --git a/src/DotNetOpenAuth/OAuth2/AuthorizationState.cs b/src/DotNetOpenAuth/OAuth2/AuthorizationState.cs
index 907f6e7..7682900 100644
--- a/src/DotNetOpenAuth/OAuth2/AuthorizationState.cs
+++ b/src/DotNetOpenAuth/OAuth2/AuthorizationState.cs
@@ -6,6 +6,9 @@
namespace DotNetOpenAuth.OAuth2 {
using System;
+ using System.Collections.Generic;
+
+ using DotNetOpenAuth.Messaging;
/// <summary>
/// A simple memory-only copy of an authorization state.
@@ -15,7 +18,11 @@ namespace DotNetOpenAuth.OAuth2 {
/// <summary>
/// Initializes a new instance of the <see cref="AuthorizationState"/> class.
/// </summary>
- public AuthorizationState() {
+ public AuthorizationState(IEnumerable<string> scopes = null) {
+ this.Scope = new HashSet<string>(OAuthUtilities.ScopeStringComparer);
+ if (scopes != null) {
+ this.Scope.AddRange(scopes);
+ }
}
/// <summary>
@@ -52,7 +59,7 @@ namespace DotNetOpenAuth.OAuth2 {
/// Gets or sets the scope the token is (to be) authorized for.
/// </summary>
/// <value>The scope.</value>
- public string Scope { get; set; }
+ public HashSet<string> Scope { get; private set; }
/// <summary>
/// Gets or sets a value indicating whether this instance is deleted.
diff --git a/src/DotNetOpenAuth/OAuth2/ChannelElements/AccessRequestBindingElement.cs b/src/DotNetOpenAuth/OAuth2/ChannelElements/AccessRequestBindingElement.cs
index 80d2cd4..2404963 100644
--- a/src/DotNetOpenAuth/OAuth2/ChannelElements/AccessRequestBindingElement.cs
+++ b/src/DotNetOpenAuth/OAuth2/ChannelElements/AccessRequestBindingElement.cs
@@ -129,7 +129,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements {
ErrorUtilities.VerifyProtocol(string.Equals(client.Secret, accessRequest.ClientSecret, StringComparison.Ordinal), Protocol.incorrect_client_credentials);
// Make sure the scope the client is requesting does not exceed the scope in the grant.
- ErrorUtilities.VerifyProtocol(OAuthUtilities.IsScopeSubset(accessRequest.Scope, tokenRequest.AuthorizationDescription.Scope), OAuthStrings.AccessScopeExceedsGrantScope, accessRequest.Scope, tokenRequest.AuthorizationDescription.Scope);
+ ErrorUtilities.VerifyProtocol(accessRequest.Scope.IsSubsetOf(tokenRequest.AuthorizationDescription.Scope), OAuthStrings.AccessScopeExceedsGrantScope, accessRequest.Scope, tokenRequest.AuthorizationDescription.Scope);
}
// Make sure the authorization this token represents hasn't already been revoked.
diff --git a/src/DotNetOpenAuth/OAuth2/ChannelElements/AccessToken.cs b/src/DotNetOpenAuth/OAuth2/ChannelElements/AccessToken.cs
index aba407f..de24428 100644
--- a/src/DotNetOpenAuth/OAuth2/ChannelElements/AccessToken.cs
+++ b/src/DotNetOpenAuth/OAuth2/ChannelElements/AccessToken.cs
@@ -35,7 +35,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements {
this.ClientIdentifier = authorization.ClientIdentifier;
this.UtcCreationDate = authorization.UtcIssued;
this.User = authorization.User;
- this.Scope = authorization.Scope;
+ this.Scope.ResetContents(authorization.Scope);
this.Lifetime = lifetime;
}
diff --git a/src/DotNetOpenAuth/OAuth2/ChannelElements/AuthorizationCode.cs b/src/DotNetOpenAuth/OAuth2/ChannelElements/AuthorizationCode.cs
index c4257aa..1ec6b41 100644
--- a/src/DotNetOpenAuth/OAuth2/ChannelElements/AuthorizationCode.cs
+++ b/src/DotNetOpenAuth/OAuth2/ChannelElements/AuthorizationCode.cs
@@ -6,6 +6,7 @@
namespace DotNetOpenAuth.OAuth2.ChannelElements {
using System;
+ using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Security.Cryptography;
using System.Text;
@@ -33,15 +34,15 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements {
/// </summary>
/// <param name="clientIdentifier">The client identifier.</param>
/// <param name="callback">The callback the client used to obtain authorization.</param>
- /// <param name="scope">The scope.</param>
+ /// <param name="scopes">The authorized scopes.</param>
/// <param name="username">The name on the account that authorized access.</param>
- internal AuthorizationCode(string clientIdentifier, Uri callback, string scope, string username) {
+ internal AuthorizationCode(string clientIdentifier, Uri callback, IEnumerable<string> scopes, string username) {
Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(clientIdentifier));
Contract.Requires<ArgumentNullException>(callback != null, "callback");
this.ClientIdentifier = clientIdentifier;
this.CallbackHash = this.CalculateCallbackHash(callback);
- this.Scope = scope;
+ this.Scope.ResetContents(scopes);
this.User = username;
}
diff --git a/src/DotNetOpenAuth/OAuth2/ChannelElements/AuthorizationDataBag.cs b/src/DotNetOpenAuth/OAuth2/ChannelElements/AuthorizationDataBag.cs
index 90d63eb..2ca8c34 100644
--- a/src/DotNetOpenAuth/OAuth2/ChannelElements/AuthorizationDataBag.cs
+++ b/src/DotNetOpenAuth/OAuth2/ChannelElements/AuthorizationDataBag.cs
@@ -22,6 +22,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements {
/// Initializes a new instance of the <see cref="AuthorizationDataBag"/> class.
/// </summary>
protected AuthorizationDataBag() {
+ this.Scope = new HashSet<string>(OAuthUtilities.ScopeStringComparer);
}
/// <summary>
@@ -48,7 +49,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements {
/// <summary>
/// Gets or sets the scope of operations the client is allowed to invoke.
/// </summary>
- [MessagePart]
- public string Scope { get; set; }
+ [MessagePart(Encoder = typeof(ScopeEncoder))]
+ public HashSet<string> Scope { get; private set; }
}
}
diff --git a/src/DotNetOpenAuth/OAuth2/ChannelElements/IAuthorizationDescription.cs b/src/DotNetOpenAuth/OAuth2/ChannelElements/IAuthorizationDescription.cs
index a4aba57..2b3a9ce 100644
--- a/src/DotNetOpenAuth/OAuth2/ChannelElements/IAuthorizationDescription.cs
+++ b/src/DotNetOpenAuth/OAuth2/ChannelElements/IAuthorizationDescription.cs
@@ -35,7 +35,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements {
/// <summary>
/// Gets the scope of operations the client is allowed to invoke.
/// </summary>
- string Scope { get; }
+ HashSet<string> Scope { get; }
}
/// <summary>
@@ -80,8 +80,11 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements {
/// <summary>
/// Gets the scope of operations the client is allowed to invoke.
/// </summary>
- string IAuthorizationDescription.Scope {
- get { throw new NotImplementedException(); }
+ HashSet<string> IAuthorizationDescription.Scope {
+ get {
+ Contract.Ensures(Contract.Result<HashSet<string>>() != null);
+ throw new NotImplementedException();
+ }
}
}
}
diff --git a/src/DotNetOpenAuth/OAuth2/ChannelElements/RefreshToken.cs b/src/DotNetOpenAuth/OAuth2/ChannelElements/RefreshToken.cs
index cd6cbf4..a88bf9f 100644
--- a/src/DotNetOpenAuth/OAuth2/ChannelElements/RefreshToken.cs
+++ b/src/DotNetOpenAuth/OAuth2/ChannelElements/RefreshToken.cs
@@ -33,7 +33,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements {
this.ClientIdentifier = authorization.ClientIdentifier;
this.UtcCreationDate = authorization.UtcIssued;
this.User = authorization.User;
- this.Scope = authorization.Scope;
+ this.Scope.ResetContents(authorization.Scope);
}
internal static IDataBagFormatter<RefreshToken> CreateFormatter(byte[] symmetricSecret)
diff --git a/src/DotNetOpenAuth/OAuth2/ChannelElements/ScopeEncoder.cs b/src/DotNetOpenAuth/OAuth2/ChannelElements/ScopeEncoder.cs
new file mode 100644
index 0000000..d35f982
--- /dev/null
+++ b/src/DotNetOpenAuth/OAuth2/ChannelElements/ScopeEncoder.cs
@@ -0,0 +1,51 @@
+//-----------------------------------------------------------------------
+// <copyright file="ScopeEncoder.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth2.ChannelElements {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Reflection;
+
+ /// <summary>
+ /// Encodes or decodes a set of scopes into the OAuth 2.0 scope message part.
+ /// </summary>
+ internal class ScopeEncoder : IMessagePartEncoder {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ScopeEncoder"/> class.
+ /// </summary>
+ public ScopeEncoder() {
+ }
+
+ /// <summary>
+ /// Encodes the specified value.
+ /// </summary>
+ /// <param name="value">The value. Guaranteed to never be null.</param>
+ /// <returns>
+ /// The <paramref name="value"/> in string form, ready for message transport.
+ /// </returns>
+ public string Encode(object value) {
+ var scopes = (IEnumerable<string>)value;
+ ErrorUtilities.VerifyProtocol(!scopes.Any(scope => scope.Contains(" ")), OAuthStrings.ScopesMayNotContainSpaces);
+ return scopes != null ? string.Join(" ", scopes.ToArray()) : null;
+ }
+
+ /// <summary>
+ /// Decodes the specified value.
+ /// </summary>
+ /// <param name="value">The string value carried by the transport. Guaranteed to never be null, although it may be empty.</param>
+ /// <returns>
+ /// The deserialized form of the given string.
+ /// </returns>
+ /// <exception cref="FormatException">Thrown when the string value given cannot be decoded into the required object type.</exception>
+ public object Decode(string value) {
+ return OAuthUtilities.SplitScopes(value);
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth/OAuth2/ClientBase.cs b/src/DotNetOpenAuth/OAuth2/ClientBase.cs
index ec957b3..d9d9232 100644
--- a/src/DotNetOpenAuth/OAuth2/ClientBase.cs
+++ b/src/DotNetOpenAuth/OAuth2/ClientBase.cs
@@ -120,7 +120,7 @@ namespace DotNetOpenAuth.OAuth2 {
// Just in case the scope has changed...
if (response.Scope != null) {
- authorization.Scope = response.Scope;
+ authorization.Scope.ResetContents(response.Scope);
}
// The authorization server MAY choose to renew the refresh token itself.
@@ -147,12 +147,12 @@ namespace DotNetOpenAuth.OAuth2 {
if (accessTokenSuccess.Scope != null && accessTokenSuccess.Scope != authorizationState.Scope) {
if (authorizationState.Scope != null) {
Logger.OAuth.InfoFormat(
- "Requested scope of \"{0}\" changed to \"{1}\" by authorization server.",
- authorizationState.Scope,
- accessTokenSuccess.Scope);
+ "Requested scope of \"{0}\" changed to \"{1}\" by authorization server.",
+ authorizationState.Scope,
+ accessTokenSuccess.Scope);
}
- authorizationState.Scope = accessTokenSuccess.Scope;
+ authorizationState.Scope.ResetContents(accessTokenSuccess.Scope);
}
authorizationState.SaveChanges();
@@ -178,7 +178,7 @@ namespace DotNetOpenAuth.OAuth2 {
accessTokenSuccess.Scope);
}
- authorizationState.Scope = accessTokenSuccess.Scope;
+ authorizationState.Scope.ResetContents(accessTokenSuccess.Scope);
}
authorizationState.SaveChanges();
diff --git a/src/DotNetOpenAuth/OAuth2/IAccessTokenAnalyzer.cs b/src/DotNetOpenAuth/OAuth2/IAccessTokenAnalyzer.cs
index d82a6ab..ed6cd63 100644
--- a/src/DotNetOpenAuth/OAuth2/IAccessTokenAnalyzer.cs
+++ b/src/DotNetOpenAuth/OAuth2/IAccessTokenAnalyzer.cs
@@ -26,7 +26,7 @@ namespace DotNetOpenAuth.OAuth2 {
/// <param name="user">The user whose data is accessible with this access token.</param>
/// <param name="scope">The scope of access authorized by this access token.</param>
/// <returns>A value indicating whether this access token is valid.</returns>
- bool TryValidateAccessToken(IDirectedProtocolMessage message, string accessToken, out string user, out string scope);
+ bool TryValidateAccessToken(IDirectedProtocolMessage message, string accessToken, out string user, out HashSet<string> scope);
}
/// <summary>
@@ -50,7 +50,7 @@ namespace DotNetOpenAuth.OAuth2 {
/// <returns>
/// A value indicating whether this access token is valid.
/// </returns>
- bool IAccessTokenAnalyzer.TryValidateAccessToken(IDirectedProtocolMessage message, string accessToken, out string user, out string scope) {
+ bool IAccessTokenAnalyzer.TryValidateAccessToken(IDirectedProtocolMessage message, string accessToken, out string user, out HashSet<string> scope) {
Contract.Requires<ArgumentNullException>(message != null, "message");
Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(accessToken));
Contract.Ensures(Contract.Result<bool>() == (Contract.ValueAtReturn<string>(out user) != null));
diff --git a/src/DotNetOpenAuth/OAuth2/IAuthorizationState.cs b/src/DotNetOpenAuth/OAuth2/IAuthorizationState.cs
index 3839209..68a109b 100644
--- a/src/DotNetOpenAuth/OAuth2/IAuthorizationState.cs
+++ b/src/DotNetOpenAuth/OAuth2/IAuthorizationState.cs
@@ -6,6 +6,7 @@
namespace DotNetOpenAuth.OAuth2 {
using System;
+ using System.Collections.Generic;
/// <summary>
/// Provides access to a persistent object that tracks the state of an authorization.
@@ -44,7 +45,7 @@ namespace DotNetOpenAuth.OAuth2 {
/// Gets or sets the scope the token is (to be) authorized for.
/// </summary>
/// <value>The scope.</value>
- string Scope { get; set; }
+ HashSet<string> Scope { get; }
/// <summary>
/// Deletes this authorization, including access token and refresh token where applicable.
diff --git a/src/DotNetOpenAuth/OAuth2/Messages/AccessTokenRequestBase.cs b/src/DotNetOpenAuth/OAuth2/Messages/AccessTokenRequestBase.cs
index d9c2144..943308e 100644
--- a/src/DotNetOpenAuth/OAuth2/Messages/AccessTokenRequestBase.cs
+++ b/src/DotNetOpenAuth/OAuth2/Messages/AccessTokenRequestBase.cs
@@ -25,6 +25,7 @@ namespace DotNetOpenAuth.OAuth2.Messages {
protected AccessTokenRequestBase(Uri tokenEndpoint, Version version)
: base(tokenEndpoint, version) {
this.HttpMethods = HttpDeliveryMethods.PostRequest;
+ this.Scope = new HashSet<string>(OAuthUtilities.ScopeStringComparer);
}
/// <summary>
@@ -34,8 +35,8 @@ namespace DotNetOpenAuth.OAuth2.Messages {
[MessagePart(Protocol.grant_type, IsRequired = true, AllowEmpty = false, Encoder = typeof(GrantTypeEncoder))]
internal abstract GrantType GrantType { get; }
- [MessagePart(Protocol.scope, IsRequired = false, AllowEmpty = true)]
- internal string Scope { get; set; }
+ [MessagePart(Protocol.scope, IsRequired = false, AllowEmpty = true, Encoder = typeof(ScopeEncoder))]
+ internal HashSet<string> Scope { get; private set; }
/// <summary>
/// Checks the message state for conformity to the protocol specification
diff --git a/src/DotNetOpenAuth/OAuth2/Messages/AccessTokenSuccessResponse.cs b/src/DotNetOpenAuth/OAuth2/Messages/AccessTokenSuccessResponse.cs
index 85db387..37a4684 100644
--- a/src/DotNetOpenAuth/OAuth2/Messages/AccessTokenSuccessResponse.cs
+++ b/src/DotNetOpenAuth/OAuth2/Messages/AccessTokenSuccessResponse.cs
@@ -6,6 +6,7 @@
namespace DotNetOpenAuth.OAuth2.Messages {
using System;
+ using System.Collections.Generic;
using System.Net;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.OAuth2.ChannelElements;
@@ -24,6 +25,7 @@ namespace DotNetOpenAuth.OAuth2.Messages {
/// <param name="request">The request.</param>
internal AccessTokenSuccessResponse(AccessTokenRequestBase request)
: base(request) {
+ this.Scope = new HashSet<string>(OAuthUtilities.ScopeStringComparer);
}
/// <summary>
@@ -75,7 +77,7 @@ namespace DotNetOpenAuth.OAuth2.Messages {
/// Gets or sets the scope of access being requested.
/// </summary>
/// <value>The scope of the access request expressed as a list of space-delimited strings. The value of the scope parameter is defined by the authorization server. If the value contains multiple space-delimited strings, their order does not matter, and each string adds an additional access range to the requested scope.</value>
- [MessagePart(Protocol.scope, IsRequired = false, AllowEmpty = true)]
- public string Scope { get; set; }
+ [MessagePart(Protocol.scope, IsRequired = false, AllowEmpty = true, Encoder = typeof(ScopeEncoder))]
+ public HashSet<string> Scope { get; private set; }
}
}
diff --git a/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationRequest.cs b/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationRequest.cs
index 108c323..a4e8b89 100644
--- a/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationRequest.cs
+++ b/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationRequest.cs
@@ -6,6 +6,7 @@
namespace DotNetOpenAuth.OAuth2.Messages {
using System;
+ using System.Collections.Generic;
using System.Diagnostics.Contracts;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.OAuth2.ChannelElements;
@@ -27,6 +28,7 @@ namespace DotNetOpenAuth.OAuth2.Messages {
Contract.Requires<ArgumentNullException>(authorizationEndpoint != null);
Contract.Requires<ArgumentNullException>(version != null);
this.HttpMethods = HttpDeliveryMethods.GetRequest;
+ this.Scope = new HashSet<string>(OAuthUtilities.ScopeStringComparer);
}
/// <summary>
@@ -85,7 +87,7 @@ namespace DotNetOpenAuth.OAuth2.Messages {
/// Gets or sets the scope of access being requested.
/// </summary>
/// <value>The scope of the access request expressed as a list of space-delimited strings. The value of the scope parameter is defined by the authorization server. If the value contains multiple space-delimited strings, their order does not matter, and each string adds an additional access range to the requested scope.</value>
- [MessagePart(Protocol.scope, IsRequired = false, AllowEmpty = true)]
- public string Scope { get; set; }
+ [MessagePart(Protocol.scope, IsRequired = false, AllowEmpty = true, Encoder = typeof(ScopeEncoder))]
+ public HashSet<string> Scope { get; private set; }
}
}
diff --git a/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationSuccessResponseBase.cs b/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationSuccessResponseBase.cs
index a02c050..305c0e6 100644
--- a/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationSuccessResponseBase.cs
+++ b/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationSuccessResponseBase.cs
@@ -6,6 +6,7 @@
namespace DotNetOpenAuth.OAuth2.Messages {
using System;
+ using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Security.Cryptography;
@@ -27,6 +28,7 @@ namespace DotNetOpenAuth.OAuth2.Messages {
: base(version, MessageTransport.Indirect, clientCallback) {
Contract.Requires<ArgumentNullException>(version != null);
Contract.Requires<ArgumentNullException>(clientCallback != null);
+ this.Scope = new HashSet<string>(OAuthUtilities.ScopeStringComparer);
}
/// <summary>
@@ -39,6 +41,7 @@ namespace DotNetOpenAuth.OAuth2.Messages {
Contract.Requires<ArgumentNullException>(clientCallback != null, "clientCallback");
Contract.Requires<ArgumentNullException>(request != null, "request");
((IMessageWithClientState)this).ClientState = request.ClientState;
+ this.Scope = new HashSet<string>(OAuthUtilities.ScopeStringComparer);
}
/// <summary>
@@ -59,11 +62,11 @@ namespace DotNetOpenAuth.OAuth2.Messages {
internal TimeSpan? Lifetime { get; set; }
/// <summary>
- /// Gets or sets the scope of the <see cref="AccessToken"/> if one is given; otherwise the scope of the authorization code.
+ /// Gets the scope of the <see cref="AccessToken"/> if one is given; otherwise the scope of the authorization code.
/// </summary>
/// <value>The scope.</value>
- [MessagePart(Protocol.scope, IsRequired = false, AllowEmpty = true)]
- public string Scope { get; set; }
+ [MessagePart(Protocol.scope, IsRequired = false, AllowEmpty = true, Encoder = typeof(ScopeEncoder))]
+ public ICollection<string> Scope { get; private set; }
/// <summary>
/// Gets or sets the authorizing user's account name.
diff --git a/src/DotNetOpenAuth/OAuth2/OAuthStrings.Designer.cs b/src/DotNetOpenAuth/OAuth2/OAuthStrings.Designer.cs
index 59099be..f4a74cc 100644
--- a/src/DotNetOpenAuth/OAuth2/OAuthStrings.Designer.cs
+++ b/src/DotNetOpenAuth/OAuth2/OAuthStrings.Designer.cs
@@ -122,5 +122,14 @@ namespace DotNetOpenAuth.OAuth2 {
return ResourceManager.GetString("NoGrantNoRefreshToken", resourceCulture);
}
}
+
+ /// <summary>
+ /// Looks up a localized string similar to Individual scopes may not contain spaces..
+ /// </summary>
+ internal static string ScopesMayNotContainSpaces {
+ get {
+ return ResourceManager.GetString("ScopesMayNotContainSpaces", resourceCulture);
+ }
+ }
}
}
diff --git a/src/DotNetOpenAuth/OAuth2/OAuthStrings.resx b/src/DotNetOpenAuth/OAuth2/OAuthStrings.resx
index d88f451..dacd9fc 100644
--- a/src/DotNetOpenAuth/OAuth2/OAuthStrings.resx
+++ b/src/DotNetOpenAuth/OAuth2/OAuthStrings.resx
@@ -138,4 +138,7 @@
<data name="NoGrantNoRefreshToken" xml:space="preserve">
<value>Refresh tokens should not be granted without the request including an access grant.</value>
</data>
+ <data name="ScopesMayNotContainSpaces" xml:space="preserve">
+ <value>Individual scopes may not contain spaces.</value>
+ </data>
</root> \ No newline at end of file
diff --git a/src/DotNetOpenAuth/OAuth2/OAuthUtilities.cs b/src/DotNetOpenAuth/OAuth2/OAuthUtilities.cs
index 609e0a6..6b4c42a 100644
--- a/src/DotNetOpenAuth/OAuth2/OAuthUtilities.cs
+++ b/src/DotNetOpenAuth/OAuth2/OAuthUtilities.cs
@@ -34,6 +34,8 @@ namespace DotNetOpenAuth.OAuth2 {
MessagingUtilities.Digits +
@"!#$%&'()*+-./:<=>?@[]^_`{|}~\,;";
+ public static readonly StringComparer ScopeStringComparer = StringComparer.Ordinal;
+
/// <summary>
/// Determines whether one given scope is a subset of another scope.
/// </summary>
@@ -64,14 +66,17 @@ namespace DotNetOpenAuth.OAuth2 {
/// <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");
-
+ public static HashSet<string> SplitScopes(string scope) {
if (string.IsNullOrEmpty(scope)) {
return new HashSet<string>();
}
- return new HashSet<string>(scope.Split(scopeDelimiter, StringSplitOptions.RemoveEmptyEntries), scopeComparer);
+ return new HashSet<string>(scope.Split(scopeDelimiter, StringSplitOptions.RemoveEmptyEntries), ScopeStringComparer);
+ }
+
+ public static string JoinScopes(HashSet<string> scopes) {
+ Contract.Requires<ArgumentNullException>(scopes != null, "scopes");
+ return string.Join(" ", scopes.ToArray());
}
/// <summary>
diff --git a/src/DotNetOpenAuth/OAuth2/ResourceServer.cs b/src/DotNetOpenAuth/OAuth2/ResourceServer.cs
index f013a5e..a2afdae 100644
--- a/src/DotNetOpenAuth/OAuth2/ResourceServer.cs
+++ b/src/DotNetOpenAuth/OAuth2/ResourceServer.cs
@@ -63,7 +63,7 @@ namespace DotNetOpenAuth.OAuth2 {
/// <param name="username">The name on the account the client has access to.</param>
/// <param name="scope">The set of operations the client is authorized for.</param>
/// <returns>An error to return to the client if access is not authorized; <c>null</c> if access is granted.</returns>
- public OutgoingWebResponse VerifyAccess(out string username, out string scope) {
+ public OutgoingWebResponse VerifyAccess(out string username, out HashSet<string> scope) {
return this.VerifyAccess(this.Channel.GetRequestFromContext(), out username, out scope);
}
@@ -76,7 +76,7 @@ namespace DotNetOpenAuth.OAuth2 {
/// <returns>
/// An error to return to the client if access is not authorized; <c>null</c> if access is granted.
/// </returns>
- public virtual OutgoingWebResponse VerifyAccess(HttpRequestInfo httpRequestInfo, out string username, out string scope) {
+ public virtual OutgoingWebResponse VerifyAccess(HttpRequestInfo httpRequestInfo, out string username, out HashSet<string> scope) {
Contract.Requires<ArgumentNullException>(httpRequestInfo != null, "httpRequestInfo");
AccessProtectedResourceRequest request = null;
@@ -113,10 +113,11 @@ namespace DotNetOpenAuth.OAuth2 {
/// An error to return to the client if access is not authorized; <c>null</c> if access is granted.
/// </returns>
public virtual OutgoingWebResponse VerifyAccess(HttpRequestInfo httpRequestInfo, out IPrincipal principal) {
- string username, scope;
+ string username;
+ HashSet<string> scope;
var result = this.VerifyAccess(httpRequestInfo, out username, out scope);
if (result == null) {
- principal = new OAuth.ChannelElements.OAuthPrincipal(username, scope != null ? scope.Split(' ') : new string[0]);
+ principal = new OAuth.ChannelElements.OAuthPrincipal(username, scope != null ? scope.ToArray() : new string[0]);
} else {
principal = null;
}
diff --git a/src/DotNetOpenAuth/OAuth2/StandardAccessTokenAnalyzer.cs b/src/DotNetOpenAuth/OAuth2/StandardAccessTokenAnalyzer.cs
index 5e0ea94..d4d4c2f 100644
--- a/src/DotNetOpenAuth/OAuth2/StandardAccessTokenAnalyzer.cs
+++ b/src/DotNetOpenAuth/OAuth2/StandardAccessTokenAnalyzer.cs
@@ -6,6 +6,7 @@
namespace DotNetOpenAuth.OAuth2 {
using System;
+ using System.Collections.Generic;
using System.Security.Cryptography;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.OAuth2.ChannelElements;
@@ -50,11 +51,11 @@ 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 virtual bool TryValidateAccessToken(IDirectedProtocolMessage message, string accessToken, out string user, out string scope) {
+ public virtual bool TryValidateAccessToken(IDirectedProtocolMessage message, string accessToken, out string user, out HashSet<string> scope) {
var accessTokenFormatter = AccessToken.CreateFormatter(this.AuthorizationServerPublicSigningKey, this.ResourceServerPrivateEncryptionKey);
var token = accessTokenFormatter.Deserialize(message, accessToken);
user = token.User;
- scope = token.Scope;
+ scope = new HashSet<string>(token.Scope, OAuthUtilities.ScopeStringComparer);
return true;
}
}
diff --git a/src/DotNetOpenAuth/OAuth2/UserAgentClient.cs b/src/DotNetOpenAuth/OAuth2/UserAgentClient.cs
index b848ec4..367a36a 100644
--- a/src/DotNetOpenAuth/OAuth2/UserAgentClient.cs
+++ b/src/DotNetOpenAuth/OAuth2/UserAgentClient.cs
@@ -42,8 +42,8 @@ namespace DotNetOpenAuth.OAuth2 {
/// </summary>
/// <param name="scope">The scope of authorized access requested.</param>
/// <returns>A fully-qualified URL suitable to initiate the authorization flow.</returns>
- public Uri RequestUserAuthorization(string scope = null) {
- var authorization = new AuthorizationState { Scope = scope };
+ public Uri RequestUserAuthorization(IEnumerable<string> scope = null) {
+ var authorization = new AuthorizationState(scope);
return this.RequestUserAuthorization(authorization);
}
@@ -63,9 +63,9 @@ namespace DotNetOpenAuth.OAuth2 {
var request = new EndUserAuthorizationRequest(this.AuthorizationServer) {
ClientIdentifier = this.ClientIdentifier,
- Scope = authorization.Scope,
Callback = authorization.Callback,
};
+ request.Scope.ResetContents(authorization.Scope);
return this.Channel.PrepareResponse(request).GetDirectUriRequest(this.Channel);
}
diff --git a/src/DotNetOpenAuth/OAuth2/WebServerClient.cs b/src/DotNetOpenAuth/OAuth2/WebServerClient.cs
index 061c58c..091b022 100644
--- a/src/DotNetOpenAuth/OAuth2/WebServerClient.cs
+++ b/src/DotNetOpenAuth/OAuth2/WebServerClient.cs
@@ -40,7 +40,7 @@ namespace DotNetOpenAuth.OAuth2 {
/// </summary>
/// <param name="scope">The scope of authorized access requested.</param>
/// <returns>The authorization request as an HTTP response that causes a redirect.</returns>
- public OutgoingWebResponse RequestUserAuthorization(string scope = null) {
+ public OutgoingWebResponse RequestUserAuthorization(IEnumerable<string> scope = null) {
var response = this.PrepareRequestUserAuthorization(scope);
return this.Channel.PrepareResponse(response);
}
@@ -50,8 +50,8 @@ namespace DotNetOpenAuth.OAuth2 {
/// </summary>
/// <param name="scope">The scope of authorized access requested.</param>
/// <returns>The authorization request.</returns>
- public EndUserAuthorizationRequest PrepareRequestUserAuthorization(string scope = null) {
- var authorizationState = new AuthorizationState { Scope = scope };
+ public EndUserAuthorizationRequest PrepareRequestUserAuthorization(IEnumerable<string> scopes = null) {
+ var authorizationState = new AuthorizationState(scopes);
return this.PrepareRequestUserAuthorization(authorizationState);
}
@@ -78,8 +78,8 @@ namespace DotNetOpenAuth.OAuth2 {
var request = new EndUserAuthorizationRequest(this.AuthorizationServer) {
ClientIdentifier = this.ClientIdentifier,
Callback = authorization.Callback,
- Scope = authorization.Scope,
};
+ request.Scope.ResetContents(authorization.Scope);
return request;
}