summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2
diff options
context:
space:
mode:
Diffstat (limited to 'src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2')
-rw-r--r--src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/IAccessTokenAnalyzer.cs23
-rw-r--r--src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/OAuth2Strings.Designer.cs20
-rw-r--r--src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/OAuth2Strings.resx6
-rw-r--r--src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/ResourceServer.cs63
-rw-r--r--src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/StandardAccessTokenAnalyzer.cs19
5 files changed, 83 insertions, 48 deletions
diff --git a/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/IAccessTokenAnalyzer.cs b/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/IAccessTokenAnalyzer.cs
index 5aa1bb6..c153bfa 100644
--- a/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/IAccessTokenAnalyzer.cs
+++ b/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/IAccessTokenAnalyzer.cs
@@ -23,13 +23,12 @@ namespace DotNetOpenAuth.OAuth2 {
/// Reads an access token to find out what data it authorizes access to.
/// </summary>
/// <param name="message">The message carrying the access token.</param>
- /// <param name="accessToken">The access token.</param>
- /// <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>
+ /// <param name="accessToken">The access token's serialized representation.</param>
+ /// <returns>The deserialized, validated token.</returns>
+ /// <exception cref="ProtocolException">Thrown if the access token is expired, invalid, or from an untrusted authorization server.</exception>
[SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#", Justification = "Try pattern")]
[SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#", Justification = "Try pattern")]
- bool TryValidateAccessToken(IDirectedProtocolMessage message, string accessToken, out string user, out HashSet<string> scope);
+ AccessToken DeserializeAccessToken(IDirectedProtocolMessage message, string accessToken);
}
/// <summary>
@@ -47,17 +46,13 @@ namespace DotNetOpenAuth.OAuth2 {
/// Reads an access token to find out what data it authorizes access to.
/// </summary>
/// <param name="message">The message carrying the access token.</param>
- /// <param name="accessToken">The access token.</param>
- /// <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 IAccessTokenAnalyzer.TryValidateAccessToken(IDirectedProtocolMessage message, string accessToken, out string user, out HashSet<string> scope) {
+ /// <param name="accessToken">The access token's serialized representation.</param>
+ /// <returns>The deserialized, validated token.</returns>
+ /// <exception cref="ProtocolException">Thrown if the access token is expired, invalid, or from an untrusted authorization server.</exception>
+ AccessToken IAccessTokenAnalyzer.DeserializeAccessToken(IDirectedProtocolMessage message, string accessToken) {
Requires.NotNull(message, "message");
Requires.NotNullOrEmpty(accessToken, "accessToken");
- Contract.Ensures(Contract.Result<bool>() == (Contract.ValueAtReturn<string>(out user) != null));
-
+ Contract.Ensures(Contract.Result<AccessToken>() != null);
throw new NotImplementedException();
}
}
diff --git a/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/OAuth2Strings.Designer.cs b/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/OAuth2Strings.Designer.cs
index 69104eb..9df9f2d 100644
--- a/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/OAuth2Strings.Designer.cs
+++ b/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/OAuth2Strings.Designer.cs
@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
-// Runtime Version:4.0.30319.239
+// Runtime Version:4.0.30319.17614
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
@@ -61,6 +61,15 @@ namespace DotNetOpenAuth.OAuth2 {
}
/// <summary>
+ /// Looks up a localized string similar to Client Identifier starts with a resource owner prefix. Authorization aborted..
+ /// </summary>
+ internal static string ClientIdentifierLooksLikeResourceOwnerName {
+ get {
+ return ResourceManager.GetString("ClientIdentifierLooksLikeResourceOwnerName", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to Invalid access token..
/// </summary>
internal static string InvalidAccessToken {
@@ -77,5 +86,14 @@ namespace DotNetOpenAuth.OAuth2 {
return ResourceManager.GetString("MissingAccessToken", resourceCulture);
}
}
+
+ /// <summary>
+ /// Looks up a localized string similar to Resource owner username starts with a client prefix. Authorization aborted..
+ /// </summary>
+ internal static string ResourceOwnerNameLooksLikeClientIdentifier {
+ get {
+ return ResourceManager.GetString("ResourceOwnerNameLooksLikeClientIdentifier", resourceCulture);
+ }
+ }
}
}
diff --git a/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/OAuth2Strings.resx b/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/OAuth2Strings.resx
index 175a386..46943c4 100644
--- a/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/OAuth2Strings.resx
+++ b/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/OAuth2Strings.resx
@@ -117,10 +117,16 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
+ <data name="ClientIdentifierLooksLikeResourceOwnerName" xml:space="preserve">
+ <value>Client Identifier starts with a resource owner prefix. Authorization aborted.</value>
+ </data>
<data name="InvalidAccessToken" xml:space="preserve">
<value>Invalid access token.</value>
</data>
<data name="MissingAccessToken" xml:space="preserve">
<value>Missing access token.</value>
</data>
+ <data name="ResourceOwnerNameLooksLikeClientIdentifier" xml:space="preserve">
+ <value>Resource owner username starts with a client prefix. Authorization aborted.</value>
+ </data>
</root> \ No newline at end of file
diff --git a/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/ResourceServer.cs b/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/ResourceServer.cs
index 79cbbd7..c2e48b8 100644
--- a/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/ResourceServer.cs
+++ b/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/ResourceServer.cs
@@ -34,6 +34,8 @@ namespace DotNetOpenAuth.OAuth2 {
this.AccessTokenAnalyzer = accessTokenAnalyzer;
this.Channel = new OAuth2ResourceServerChannel();
+ this.ResourceOwnerPrincipalPrefix = string.Empty;
+ this.ClientPrincipalPrefix = "client:";
}
/// <summary>
@@ -43,6 +45,18 @@ namespace DotNetOpenAuth.OAuth2 {
public IAccessTokenAnalyzer AccessTokenAnalyzer { get; private set; }
/// <summary>
+ /// Gets or sets the prefix to apply to a resource owner's username when used as the username in an <see cref="IPrincipal"/>.
+ /// </summary>
+ /// <value>The default value is the empty string.</value>
+ public string ResourceOwnerPrincipalPrefix { get; set; }
+
+ /// <summary>
+ /// Gets or sets the prefix to apply to a client identifier when used as the username in an <see cref="IPrincipal"/>.
+ /// </summary>
+ /// <value>The default value is "client:"</value>
+ public string ClientPrincipalPrefix { get; set; }
+
+ /// <summary>
/// Gets the channel.
/// </summary>
/// <value>The channel.</value>
@@ -51,50 +65,48 @@ namespace DotNetOpenAuth.OAuth2 {
/// <summary>
/// Discovers what access the client should have considering the access token in the current request.
/// </summary>
- /// <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>
+ /// <param name="accessToken">Receives the access token describing the authorization the client has.</param>
/// <returns>An error to return to the client if access is not authorized; <c>null</c> if access is granted.</returns>
[SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "0#", Justification = "Try pattern")]
[SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#", Justification = "Try pattern")]
- public OutgoingWebResponse VerifyAccess(out string userName, out HashSet<string> scope) {
- return this.VerifyAccess(this.Channel.GetRequestFromContext(), out userName, out scope);
+ public OutgoingWebResponse VerifyAccess(out AccessToken accessToken) {
+ return this.VerifyAccess(this.Channel.GetRequestFromContext(), out accessToken);
}
/// <summary>
/// Discovers what access the client should have considering the access token in the current request.
/// </summary>
/// <param name="httpRequestInfo">The HTTP request info.</param>
- /// <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>
+ /// <param name="accessToken">Receives the access token describing the authorization the client has.</param>
/// <returns>
/// An error to return to the client if access is not authorized; <c>null</c> if access is granted.
/// </returns>
[SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#", Justification = "Try pattern")]
[SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#", Justification = "Try pattern")]
- public virtual OutgoingWebResponse VerifyAccess(HttpRequestBase httpRequestInfo, out string userName, out HashSet<string> scope) {
+ public virtual OutgoingWebResponse VerifyAccess(HttpRequestBase httpRequestInfo, out AccessToken accessToken) {
Requires.NotNull(httpRequestInfo, "httpRequestInfo");
AccessProtectedResourceRequest request = null;
try {
if (this.Channel.TryReadFromRequest<AccessProtectedResourceRequest>(httpRequestInfo, out request)) {
- if (this.AccessTokenAnalyzer.TryValidateAccessToken(request, request.AccessToken, out userName, out scope)) {
- // No errors to return.
- return null;
+ accessToken = this.AccessTokenAnalyzer.DeserializeAccessToken(request, request.AccessToken);
+ ErrorUtilities.VerifyHost(accessToken != null, "IAccessTokenAnalyzer.DeserializeAccessToken returned a null reslut.");
+ if (string.IsNullOrEmpty(accessToken.User) && string.IsNullOrEmpty(accessToken.ClientIdentifier)) {
+ Logger.OAuth.Error("Access token rejected because both the username and client id properties were null or empty.");
+ ErrorUtilities.ThrowProtocol(OAuth2Strings.InvalidAccessToken);
}
- throw ErrorUtilities.ThrowProtocol(OAuth2Strings.InvalidAccessToken);
+ return null;
} else {
var response = new UnauthorizedResponse(new ProtocolException(OAuth2Strings.MissingAccessToken));
- userName = null;
- scope = null;
+ accessToken = null;
return this.Channel.PrepareResponse(response);
}
} catch (ProtocolException ex) {
var response = request != null ? new UnauthorizedResponse(request, ex) : new UnauthorizedResponse(ex);
- userName = null;
- scope = null;
+ accessToken = null;
return this.Channel.PrepareResponse(response);
}
}
@@ -109,10 +121,23 @@ namespace DotNetOpenAuth.OAuth2 {
/// </returns>
[SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#", Justification = "Try pattern")]
public virtual OutgoingWebResponse VerifyAccess(HttpRequestBase httpRequestInfo, out IPrincipal principal) {
- string username;
- HashSet<string> scope;
- var result = this.VerifyAccess(httpRequestInfo, out username, out scope);
- principal = result == null ? new OAuthPrincipal(username, scope != null ? scope.ToArray() : new string[0]) : null;
+ AccessToken accessToken;
+ var result = this.VerifyAccess(httpRequestInfo, out accessToken);
+ if (result == null) {
+ // Mitigates attacks on this approach of differentiating clients from resource owners
+ // by checking that a username doesn't look suspiciously engineered to appear like the other type.
+ ErrorUtilities.VerifyProtocol(accessToken.User == null || string.IsNullOrEmpty(this.ClientPrincipalPrefix) || !accessToken.User.StartsWith(this.ClientPrincipalPrefix, StringComparison.OrdinalIgnoreCase), OAuth2Strings.ResourceOwnerNameLooksLikeClientIdentifier);
+ ErrorUtilities.VerifyProtocol(accessToken.ClientIdentifier == null || string.IsNullOrEmpty(this.ResourceOwnerPrincipalPrefix) || !accessToken.ClientIdentifier.StartsWith(this.ResourceOwnerPrincipalPrefix, StringComparison.OrdinalIgnoreCase), OAuth2Strings.ClientIdentifierLooksLikeResourceOwnerName);
+
+ string principalUserName = !string.IsNullOrEmpty(accessToken.User)
+ ? this.ResourceOwnerPrincipalPrefix + accessToken.User
+ : this.ClientPrincipalPrefix + accessToken.ClientIdentifier;
+ string[] principalScope = accessToken.Scope != null ? accessToken.Scope.ToArray() : new string[0];
+ principal = new OAuthPrincipal(principalUserName, principalScope);
+ } else {
+ principal = null;
+ }
+
return result;
}
diff --git a/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/StandardAccessTokenAnalyzer.cs b/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/StandardAccessTokenAnalyzer.cs
index 636f490..992e93c 100644
--- a/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/StandardAccessTokenAnalyzer.cs
+++ b/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/StandardAccessTokenAnalyzer.cs
@@ -45,22 +45,13 @@ namespace DotNetOpenAuth.OAuth2 {
/// Reads an access token to find out what data it authorizes access to.
/// </summary>
/// <param name="message">The message carrying the access token.</param>
- /// <param name="accessToken">The access token.</param>
- /// <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>
- /// <remarks>
- /// 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 HashSet<string> scope) {
+ /// <param name="accessToken">The access token's serialized representation.</param>
+ /// <returns>The deserialized, validated token.</returns>
+ /// <exception cref="ProtocolException">Thrown if the access token is expired, invalid, or from an untrusted authorization server.</exception>
+ public virtual AccessToken DeserializeAccessToken(IDirectedProtocolMessage message, string accessToken) {
var accessTokenFormatter = AccessToken.CreateFormatter(this.AuthorizationServerPublicSigningKey, this.ResourceServerPrivateEncryptionKey);
var token = accessTokenFormatter.Deserialize(message, accessToken, Protocol.access_token);
- user = token.User;
- scope = new HashSet<string>(token.Scope, OAuthUtilities.ScopeStringComparer);
- return true;
+ return token;
}
}
}