summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndrew Arnott <andrewarnott@gmail.com>2012-04-01 15:36:22 -0700
committerAndrew Arnott <andrewarnott@gmail.com>2012-04-01 15:36:22 -0700
commit0c8a4a3a33e840e7c449388f078155efaf1854c7 (patch)
treea2737354658f5bb6699197e615e84182a48a6f0d
parent4fcf484a281697630698c12f81fdcf7306346366 (diff)
downloadDotNetOpenAuth-0c8a4a3a33e840e7c449388f078155efaf1854c7.zip
DotNetOpenAuth-0c8a4a3a33e840e7c449388f078155efaf1854c7.tar.gz
DotNetOpenAuth-0c8a4a3a33e840e7c449388f078155efaf1854c7.tar.bz2
AccessToken is now a public class.
Resource Servers can now handle access tokens that are issued for a client's data (not a 3rd party resource owner's). Client Identifiers are no longer included in access tokens for unauthenticated clients. More work needed on IAccessTokenAnalyzer and the access token formatter. We need to generalize the serialization itself so folks can use JWT, etc. We also still need access token to have a host-defined map of claims. Fixes #104 Fixes #102
-rw-r--r--projecttemplates/RelyingPartyLogic/SpecialAccessTokenAnalyzer.cs13
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/DataBag.cs2
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthorizationServer.cs1
-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
-rw-r--r--src/DotNetOpenAuth.OAuth2/DotNetOpenAuth.OAuth2.csproj2
-rw-r--r--src/DotNetOpenAuth.OAuth2/OAuth2/AccessToken.cs (renamed from src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AccessToken.cs)18
-rw-r--r--src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AuthorizationDataBag.cs2
11 files changed, 102 insertions, 67 deletions
diff --git a/projecttemplates/RelyingPartyLogic/SpecialAccessTokenAnalyzer.cs b/projecttemplates/RelyingPartyLogic/SpecialAccessTokenAnalyzer.cs
index 69788ab..e8b00b5 100644
--- a/projecttemplates/RelyingPartyLogic/SpecialAccessTokenAnalyzer.cs
+++ b/projecttemplates/RelyingPartyLogic/SpecialAccessTokenAnalyzer.cs
@@ -23,14 +23,13 @@ namespace RelyingPartyLogic {
: base(authorizationServerPublicSigningKey, resourceServerPrivateEncryptionKey) {
}
- public override bool TryValidateAccessToken(DotNetOpenAuth.Messaging.IDirectedProtocolMessage message, string accessToken, out string user, out HashSet<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.Add("oauth_client");
- }
+ public override AccessToken DeserializeAccessToken(DotNetOpenAuth.Messaging.IDirectedProtocolMessage message, string accessToken) {
+ var token = base.DeserializeAccessToken(message, accessToken);
- return result;
+ // Ensure that clients coming in this way always belong to the oauth_client role.
+ token.Scope.Add("oauth_client");
+
+ return token;
}
}
}
diff --git a/src/DotNetOpenAuth.Core/Messaging/DataBag.cs b/src/DotNetOpenAuth.Core/Messaging/DataBag.cs
index c9c3415..f09bcea 100644
--- a/src/DotNetOpenAuth.Core/Messaging/DataBag.cs
+++ b/src/DotNetOpenAuth.Core/Messaging/DataBag.cs
@@ -14,7 +14,7 @@ namespace DotNetOpenAuth.Messaging {
/// A collection of message parts that will be serialized into a single string,
/// to be set into a larger message.
/// </summary>
- internal abstract class DataBag : IMessage {
+ public abstract class DataBag : IMessage {
/// <summary>
/// The default version for DataBags.
/// </summary>
diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthorizationServer.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthorizationServer.cs
index 9600ec6..c3d1772 100644
--- a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthorizationServer.cs
+++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthorizationServer.cs
@@ -184,7 +184,6 @@ namespace DotNetOpenAuth.OAuth2 {
implicitGrantResponse.Lifetime = accessRequestInternal.AccessTokenCreationParameters.AccessTokenLifetime;
IAccessTokenCarryingRequest tokenCarryingResponse = implicitGrantResponse;
tokenCarryingResponse.AuthorizationDescription = new AccessToken(
- authorizationRequest.ClientIdentifier,
implicitGrantResponse.Scope,
userName,
implicitGrantResponse.Lifetime);
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;
}
}
}
diff --git a/src/DotNetOpenAuth.OAuth2/DotNetOpenAuth.OAuth2.csproj b/src/DotNetOpenAuth.OAuth2/DotNetOpenAuth.OAuth2.csproj
index 1b410ac..921cd84 100644
--- a/src/DotNetOpenAuth.OAuth2/DotNetOpenAuth.OAuth2.csproj
+++ b/src/DotNetOpenAuth.OAuth2/DotNetOpenAuth.OAuth2.csproj
@@ -19,7 +19,7 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="GlobalSuppressions.cs" />
- <Compile Include="OAuth2\ChannelElements\AccessToken.cs" />
+ <Compile Include="OAuth2\AccessToken.cs" />
<Compile Include="OAuth2\ChannelElements\AuthorizationDataBag.cs" />
<Compile Include="OAuth2\ChannelElements\IAccessTokenCarryingRequest.cs" />
<Compile Include="OAuth2\ChannelElements\ScopeEncoder.cs" />
diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AccessToken.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/AccessToken.cs
index 84b17cc..1aeeea7 100644
--- a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AccessToken.cs
+++ b/src/DotNetOpenAuth.OAuth2/OAuth2/AccessToken.cs
@@ -4,18 +4,19 @@
// </copyright>
//-----------------------------------------------------------------------
-namespace DotNetOpenAuth.OAuth2.ChannelElements {
+namespace DotNetOpenAuth.OAuth2 {
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Security.Cryptography;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.Messaging.Bindings;
+ using DotNetOpenAuth.OAuth2.ChannelElements;
/// <summary>
/// A short-lived token that accompanies HTTP requests to protected data to authorize the request.
/// </summary>
- internal class AccessToken : AuthorizationDataBag {
+ public class AccessToken : AuthorizationDataBag {
/// <summary>
/// Initializes a new instance of the <see cref="AccessToken"/> class.
/// </summary>
@@ -40,14 +41,15 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements {
/// <summary>
/// Initializes a new instance of the <see cref="AccessToken"/> class.
/// </summary>
- /// <param name="clientIdentifier">The client identifier.</param>
/// <param name="scopes">The scopes.</param>
/// <param name="username">The username of the account that authorized this token.</param>
/// <param name="lifetime">The lifetime for this access token.</param>
- internal AccessToken(string clientIdentifier, IEnumerable<string> scopes, string username, TimeSpan? lifetime) {
- Requires.NotNullOrEmpty(clientIdentifier, "clientIdentifier");
-
- this.ClientIdentifier = clientIdentifier;
+ /// <remarks>
+ /// The <see cref="ClientIdentifier.ClientIdentifier"/> is left <c>null</c> in this case because this constructor
+ /// is invoked in the case where the client is <em>not</em> authenticated, and therefore no
+ /// trust in the client_id is appropriate.
+ /// </remarks>
+ internal AccessToken(IEnumerable<string> scopes, string username, TimeSpan? lifetime) {
this.Scope.ResetContents(scopes);
this.User = username;
this.Lifetime = lifetime;
@@ -59,7 +61,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements {
/// </summary>
/// <value>The lifetime.</value>
[MessagePart(Encoder = typeof(TimespanSecondsEncoder))]
- internal TimeSpan? Lifetime { get; set; }
+ public TimeSpan? Lifetime { get; set; }
/// <summary>
/// Creates a formatter capable of serializing/deserializing an access token.
diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AuthorizationDataBag.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AuthorizationDataBag.cs
index cee38db..a078d2f 100644
--- a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AuthorizationDataBag.cs
+++ b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AuthorizationDataBag.cs
@@ -13,7 +13,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements {
/// <summary>
/// A data bag that stores authorization data.
/// </summary>
- internal abstract class AuthorizationDataBag : DataBag, IAuthorizationDescription {
+ public abstract class AuthorizationDataBag : DataBag, IAuthorizationDescription {
/// <summary>
/// Initializes a new instance of the <see cref="AuthorizationDataBag"/> class.
/// </summary>