Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge branch 'master' of https://github.com/ServiceStack/ServiceStack

  • Loading branch information...
commit 6b84c771f9277bff77ba023aeadafc7263b66ce9 2 parents 0e2f50e + ef5d26e
Demis Bellot mythz authored
1  src/ServiceStack.Interfaces/Redis/IRedisClient.cs
View
@@ -30,6 +30,7 @@ public interface IRedisClient
DateTime LastSave { get; }
string Host { get; }
int Port { get; }
+ int ConnectTimeout { get; set; }
int RetryTimeout { get; set; }
int RetryCount { get; set; }
int SendTimeout { get; set; }
8 src/ServiceStack.ServiceInterface/Auth/AuthService.cs
View
@@ -32,6 +32,13 @@ public class Auth
public string UserName { get; set; }
public string Password { get; set; }
public bool? RememberMe { get; set; }
+ // Thise are used for digest auth
+ public string nonce { get; set; }
+ public string uri { get; set; }
+ public string response { get; set; }
+ public string qop { get; set; }
+ public string nc { get; set; }
+ public string cnonce { get; set; }
}
public class AuthResponse
@@ -55,6 +62,7 @@ public class AuthService : RestServiceBase<Auth>
public const string BasicProvider = "basic";
public const string CredentialsProvider = "credentials";
public const string LogoutAction = "logout";
+ public const string DigestProvider = "digest";
public static Func<IAuthSession> CurrentSessionFactory { get; set; }
public static ValidateFn ValidateFn { get; set; }
2  src/ServiceStack.ServiceInterface/Auth/AuthUserSession.cs
View
@@ -53,6 +53,8 @@ public AuthUserSession()
public virtual bool IsAuthenticated { get; set; }
+ public virtual string Sequence { get; set; }
+
public virtual bool IsAuthorized(string provider)
{
var tokens = ProviderOAuthAccess.FirstOrDefault(x => x.Provider == provider);
105 src/ServiceStack.ServiceInterface/Auth/DigestAuthFunctions.cs
View
@@ -0,0 +1,105 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Security.Cryptography;
+using System.Globalization;
+
+namespace ServiceStack.ServiceInterface.Auth
+{
+ public class DigestAuthFunctions
+ {
+ public string PrivateHashEncode(string TimeStamp, string IPAddress, string PrivateKey)
+ {
+ var hashing = MD5.Create();
+ return(ConvertToHexString(hashing.ComputeHash(Encoding.UTF8.GetBytes(string.Format("{0}:{1}:{2}", TimeStamp, IPAddress, PrivateKey)))));
+
+ }
+ public string Base64Encode(string StringToEncode)
+ {
+ if (StringToEncode != null)
+ {
+ return Convert.ToBase64String(Encoding.UTF8.GetBytes(StringToEncode));
+ }
+ return null;
+ }
+ public string Base64Decode(string StringToDecode)
+ {
+ if (StringToDecode != null)
+ {
+ return Encoding.UTF8.GetString(Convert.FromBase64String(StringToDecode));
+ }
+ return null;
+ }
+ public string[] GetNonceParts(string nonce)
+ {
+ return Base64Decode(nonce).Split(':');
+ }
+ public string GetNonce(string IPAddress, string PrivateKey)
+ {
+ double dateTimeInMilliSeconds = (DateTime.UtcNow - DateTime.MinValue).TotalMilliseconds;
+ string dateTimeInMilliSecondsString = dateTimeInMilliSeconds.ToString(CultureInfo.InvariantCulture);
+ string privateHash = PrivateHashEncode(dateTimeInMilliSecondsString, IPAddress, PrivateKey);
+ return Base64Encode(string.Format("{0}:{1}", dateTimeInMilliSecondsString, privateHash));
+ }
+ public bool ValidateNonce(string nonce, string IPAddress, string PrivateKey)
+ {
+ var nonceparts = GetNonceParts(nonce);
+ string privateHash = PrivateHashEncode(nonceparts[0], IPAddress, PrivateKey);
+ return string.CompareOrdinal(privateHash, nonceparts[1]) == 0;
+ }
+ public bool StaleNonce(string nonce, int Timeout)
+ {
+ var nonceparts = GetNonceParts(nonce);
+ return TimeStampAsDateTime(nonceparts[0]).AddSeconds(Timeout) < DateTime.UtcNow;
+ }
+ private DateTime TimeStampAsDateTime(string TimeStamp)
+ {
+ double nonceTimeStampDouble;
+ if (Double.TryParse(TimeStamp, NumberStyles.Float, CultureInfo.InvariantCulture, out nonceTimeStampDouble))
+ {
+ return DateTime.MinValue.AddMilliseconds(nonceTimeStampDouble);
+ }
+ throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "The given nonce time stamp {0} was not valid", TimeStamp));
+ }
+ public string ConvertToHexString(IEnumerable<byte> hash)
+ {
+ var hexString = new StringBuilder();
+ foreach (byte byteFromHash in hash)
+ {
+ hexString.AppendFormat("{0:x2}", byteFromHash);
+ }
+ return hexString.ToString();
+ }
+ public string CreateAuthResponse(Dictionary<string, string> digestHeaders, string Ha1)
+ {
+ string Ha2 = CreateHa2(digestHeaders);
+ return CreateAuthResponse(digestHeaders, Ha1, Ha2);
+ }
+ public string CreateAuthResponse(Dictionary<string, string> digestHeaders, string Ha1, string Ha2)
+ {
+ string response = string.Format(CultureInfo.InvariantCulture, "{0}:{1}:{2}:{3}:{4}:{5}", Ha1, digestHeaders["nonce"], digestHeaders["nc"], digestHeaders["cnonce"], digestHeaders["qop"].ToLower(), Ha2);
+ return ConvertToHexString(MD5.Create().ComputeHash(Encoding.UTF8.GetBytes(response)));
+ }
+ public string CreateHa1(Dictionary<string,string> digestHeaders, string password)
+ {
+ return CreateHa1(digestHeaders["username"],digestHeaders["realm"],password);
+ }
+ public string CreateHa1(string Username, string Realm, string Password)
+ {
+ return ConvertToHexString(MD5.Create().ComputeHash(Encoding.UTF8.GetBytes(string.Format("{0}:{1}:{2}", Username,Realm,Password))));
+ }
+ public string CreateHa2(Dictionary<string, string> digestHeaders)
+ {
+ return ConvertToHexString(MD5.Create().ComputeHash(Encoding.UTF8.GetBytes(string.Format("{0}:{1}",digestHeaders["method"],digestHeaders["uri"]))));
+ }
+ public bool ValidateResponse(Dictionary<string, string> digestInfo, string PrivateKey, int NonceTimeOut, string DigestHA1, string sequence)
+ {
+ var noncevalid = ValidateNonce(digestInfo["nonce"], digestInfo["userhostaddress"], PrivateKey);
+ var noncestale = StaleNonce(digestInfo["nonce"], NonceTimeOut);
+ var uservalid = CreateAuthResponse(digestInfo, DigestHA1) == digestInfo["response"];
+ var sequencevalid = sequence != digestInfo["nc"];
+ return noncevalid && !noncestale && uservalid && sequencevalid;
+ }
+ }
+}
158 src/ServiceStack.ServiceInterface/Auth/DigestAuthProvider.cs
View
@@ -0,0 +1,158 @@
+using System.Globalization;
+using System.Collections.Generic;
+using ServiceStack.Common;
+using ServiceStack.Common.Web;
+using ServiceStack.ServiceHost;
+using ServiceStack.Configuration;
+using ServiceStack.FluentValidation;
+using ServiceStack.Text;
+using System;
+using System.Net;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace ServiceStack.ServiceInterface.Auth
+{
+ public class DigestAuthProvider : AuthProvider
+ {
+ class DigestAuthValidator : AbstractValidator<Auth>
+ {
+ public DigestAuthValidator()
+ {
+ RuleFor(x => x.UserName).NotEmpty();
+ RuleFor(x => x.Password).NotEmpty();
+ }
+ }
+
+ public static string Name = AuthService.DigestProvider;
+ public static string Realm = "/auth/" + AuthService.DigestProvider;
+ public static int NonceTimeOut = 600;
+ public string PrivateKey;
+ public IResourceManager AppSettings { get; set; }
+ public DigestAuthProvider()
+ {
+ this.Provider = Name;
+ PrivateKey = Guid.NewGuid().ToString();
+ this.AuthRealm = Realm;
+ }
+ public DigestAuthProvider(IResourceManager appSettings, string authRealm, string oAuthProvider)
+ : base(appSettings, authRealm, oAuthProvider) { }
+
+ public DigestAuthProvider(IResourceManager appSettings)
+ : base(appSettings, Realm, Name) { }
+
+ public virtual bool TryAuthenticate(IServiceBase authService, string userName, string password)
+ {
+ var authRepo = authService.TryResolve<IUserAuthRepository>();
+ if (authRepo == null)
+ {
+ Log.WarnFormat("Tried to authenticate without a registered IUserAuthRepository");
+ return false;
+ }
+
+ var session = authService.GetSession();
+ var digestInfo = authService.RequestContext.Get<IHttpRequest>().GetDigestAuth();
+ UserAuth userAuth = null;
+ if (authRepo.TryAuthenticate(digestInfo,PrivateKey,NonceTimeOut, session.Sequence, out userAuth))
+ {
+ session.PopulateWith(userAuth);
+ session.IsAuthenticated = true;
+ session.Sequence = digestInfo["nc"];
+ session.UserAuthId = userAuth.Id.ToString(CultureInfo.InvariantCulture);
+ session.ProviderOAuthAccess = authRepo.GetUserOAuthProviders(session.UserAuthId)
+ .ConvertAll(x => (IOAuthTokens)x);
+
+ return true;
+ }
+ return false;
+ }
+
+ public override bool IsAuthorized(IAuthSession session, IOAuthTokens tokens, Auth request = null)
+ {
+ return false;
+ //if (request != null)
+ //{
+ // if (!LoginMatchesSession(session, request.UserName)) return false;
+ //}
+
+ //return !session.UserAuthName.IsNullOrEmpty();
+ }
+
+ public override object Authenticate(IServiceBase authService, IAuthSession session, Auth request)
+ {
+ //new CredentialsAuthValidator().ValidateAndThrow(request);
+ return Authenticate(authService, session, request.UserName, request.Password);
+ }
+ protected object Authenticate(IServiceBase authService, IAuthSession session, string userName, string password)
+ {
+ if (!LoginMatchesSession(session, userName))
+ {
+ authService.RemoveSession();
+ session = authService.GetSession();
+ }
+
+ if (TryAuthenticate(authService, userName, password))
+ {
+ if (session.UserAuthName == null)
+ session.UserAuthName = userName;
+
+ OnAuthenticated(authService, session, null, null);
+
+ return new AuthResponse
+ {
+ UserName = userName,
+ SessionId = session.Id,
+ };
+ }
+
+ throw HttpError.Unauthorized("Invalid UserName or Password");
+ }
+
+ public override void OnAuthenticated(IServiceBase authService, IAuthSession session, IOAuthTokens tokens, Dictionary<string, string> authInfo)
+ {
+ var userSession = session as AuthUserSession;
+ if (userSession != null)
+ {
+ LoadUserAuthInfo(userSession, tokens, authInfo);
+ }
+
+ var authRepo = authService.TryResolve<IUserAuthRepository>();
+ if (authRepo != null)
+ {
+ if (tokens != null)
+ {
+ authInfo.ForEach((x, y) => tokens.Items[x] = y);
+ session.UserAuthId = authRepo.CreateOrMergeAuthSession(session, tokens);
+ }
+
+ foreach (var oAuthToken in session.ProviderOAuthAccess)
+ {
+ var authProvider = AuthService.GetAuthProvider(oAuthToken.Provider);
+ if (authProvider == null) continue;
+ var userAuthProvider = authProvider as OAuthProvider;
+ if (userAuthProvider != null)
+ {
+ userAuthProvider.LoadUserOAuthProvider(session, oAuthToken);
+ }
+ }
+
+ //var httpRes = authService.RequestContext.Get<IHttpResponse>();
+ //if (httpRes != null)
+ //{
+ // httpRes.Cookies.AddPermanentCookie(HttpHeaders.XUserAuthId, session.UserAuthId);
+ //}
+
+ }
+
+ authService.SaveSession(session, SessionExpiry);
+ session.OnAuthenticated(authService, session, tokens, authInfo);
+ }
+ public override void OnFailedAuthentication(IAuthSession session, IHttpRequest httpReq, IHttpResponse httpRes)
+ {
+ var digestHelper = new DigestAuthFunctions();
+ httpRes.StatusCode = (int)HttpStatusCode.Unauthorized;
+ httpRes.AddHeader(HttpHeaders.WwwAuthenticate, "{0} realm=\"{1}\", nonce=\"{2}\", qop=\"auth\"".Fmt(Provider, AuthRealm,digestHelper.GetNonce(httpReq.UserHostAddress,PrivateKey)));
+ httpRes.Close();
+ }
+ }
+}
2  src/ServiceStack.ServiceInterface/Auth/IAuthSession.cs
View
@@ -20,6 +20,8 @@ public interface IAuthSession
List<string> Roles { get; set; }
List<string> Permissions { get; set; }
bool IsAuthenticated { get; set; }
+ //Used for digest authentication replay protection
+ string Sequence { get; set; }
bool HasRole(string role);
bool HasPermission(string permission);
3  src/ServiceStack.ServiceInterface/Auth/IUserAuthRepository.cs
View
@@ -8,7 +8,8 @@ public interface IUserAuthRepository
UserAuth UpdateUserAuth(UserAuth existingUser, UserAuth newUser, string password);
UserAuth GetUserAuthByUserName(string userNameOrEmail);
bool TryAuthenticate(string userName, string password, out UserAuth userAuth);
- void LoadUserAuth(IAuthSession session, IOAuthTokens tokens);
+ bool TryAuthenticate(Dictionary<string, string> digestHeaders, string PrivateKey, int NonceTimeOut, string sequence, out UserAuth userAuth);
+ void LoadUserAuth(IAuthSession session, IOAuthTokens tokens);
UserAuth GetUserAuth(string userAuthId);
void SaveUserAuth(IAuthSession authSession);
void SaveUserAuth(UserAuth userAuth);
29 src/ServiceStack.ServiceInterface/Auth/OrmLiteAuthRepository.cs
View
@@ -64,7 +64,8 @@ public UserAuth CreateUserAuth(UserAuth newUser, string password)
string salt;
string hash;
saltedHash.GetHashAndSaltString(password, out hash, out salt);
-
+ var digestHelper = new DigestAuthFunctions();
+ newUser.DigestHA1Hash = digestHelper.CreateHa1(newUser.UserName, DigestAuthProvider.Realm, password);
newUser.PasswordHash = hash;
newUser.Salt = salt;
newUser.CreatedDate = DateTime.UtcNow;
@@ -104,15 +105,22 @@ public UserAuth UpdateUserAuth(UserAuth existingUser, UserAuth newUser, string p
var hash = existingUser.PasswordHash;
var salt = existingUser.Salt;
- if (password != null)
+ if (password != null)
{
var saltedHash = new SaltedHash();
saltedHash.GetHashAndSaltString(password, out hash, out salt);
}
-
+ // If either one changes the digest hash has to be recalculated
+ var digestHash = existingUser.DigestHA1Hash;
+ if (password != null || existingUser.UserName != newUser.UserName)
+ {
+ var digestHelper = new DigestAuthFunctions();
+ digestHash = digestHelper.CreateHa1(newUser.UserName, DigestAuthProvider.Realm, password);
+ }
newUser.Id = existingUser.Id;
newUser.PasswordHash = hash;
newUser.Salt = salt;
+ newUser.DigestHA1Hash = digestHash;
newUser.CreatedDate = existingUser.CreatedDate;
newUser.ModifiedDate = DateTime.UtcNow;
@@ -153,6 +161,21 @@ public bool TryAuthenticate(string userName, string password, out UserAuth userA
userAuth = null;
return false;
}
+ public bool TryAuthenticate(Dictionary<string,string> digestHeaders, string PrivateKey, int NonceTimeOut, string sequence, out UserAuth userAuth)
+ {
+ //userId = null;
+ userAuth = GetUserAuthByUserName(digestHeaders["username"]);
+ if (userAuth == null) return false;
+
+ var digestHelper = new DigestAuthFunctions();
+ if (digestHelper.ValidateResponse(digestHeaders,PrivateKey,NonceTimeOut,userAuth.DigestHA1Hash,sequence))
+ {
+ //userId = userAuth.Id.ToString(CultureInfo.InvariantCulture);
+ return true;
+ }
+ userAuth = null;
+ return false;
+ }
public void LoadUserAuth(IAuthSession session, IOAuthTokens tokens)
{
29 src/ServiceStack.ServiceInterface/Auth/RedisAuthRepository.cs
View
@@ -99,7 +99,9 @@ public UserAuth CreateUserAuth(UserAuth newUser, string password)
newUser.Id = redis.As<UserAuth>().GetNextSequence();
newUser.PasswordHash = hash;
newUser.Salt = salt;
- newUser.CreatedDate = DateTime.UtcNow;
+ var digestHelper = new DigestAuthFunctions();
+ newUser.DigestHA1Hash = digestHelper.CreateHa1(newUser.UserName, DigestAuthProvider.Realm, password);
+ newUser.CreatedDate = DateTime.UtcNow;
newUser.ModifiedDate = newUser.CreatedDate;
var userId = newUser.Id.ToString(CultureInfo.InvariantCulture);
@@ -138,6 +140,13 @@ public UserAuth UpdateUserAuth(UserAuth existingUser, UserAuth newUser, string p
var saltedHash = new SaltedHash();
saltedHash.GetHashAndSaltString(password, out hash, out salt);
}
+ // If either one changes the digest hash has to be recalculated
+ var digestHash = existingUser.DigestHA1Hash;
+ if (password != null || existingUser.UserName != newUser.UserName)
+ {
+ var digestHelper = new DigestAuthFunctions();
+ digestHash = digestHelper.CreateHa1(newUser.UserName, DigestAuthProvider.Realm, password);
+ }
newUser.Id = existingUser.Id;
newUser.PasswordHash = hash;
@@ -191,6 +200,20 @@ public bool TryAuthenticate(string userName, string password, out UserAuth userA
return false;
}
+ public bool TryAuthenticate (Dictionary<string, string> digestHeaders, string PrivateKey, int NonceTimeOut, string sequence, out UserAuth userAuth)
+ {
+ userAuth = GetUserAuthByUserName(digestHeaders["username"]);
+ if (userAuth == null) return false;
+
+ var digestHelper = new DigestAuthFunctions();
+ if (digestHelper.ValidateResponse(digestHeaders, PrivateKey, NonceTimeOut, userAuth.DigestHA1Hash,sequence))
+ {
+ return true;
+ }
+ userAuth = null;
+ return false;
+ }
+
public virtual void LoadUserAuth(IAuthSession session, IOAuthTokens tokens)
{
session.ThrowIfNull("session");
@@ -347,6 +370,8 @@ public void Clear()
{
this.factory.Clear();
}
- }
+
+
+ }
}
1  src/ServiceStack.ServiceInterface/Auth/UserAuth.cs
View
@@ -23,6 +23,7 @@ public UserAuth()
public virtual string DisplayName { get; set; }
public virtual string Salt { get; set; }
public virtual string PasswordHash { get; set; }
+ public virtual string DigestHA1Hash { get; set; }
public virtual List<string> Roles { get; set; }
public virtual List<string> Permissions { get; set; }
public virtual DateTime CreatedDate { get; set; }
26 src/ServiceStack.ServiceInterface/AuthenticateAttribute.cs
View
@@ -58,6 +58,7 @@ public override void Execute(IHttpRequest req, IHttpResponse res, object request
return;
}
+ AuthenticateIfDigestAuth(req, res);
AuthenticateIfBasicAuth(req, res);
using (var cache = req.GetCacheClient())
@@ -90,5 +91,28 @@ public static void AuthenticateIfBasicAuth(IHttpRequest req, IHttpResponse res)
});
}
}
- }
+ public static void AuthenticateIfDigestAuth(IHttpRequest req, IHttpResponse res)
+ {
+ //Need to run SessionFeature filter since its not executed before this attribute (Priority -100)
+ SessionFeature.AddSessionIdToRequestFilter(req, res, null); //Required to get req.GetSessionId()
+
+ var digestAuth = req.GetDigestAuth();
+ if (digestAuth != null)
+ {
+ var authService = req.TryResolve<AuthService>();
+ authService.RequestContext = new HttpRequestContext(req, res, null);
+ var response = authService.Post(new Auth.Auth
+ {
+ provider = DigestAuthProvider.Name,
+ nonce = digestAuth["nonce"],
+ uri = digestAuth["uri"],
+ response = digestAuth["response"],
+ qop = digestAuth["qop"],
+ nc = digestAuth["nc"],
+ cnonce = digestAuth["cnonce"],
+ UserName = digestAuth["username"]
+ });
+ }
+ }
+ }
}
2  src/ServiceStack.ServiceInterface/ServiceStack.ServiceInterface.csproj
View
@@ -110,6 +110,8 @@
<Compile Include="Auth\AuthService.cs" />
<Compile Include="Auth\BasicAuthProvider.cs" />
<Compile Include="Auth\CredentialsAuthProvider.cs" />
+ <Compile Include="Auth\DigestAuthProvider.cs" />
+ <Compile Include="Auth\DigestAuthFunctions.cs" />
<Compile Include="Auth\FacebookAuthProvider.cs" />
<Compile Include="Auth\IAuthProvider.cs" />
<Compile Include="Auth\InMemoryAuthRepository.cs" />
30 src/ServiceStack/ServiceHost/HttpRequestAuthentication.cs
View
@@ -27,7 +27,35 @@ public static string GetBasicAuth(this IHttpRequest httpReq)
var parts = userPass.SplitOnFirst(':');
return new KeyValuePair<string, string>(parts[0], parts[1]);
}
-
+ public static Dictionary<string,string> GetDigestAuth(this IHttpRequest httpReq)
+ {
+ var auth = httpReq.Headers[HttpHeaders.Authorization];
+ if (auth == null) return null;
+ var parts = auth.Split(' ');
+ // There should be at least to parts
+ if (parts.Length < 2) return null;
+ // It has to be a digest request
+ if (parts[0].ToLower() != "digest") return null;
+ // Remove uptil the first space
+ auth = auth.Substring(auth.IndexOf(' '));
+ parts = auth.Split(',');
+ try
+ {
+ var result = new Dictionary<string, string>();
+ foreach (var item in parts)
+ {
+ var param = item.Trim().Split(new char[] {'='},2);
+ result.Add(param[0],param[1].Trim(new char[] {'"'}));
+ }
+ result.Add("method", httpReq.HttpMethod);
+ result.Add("userhostaddress", httpReq.UserHostAddress);
+ return result;
+ }
+ catch (Exception)
+ {
+ }
+ return null;
+ }
public static string GetCookieValue(this IHttpRequest httpReq, string cookieName)
{
Cookie cookie;
15 src/ServiceStack/ServiceHost/HttpResponseExtensions.cs
View
@@ -64,10 +64,14 @@ public static void ReturnAuthRequired(this IHttpResponse httpRes)
public static void ReturnAuthRequired(this IHttpResponse httpRes, string authRealm)
{
- httpRes.StatusCode = (int)HttpStatusCode.Unauthorized;
- httpRes.AddHeader(HttpHeaders.WwwAuthenticate, "Basic realm=\"" + authRealm + "\"");
- httpRes.Close();
+ httpRes.ReturnAuthRequired(AuthenticationHeaderType.Basic, authRealm);
}
+ public static void ReturnAuthRequired(this IHttpResponse httpRes, AuthenticationHeaderType AuthType, string authRealm)
+ {
+ httpRes.StatusCode = (int)HttpStatusCode.Unauthorized;
+ httpRes.AddHeader(HttpHeaders.WwwAuthenticate, string.Format("{0} realm=\"{1}\"",AuthType.ToString(),authRealm));
+ httpRes.Close();
+ }
/// <summary>
/// Sets a persistent cookie which never expires
@@ -150,4 +154,9 @@ public static void AddHeaderLastModified(this IHttpResponse httpRes, DateTime? l
httpRes.AddHeader(HttpHeaders.LastModified, lastWt.ToString("r"));
}
}
+ public enum AuthenticationHeaderType
+ {
+ Basic,
+ Digest
+ }
}
1  tests/ServiceStack.WebHost.IntegrationTests/Global.asax.cs
View
@@ -123,6 +123,7 @@ private void ConfigureAuth(Funq.Container container)
new CredentialsAuthProvider(appSettings),
new FacebookAuthProvider(appSettings),
new TwitterAuthProvider(appSettings),
+ new DigestAuthProvider(appSettings),
new BasicAuthProvider(appSettings),
}));
1  tests/ServiceStack.WebHost.IntegrationTests/ServiceStack.WebHost.IntegrationTests.csproj
View
@@ -170,6 +170,7 @@
<DependentUpon>Default.aspx</DependentUpon>
</Compile>
<Compile Include="Services\HelloImage.cs" />
+ <Compile Include="Tests\DigestAuthTests.cs" />
<Compile Include="Tests\ProtoBufServiceTests.cs" />
<Compile Include="Services\ContentManagerOnly.cs" />
<Compile Include="Services\ThrowsArgumentNullService.cs" />
Please sign in to comment.
Something went wrong with that request. Please try again.