diff options
Diffstat (limited to 'src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2')
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; } } } |