using System;
using System.Configuration;
using System.Runtime.Serialization;
using ServiceStack.Common.Web;
using ServiceStack.ServiceHost;
using ServiceStack.ServiceInterface.ServiceModel;
using ServiceStack.Text;
namespace ServiceStack.ServiceInterface.Auth
{
///
/// Inject logic into existing services by introspecting the request and injecting your own
/// validation logic. Exceptions thrown will have the same behaviour as if the service threw it.
///
/// If a non-null object is returned the request will short-circuit and return that response.
///
/// The instance of the service
/// GET,POST,PUT,DELETE
///
/// Response DTO; non-null will short-circuit execution and return that response
public delegate object ValidateFn(IServiceBase service, string httpMethod, object requestDto);
[DataContract]
public class Auth : IReturn
{
[DataMember(Order=1)] public string provider { get; set; }
[DataMember(Order=2)] public string State { get; set; }
[DataMember(Order=3)] public string oauth_token { get; set; }
[DataMember(Order=4)] public string oauth_verifier { get; set; }
[DataMember(Order=5)] public string UserName { get; set; }
[DataMember(Order=6)] public string Password { get; set; }
[DataMember(Order=7)] public bool? RememberMe { get; set; }
[DataMember(Order=8)] public string Continue { get; set; }
// Thise are used for digest auth
[DataMember(Order=9)] public string nonce { get; set; }
[DataMember(Order=10)] public string uri { get; set; }
[DataMember(Order=11)] public string response { get; set; }
[DataMember(Order=12)] public string qop { get; set; }
[DataMember(Order=13)] public string nc { get; set; }
[DataMember(Order=14)] public string cnonce { get; set; }
}
[DataContract]
public class AuthResponse
{
public AuthResponse()
{
this.ResponseStatus = new ResponseStatus();
}
[DataMember(Order=1)] public string SessionId { get; set; }
[DataMember(Order=2)] public string UserName { get; set; }
[DataMember(Order=3)] public string ReferrerUrl { get; set; }
[DataMember(Order=4)] public ResponseStatus ResponseStatus { get; set; }
}
[DefaultRequest(typeof(Auth))]
public class AuthService : Service
{
public const string BasicProvider = "basic";
public const string CredentialsProvider = "credentials";
public const string LogoutAction = "logout";
public const string DigestProvider = "digest";
public static Func CurrentSessionFactory { get; set; }
public static ValidateFn ValidateFn { get; set; }
public static string DefaultOAuthProvider { get; private set; }
public static string DefaultOAuthRealm { get; private set; }
public static string HtmlRedirect { get; internal set; }
public static IAuthProvider[] AuthProviders { get; private set; }
static AuthService()
{
CurrentSessionFactory = () => new AuthUserSession();
}
public static IAuthProvider GetAuthProvider(string provider)
{
if (AuthProviders == null || AuthProviders.Length == 0) return null;
if (provider == LogoutAction) return AuthProviders[0];
foreach (var authConfig in AuthProviders)
{
if (string.Compare(authConfig.Provider, provider,
StringComparison.InvariantCultureIgnoreCase) == 0)
return authConfig;
}
return null;
}
public static void Init(Func sessionFactory, params IAuthProvider[] authProviders)
{
if (authProviders.Length == 0)
throw new ArgumentNullException("authProviders");
DefaultOAuthProvider = authProviders[0].Provider;
DefaultOAuthRealm = authProviders[0].AuthRealm;
AuthProviders = authProviders;
if (sessionFactory != null)
CurrentSessionFactory = sessionFactory;
}
private void AssertAuthProviders()
{
if (AuthProviders == null || AuthProviders.Length == 0)
throw new ConfigurationException("No OAuth providers have been registered in your AppHost.");
}
public void Options(Auth request) {}
public object Get(Auth request)
{
return Post(request);
}
public object Post(Auth request)
{
AssertAuthProviders();
if (ValidateFn != null)
{
var validationResponse = ValidateFn(this, HttpMethods.Get, request);
if (validationResponse != null) return validationResponse;
}
if (request.RememberMe.HasValue)
{
var opt = request.RememberMe.GetValueOrDefault(false)
? SessionOptions.Permanent
: SessionOptions.Temporary;
base.RequestContext.Get()
.AddSessionOptions(base.RequestContext.Get(), opt);
}
var provider = request.provider ?? AuthProviders[0].Provider;
var oAuthConfig = GetAuthProvider(provider);
if (oAuthConfig == null)
throw HttpError.NotFound("No configuration was added for OAuth provider '{0}'".Fmt(provider));
if (request.provider == LogoutAction)
return oAuthConfig.Logout(this, request);
var session = this.GetSession();
var isHtml = base.RequestContext.ResponseContentType.MatchesContentType(ContentType.Html);
try
{
var response = Authenticate(request, provider, session, oAuthConfig);
// The above Authenticate call may end an existing session and create a new one so we need
// to refresh the current session reference.
session = this.GetSession();
var referrerUrl = request.Continue
?? session.ReferrerUrl
?? this.RequestContext.GetHeader("Referer")
?? oAuthConfig.CallbackUrl;
var alreadyAuthenticated = response == null;
response = response ?? new AuthResponse {
UserName = session.UserAuthName,
SessionId = session.Id,
ReferrerUrl = referrerUrl,
};
if (isHtml)
{
if (alreadyAuthenticated)
return this.Redirect(referrerUrl.AddHashParam("s", "0"));
if (!(response is IHttpResult) && !String.IsNullOrEmpty(referrerUrl))
{
return new HttpResult(response) {
Location = referrerUrl
};
}
}
return response;
}
catch (HttpError ex)
{
var errorReferrerUrl = this.RequestContext.GetHeader("Referer");
if (isHtml && errorReferrerUrl != null)
{
errorReferrerUrl = errorReferrerUrl.SetQueryParam("error", ex.Message);
return HttpResult.Redirect(errorReferrerUrl);
}
throw;
}
}
///
/// Public API entry point to authenticate via code
///
///
/// null; if already autenticated otherwise a populated instance of AuthResponse
public AuthResponse Authenticate(Auth request)
{
//Remove HTML Content-Type to avoid auth providers issuing browser re-directs
((HttpRequestContext)this.RequestContext).ResponseContentType = ContentType.PlainText;
var provider = request.provider ?? AuthProviders[0].Provider;
var oAuthConfig = GetAuthProvider(provider);
if (oAuthConfig == null)
throw HttpError.NotFound("No configuration was added for OAuth provider '{0}'".Fmt(provider));
if (request.provider == LogoutAction)
return oAuthConfig.Logout(this, request) as AuthResponse;
var result = Authenticate(request, provider, this.GetSession(), oAuthConfig);
var httpError = result as HttpError;
if (httpError != null)
throw httpError;
return result as AuthResponse;
}
///
/// The specified may change as a side-effect of this method. If
/// subsequent code relies on current data be sure to reload
/// the session istance via .
///
private object Authenticate(Auth request, string provider, IAuthSession session, IAuthProvider oAuthConfig)
{
object response = null;
if (!oAuthConfig.IsAuthorized(session, session.GetOAuthTokens(provider), request))
{
response = oAuthConfig.Authenticate(this, session, request);
}
return response;
}
public object Delete(Auth request)
{
if (ValidateFn != null)
{
var response = ValidateFn(this, HttpMethods.Delete, request);
if (response != null) return response;
}
this.RemoveSession();
return new AuthResponse();
}
}
}