summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenAuth.OAuth2/OAuth2
diff options
context:
space:
mode:
Diffstat (limited to 'src/DotNetOpenAuth.OAuth2/OAuth2')
-rw-r--r--src/DotNetOpenAuth.OAuth2/OAuth2/IScopeSatisfiedCheck.cs37
-rw-r--r--src/DotNetOpenAuth.OAuth2/OAuth2/Messages/UnauthorizedResponse.cs194
-rw-r--r--src/DotNetOpenAuth.OAuth2/OAuth2/OAuthStrings.Designer.cs20
-rw-r--r--src/DotNetOpenAuth.OAuth2/OAuth2/OAuthStrings.resx10
-rw-r--r--src/DotNetOpenAuth.OAuth2/OAuth2/OAuthUtilities.cs45
-rw-r--r--src/DotNetOpenAuth.OAuth2/OAuth2/Protocol.cs34
-rw-r--r--src/DotNetOpenAuth.OAuth2/OAuth2/StandardScopeSatisfiedCheck.cs37
7 files changed, 300 insertions, 77 deletions
diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/IScopeSatisfiedCheck.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/IScopeSatisfiedCheck.cs
new file mode 100644
index 0000000..c1fe3e4
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth2/OAuth2/IScopeSatisfiedCheck.cs
@@ -0,0 +1,37 @@
+//-----------------------------------------------------------------------
+// <copyright file="IScopeSatisfiedCheck.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth2 {
+ using System.Collections.Generic;
+
+ /// <summary>
+ /// An extensibility point that allows authorization servers and resource servers to customize how scopes may be considered
+ /// supersets of each other.
+ /// </summary>
+ /// <remarks>
+ /// Implementations must be thread-safe.
+ /// </remarks>
+ public interface IScopeSatisfiedCheck {
+ /// <summary>
+ /// Checks whether the granted scope is a superset of the required scope.
+ /// </summary>
+ /// <param name="requiredScope">The set of strings that the resource server demands in an access token's scope in order to complete some operation.</param>
+ /// <param name="grantedScope">The set of strings that define the scope within an access token that the client is authorized to.</param>
+ /// <returns><c>true</c> if <paramref name="grantedScope"/> is a superset of <paramref name="requiredScope"/> to allow the request to proceed; <c>false</c> otherwise.</returns>
+ /// <remarks>
+ /// The default reasonable implementation of this is:
+ /// <code>
+ /// return <paramref name="grantedScope"/>.IsSupersetOf(<paramref name="requiredScope"/>);
+ /// </code>
+ /// <para>In some advanced cases it may not be so simple. One case is that there may be a string that aggregates the capabilities of several others
+ /// in order to simplify common scenarios. For example, the scope "ReadAll" may represent the same authorization as "ReadProfile", "ReadEmail", and
+ /// "ReadFriends".
+ /// </para>
+ /// <para>Great care should be taken in implementing this method as this is a critical security module for the authorization and resource servers.</para>
+ /// </remarks>
+ bool IsScopeSatisfied(ISet<string> requiredScope, ISet<string> grantedScope);
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/UnauthorizedResponse.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/UnauthorizedResponse.cs
index 3f4bb5b..e73f3cf 100644
--- a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/UnauthorizedResponse.cs
+++ b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/UnauthorizedResponse.cs
@@ -6,110 +6,204 @@
namespace DotNetOpenAuth.OAuth2.Messages {
using System;
+ using System.Collections.Generic;
using System.Diagnostics.Contracts;
+ using System.Globalization;
using System.Net;
using System.Text;
using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OAuth2.ChannelElements;
/// <summary>
- /// A direct response that is simply a 401 Unauthorized with an
- /// WWW-Authenticate: OAuth header.
+ /// A direct response sent in response to a rejected Bearer access token.
/// </summary>
- internal class UnauthorizedResponse : MessageBase, IHttpDirectResponse {
+ /// <remarks>
+ /// This satisfies the spec in: http://self-issued.info/docs/draft-ietf-oauth-v2-bearer.html#authn-header
+ /// </remarks>
+ public class UnauthorizedResponse : MessageBase, IHttpDirectResponse {
/// <summary>
- /// Initializes a new instance of the <see cref="UnauthorizedResponse"/> class.
+ /// The headers in the response message.
/// </summary>
- /// <param name="exception">The exception.</param>
- /// <param name="version">The protocol version.</param>
- internal UnauthorizedResponse(ProtocolException exception, Version version = null)
- : base(version ?? Protocol.Default.Version) {
- Requires.NotNull(exception, "exception");
- this.ErrorMessage = exception.Message;
- }
+ private readonly WebHeaderCollection headers = new WebHeaderCollection();
/// <summary>
/// Initializes a new instance of the <see cref="UnauthorizedResponse"/> class.
/// </summary>
- /// <param name="request">The request.</param>
- internal UnauthorizedResponse(IDirectedProtocolMessage request)
- : base(request) {
- this.Realm = "Service";
+ /// <param name="version">The protocol version.</param>
+ protected UnauthorizedResponse(Version version = null)
+ : base(version ?? Protocol.Default.Version) {
}
/// <summary>
/// Initializes a new instance of the <see cref="UnauthorizedResponse"/> class.
/// </summary>
/// <param name="request">The request.</param>
- /// <param name="exception">The exception.</param>
- internal UnauthorizedResponse(IDirectedProtocolMessage request, ProtocolException exception)
- : this(request) {
- Requires.NotNull(exception, "exception");
- this.ErrorMessage = exception.Message;
+ protected UnauthorizedResponse(IDirectedProtocolMessage request)
+ : base(request) {
}
#region IHttpDirectResponse Members
/// <summary>
- /// Gets the HTTP status code that the direct response should be sent with.
+ /// Gets or sets the HTTP status code that the direct response should be sent with.
/// </summary>
- HttpStatusCode IHttpDirectResponse.HttpStatusCode {
- get { return HttpStatusCode.Unauthorized; }
- }
+ public HttpStatusCode HttpStatusCode { get; set; }
/// <summary>
/// Gets the HTTP headers to add to the response.
/// </summary>
/// <value>May be an empty collection, but must not be <c>null</c>.</value>
- WebHeaderCollection IHttpDirectResponse.Headers {
- get {
- return new WebHeaderCollection() {
- { HttpResponseHeader.WwwAuthenticate, Protocol.BearerHttpAuthorizationScheme },
- };
- }
+ public WebHeaderCollection Headers {
+ get { return this.headers; }
}
#endregion
/// <summary>
- /// Gets or sets the error message.
+ /// Gets or sets the well known error code.
/// </summary>
- /// <value>The error message.</value>
- [MessagePart("error")]
- internal string ErrorMessage { get; set; }
+ /// <value>One of the values from <see cref="Protocol.BearerTokenErrorCodes"/>.</value>
+ [MessagePart(Protocol.BearerTokenUnauthorizedResponseParameters.ErrorCode)]
+ public string ErrorCode { get; set; }
+
+ /// <summary>
+ /// Gets or sets a human-readable explanation for developers that is not meant to be displayed to end users.
+ /// </summary>
+ [MessagePart(Protocol.BearerTokenUnauthorizedResponseParameters.ErrorDescription)]
+ public string ErrorDescription { get; set; }
+
+ /// <summary>
+ /// Gets or sets an absolute URI identifying a human-readable web page explaining the error.
+ /// </summary>
+ [MessagePart(Protocol.BearerTokenUnauthorizedResponseParameters.ErrorUri)]
+ public Uri ErrorUri { get; set; }
/// <summary>
/// Gets or sets the realm.
/// </summary>
/// <value>The realm.</value>
- [MessagePart("realm")]
- internal string Realm { get; set; }
+ [MessagePart(Protocol.BearerTokenUnauthorizedResponseParameters.Realm)]
+ public string Realm { get; set; }
/// <summary>
/// Gets or sets the scope.
/// </summary>
/// <value>The scope.</value>
- [MessagePart("scope")]
- internal string Scope { get; set; }
+ [MessagePart(Protocol.BearerTokenUnauthorizedResponseParameters.Scope, Encoder = typeof(ScopeEncoder))]
+ public ISet<string> Scope { get; set; }
+
+ /// <summary>
+ /// Gets the scheme to use in the WWW-Authenticate header.
+ /// </summary>
+ internal virtual string Scheme {
+ get { return Protocol.BearerHttpAuthorizationScheme; }
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="UnauthorizedResponse"/> class
+ /// to inform the client that the request was invalid.
+ /// </summary>
+ /// <param name="exception">The exception.</param>
+ /// <param name="version">The version of OAuth 2 that is in use.</param>
+ /// <returns>The error message.</returns>
+ internal static UnauthorizedResponse InvalidRequest(ProtocolException exception, Version version = null) {
+ Requires.NotNull(exception, "exception");
+ var message = new UnauthorizedResponse(version) {
+ ErrorCode = Protocol.BearerTokenErrorCodes.InvalidRequest,
+ ErrorDescription = exception.Message,
+ HttpStatusCode = System.Net.HttpStatusCode.BadRequest,
+ };
+
+ return message;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="UnauthorizedResponse"/> class
+ /// to inform the client that the bearer token included in the request was rejected.
+ /// </summary>
+ /// <param name="request">The request.</param>
+ /// <param name="exception">The exception.</param>
+ /// <returns>The error message.</returns>
+ internal static UnauthorizedResponse InvalidToken(IDirectedProtocolMessage request, ProtocolException exception) {
+ Requires.NotNull(request, "request");
+ Requires.NotNull(exception, "exception");
+ var message = new UnauthorizedResponse(request) {
+ ErrorCode = Protocol.BearerTokenErrorCodes.InvalidToken,
+ ErrorDescription = exception.Message,
+ HttpStatusCode = System.Net.HttpStatusCode.Unauthorized,
+ };
+
+ return message;
+ }
/// <summary>
- /// Gets or sets the algorithms.
+ /// Initializes a new instance of the <see cref="UnauthorizedResponse"/> class
+ /// to inform the client of the required set of scopes required to perform this operation.
/// </summary>
- /// <value>The algorithms.</value>
- [MessagePart("algorithms")]
- internal string Algorithms { get; set; }
+ /// <param name="request">The request.</param>
+ /// <param name="requiredScopes">The set of scopes required to perform this operation.</param>
+ /// <returns>The error message.</returns>
+ internal static UnauthorizedResponse InsufficientScope(IDirectedProtocolMessage request, ISet<string> requiredScopes) {
+ Requires.NotNull(request, "request");
+ Requires.NotNull(requiredScopes, "requiredScopes");
+ var message = new UnauthorizedResponse(request) {
+ HttpStatusCode = System.Net.HttpStatusCode.Forbidden,
+ ErrorCode = Protocol.BearerTokenErrorCodes.InsufficientScope,
+ Scope = requiredScopes,
+ };
+ return message;
+ }
/// <summary>
- /// Gets or sets the user endpoint.
+ /// Ensures the message is valid.
/// </summary>
- /// <value>The user endpoint.</value>
- [MessagePart("user-uri")]
- internal Uri UserEndpoint { get; set; }
+ protected override void EnsureValidMessage() {
+ base.EnsureValidMessage();
+
+ // Make sure the characters used in the supplied parameters satisfy requirements.
+ VerifyErrorCodeOrDescription(this.ErrorCode, Protocol.BearerTokenUnauthorizedResponseParameters.ErrorCode);
+ VerifyErrorCodeOrDescription(this.ErrorDescription, Protocol.BearerTokenUnauthorizedResponseParameters.ErrorDescription);
+ VerifyErrorUri(this.ErrorUri);
+
+ // Ensure that at least one parameter is specified, as required in the spec.
+ ErrorUtilities.VerifyProtocol(
+ this.ErrorCode != null || this.ErrorDescription != null || this.ErrorUri != null || this.Realm != null || this.Scope != null,
+ OAuthStrings.BearerTokenUnauthorizedAtLeastOneParameterRequired);
+ }
/// <summary>
- /// Gets or sets the token endpoint.
+ /// Ensures the error or error_description parameters contain only allowed characters.
/// </summary>
- /// <value>The token endpoint.</value>
- [MessagePart("token-uri")]
- internal Uri TokenEndpoint { get; set; }
+ /// <param name="value">The argument.</param>
+ /// <param name="parameterName">The name of the parameter being validated. Used when errors are reported.</param>
+ private static void VerifyErrorCodeOrDescription(string value, string parameterName) {
+ if (value != null) {
+ for (int i = 0; i < value.Length; i++) {
+ // The allowed set of characters comes from http://self-issued.info/docs/draft-ietf-oauth-v2-bearer.html#authn-header
+ char ch = value[i];
+ if (!((ch >= '\x20' && ch <= '\x21') || (ch >= '\x23' && ch <= '\x5B') || (ch >= '\x5D' && ch <= '\x7E'))) {
+ ErrorUtilities.ThrowProtocol(OAuthStrings.ParameterContainsIllegalCharacters, parameterName, ch);
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Ensures the error_uri parameter contains only allowed characters and is an absolute URI.
+ /// </summary>
+ /// <param name="valueUri">The absolute URI.</param>
+ private static void VerifyErrorUri(Uri valueUri) {
+ if (valueUri != null) {
+ ErrorUtilities.VerifyProtocol(valueUri.IsAbsoluteUri, OAuthStrings.AbsoluteUriRequired);
+ string value = valueUri.AbsoluteUri;
+ for (int i = 0; i < value.Length; i++) {
+ // The allowed set of characters comes from http://self-issued.info/docs/draft-ietf-oauth-v2-bearer.html#authn-header
+ char ch = value[i];
+ if (!(ch == '\x21' || (ch >= '\x23' && ch <= '\x5B') || (ch >= '\x5D' && ch <= '\x7E'))) {
+ ErrorUtilities.ThrowProtocol(OAuthStrings.ParameterContainsIllegalCharacters, Protocol.BearerTokenUnauthorizedResponseParameters.ErrorUri, ch);
+ }
+ }
+ }
+ }
}
}
diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/OAuthStrings.Designer.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/OAuthStrings.Designer.cs
index 051d0d5..b440c1f 100644
--- a/src/DotNetOpenAuth.OAuth2/OAuth2/OAuthStrings.Designer.cs
+++ b/src/DotNetOpenAuth.OAuth2/OAuth2/OAuthStrings.Designer.cs
@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
-// Runtime Version:4.0.30319.17614
+// Runtime Version:4.0.30319.17622
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
@@ -79,6 +79,15 @@ namespace DotNetOpenAuth.OAuth2 {
}
/// <summary>
+ /// Looks up a localized string similar to At least one parameter is required for the Bearer scheme in its WWW-Authenticate header..
+ /// </summary>
+ internal static string BearerTokenUnauthorizedAtLeastOneParameterRequired {
+ get {
+ return ResourceManager.GetString("BearerTokenUnauthorizedAtLeastOneParameterRequired", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to This message can only be sent over HTTPS..
/// </summary>
internal static string HttpsRequired {
@@ -106,6 +115,15 @@ namespace DotNetOpenAuth.OAuth2 {
}
/// <summary>
+ /// Looks up a localized string similar to The &apos;{0}&apos; parameter contains the illegal character &apos;{1}&apos;..
+ /// </summary>
+ internal static string ParameterContainsIllegalCharacters {
+ get {
+ return ResourceManager.GetString("ParameterContainsIllegalCharacters", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to The return value of {0}.{1} should never be null..
/// </summary>
internal static string ResultShouldNotBeNull {
diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/OAuthStrings.resx b/src/DotNetOpenAuth.OAuth2/OAuth2/OAuthStrings.resx
index 4d9d248..4298af6 100644
--- a/src/DotNetOpenAuth.OAuth2/OAuth2/OAuthStrings.resx
+++ b/src/DotNetOpenAuth.OAuth2/OAuth2/OAuthStrings.resx
@@ -112,10 +112,10 @@
<value>2.0</value>
</resheader>
<resheader name="reader">
- <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
- <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="AbsoluteUriRequired" xml:space="preserve">
<value>The value for message part "{0}" must be an absolute URI.</value>
@@ -123,6 +123,9 @@
<data name="AccessTokenInvalidForHttpAuthorizationHeader" xml:space="preserve">
<value>The access token contains characters that must not appear in the HTTP Authorization header.</value>
</data>
+ <data name="BearerTokenUnauthorizedAtLeastOneParameterRequired" xml:space="preserve">
+ <value>At least one parameter is required for the Bearer scheme in its WWW-Authenticate header.</value>
+ </data>
<data name="HttpsRequired" xml:space="preserve">
<value>This message can only be sent over HTTPS.</value>
</data>
@@ -132,6 +135,9 @@
<data name="NoGrantNoRefreshToken" xml:space="preserve">
<value>Refresh tokens should not be granted without the request including an access grant.</value>
</data>
+ <data name="ParameterContainsIllegalCharacters" xml:space="preserve">
+ <value>The '{0}' parameter contains the illegal character '{1}'.</value>
+ </data>
<data name="ResultShouldNotBeNull" xml:space="preserve">
<value>The return value of {0}.{1} should never be null.</value>
</data>
diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/OAuthUtilities.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/OAuthUtilities.cs
index 661d102..4c46f75 100644
--- a/src/DotNetOpenAuth.OAuth2/OAuth2/OAuthUtilities.cs
+++ b/src/DotNetOpenAuth.OAuth2/OAuth2/OAuthUtilities.cs
@@ -55,29 +55,6 @@ namespace DotNetOpenAuth.OAuth2 {
@"!#$%&'()*+-./:<=>?@[]^_`{|}~\,;";
/// <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 space-delimited list of scopes.</param>
@@ -97,13 +74,33 @@ namespace DotNetOpenAuth.OAuth2 {
/// </summary>
/// <param name="scopes">The scopes to serialize.</param>
/// <returns>A space-delimited list.</returns>
- public static string JoinScopes(HashSet<string> scopes) {
+ public static string JoinScopes(ISet<string> scopes) {
Requires.NotNull(scopes, "scopes");
VerifyValidScopeTokens(scopes);
return string.Join(" ", scopes.ToArray());
}
/// <summary>
+ /// Parses a space-delimited list of scopes into a set.
+ /// </summary>
+ /// <param name="scopes">The space-delimited string.</param>
+ /// <returns>A set.</returns>
+ internal static ISet<string> ParseScopeSet(string scopes) {
+ Requires.NotNull(scopes, "scopes");
+ return ParseScopeSet(scopes.Split(scopeDelimiter, StringSplitOptions.RemoveEmptyEntries));
+ }
+
+ /// <summary>
+ /// Creates a set out of an array of strings.
+ /// </summary>
+ /// <param name="scopes">The array of strings.</param>
+ /// <returns>A set.</returns>
+ internal static ISet<string> ParseScopeSet(string[] scopes) {
+ Requires.NotNull(scopes, "scopes");
+ return new HashSet<string>(scopes, StringComparer.Ordinal);
+ }
+
+ /// <summary>
/// Verifies that a sequence of scope tokens are all valid.
/// </summary>
/// <param name="scopes">The scopes.</param>
diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/Protocol.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/Protocol.cs
index 986af13..d780a81 100644
--- a/src/DotNetOpenAuth.OAuth2/OAuth2/Protocol.cs
+++ b/src/DotNetOpenAuth.OAuth2/OAuth2/Protocol.cs
@@ -297,5 +297,39 @@ namespace DotNetOpenAuth.OAuth2 {
/// </summary>
internal const string Bearer = "bearer";
}
+
+ internal static class BearerTokenUnauthorizedResponseParameters {
+ internal const string Realm = "realm";
+ internal const string ErrorCode = "error";
+ internal const string ErrorDescription = "error_description";
+ internal const string ErrorUri = "error_uri";
+ internal const string Scope = "scope";
+ }
+
+ /// <summary>
+ /// The error codes prescribed in http://self-issued.info/docs/draft-ietf-oauth-v2-bearer.html#resource-error-codes
+ /// </summary>
+ internal static class BearerTokenErrorCodes {
+ /// <summary>
+ /// The request is missing a required parameter, includes an unsupported parameter or parameter value,
+ /// repeats the same parameter, uses more than one method for including an access token, or is otherwise
+ /// malformed. The resource server SHOULD respond with the HTTP 400 (Bad Request) status code.
+ /// </summary>
+ internal const string InvalidRequest = "invalid_request";
+
+ /// <summary>
+ /// The access token provided is expired, revoked, malformed, or invalid for other reasons.
+ /// The resource SHOULD respond with the HTTP 401 (Unauthorized) status code. The client MAY request
+ /// a new access token and retry the protected resource request.
+ /// </summary>
+ internal const string InvalidToken = "invalid_token";
+
+ /// <summary>
+ /// The request requires higher privileges than provided by the access token. The resource server
+ /// SHOULD respond with the HTTP 403 (Forbidden) status code and MAY include the scope attribute
+ /// with the scope necessary to access the protected resource.
+ /// </summary>
+ internal const string InsufficientScope = "insufficient_scope";
+ }
}
}
diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/StandardScopeSatisfiedCheck.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/StandardScopeSatisfiedCheck.cs
new file mode 100644
index 0000000..1370057
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth2/OAuth2/StandardScopeSatisfiedCheck.cs
@@ -0,0 +1,37 @@
+//-----------------------------------------------------------------------
+// <copyright file="StandardScopeSatisfiedCheck.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth2 {
+ using System.Collections.Generic;
+
+ /// <summary>
+ /// The default scope superset checker, which assumes that no scopes overlap.
+ /// </summary>
+ internal class StandardScopeSatisfiedCheck : IScopeSatisfiedCheck {
+ /// <summary>
+ /// Checks whether the granted scope is a superset of the required scope.
+ /// </summary>
+ /// <param name="requiredScope">The set of strings that the resource server demands in an access token's scope in order to complete some operation.</param>
+ /// <param name="grantedScope">The set of strings that define the scope within an access token that the client is authorized to.</param>
+ /// <returns><c>true</c> if <paramref name="grantedScope"/> is a superset of <paramref name="requiredScope"/> to allow the request to proceed; <c>false</c> otherwise.</returns>
+ /// <remarks>
+ /// The default reasonable implementation of this is:
+ /// <code>
+ /// return <paramref name="grantedScope"/>.IsSupersetOf(<paramref name="requiredScope"/>);
+ /// </code>
+ /// <para>In some advanced cases it may not be so simple. One case is that there may be a string that aggregates the capabilities of several others
+ /// in order to simplify common scenarios. For example, the scope "ReadAll" may represent the same authorization as "ReadProfile", "ReadEmail", and
+ /// "ReadFriends".
+ /// </para>
+ /// <para>Great care should be taken in implementing this method as this is a critical security module for the authorization and resource servers.</para>
+ /// </remarks>
+ public bool IsScopeSatisfied(ISet<string> requiredScope, ISet<string> grantedScope) {
+ Requires.NotNull(requiredScope, "requiredScope");
+ Requires.NotNull(grantedScope, "grantedScope");
+ return grantedScope.IsSupersetOf(requiredScope);
+ }
+ }
+}