WCF over TCP with username authentication and no certificates - wcf

I'm using WCF for communication between various .NET applications. These services are all on the same private subnet, so I'd like to avoid the complexity and performance overhead of encryption and certificates. I do, however, need basic username/password support since the requests are all authenticated against our custom MembershipProvider.
We are currently using HTTP with Clear Username Binding and that is working well. However, I would like to use TCP to improve performance. Is it possible to do simple username/password authentication (the way Clear Username Binding does) over NetTcpBinding without having to use certificates, encryption, etc?

The solution I ended up going with was modifying Clear Username Binding to use TCP for transport and binary message encoding. I got the idea from a series of comments on the author's blog. The complete code for my binding is below:
using System;
using System.Configuration;
using System.Net.Security;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;
namespace ClearTcpBinding
{
public class ClearTcpBinding : CustomBinding
{
private long _maxReceivedMessageSize = 65536;
public void SetMaxReceivedMessageSize(long value)
{
_maxReceivedMessageSize = value;
}
public override BindingElementCollection CreateBindingElements()
{
var res = new BindingElementCollection
{
new BinaryMessageEncodingBindingElement {MessageVersion = MessageVersion.Soap12WSAddressing10},
SecurityBindingElement.CreateUserNameOverTransportBindingElement(),
new AutoSecuredTcpTransportElement {MaxReceivedMessageSize = _maxReceivedMessageSize}
};
return res;
}
public override string Scheme { get { return "net.tcp"; } }
}
public class ClearTcpBindingElement : StandardBindingElement
{
private ConfigurationPropertyCollection _properties;
protected override void OnApplyConfiguration(Binding binding)
{
var b = (ClearTcpBinding)binding;
b.SetMaxReceivedMessageSize(Convert.ToInt64(MaxReceivedMessageSize));
}
protected override Type BindingElementType
{
get { return typeof(ClearTcpBinding); }
}
protected override ConfigurationPropertyCollection Properties
{
get
{
if (_properties == null)
{
var properties = base.Properties;
properties.Add(new ConfigurationProperty("maxReceivedMessageSize", typeof(string), "65536"));
_properties = properties;
}
return _properties;
}
}
public string MaxReceivedMessageSize
{
get { return (string)base["maxReceivedMessageSize"]; }
set { base["maxReceivedMessageSize"] = value; }
}
}
public class ClearTcpCollectionElement
: StandardBindingCollectionElement<ClearTcpBinding, ClearTcpBindingElement>
{
}
public class AutoSecuredTcpTransportElement : TcpTransportBindingElement, ITransportTokenAssertionProvider
{
public override T GetProperty<T>(BindingContext context)
{
if (typeof(T) == typeof(ISecurityCapabilities))
return (T)(ISecurityCapabilities)new AutoSecuredTcpSecurityCapabilities();
return base.GetProperty<T>(context);
}
public System.Xml.XmlElement GetTransportTokenAssertion()
{
return null;
}
}
public class AutoSecuredTcpSecurityCapabilities : ISecurityCapabilities
{
public ProtectionLevel SupportedRequestProtectionLevel { get { return ProtectionLevel.EncryptAndSign; } }
public ProtectionLevel SupportedResponseProtectionLevel { get { return ProtectionLevel.EncryptAndSign; } }
public bool SupportsClientAuthentication { get { return false; } }
public bool SupportsClientWindowsIdentity { get { return false; } }
public bool SupportsServerAuthentication { get { return true; } }
}
}

Related

RabbitMQ channel lifetime and access in ASP Net Core

I have a ASP NET Core application that will serve as a RabbitMQ producer.I have read the tutorial and guides regarding the RabbitMQ .NET client and i still do not know how to deal with the channel lifetime and concurrent access.
From what i have read i understood the following:
IConnection is threadsafe ,but is costly to create
IModel is not threadsafe but is lightweight
For the IConnection i would initialize it in the Startup and inject it as a singleton (service).
However i I do not know how to deal with IModel management.Lets say i have a couple of services that use it, is it scalable to just :
Solution 1
public void Publish(IConnection connection)
{
using(IModel model=connection.CreateChannel())
{
model.BasicPublish(...);
}
}
Solution 2
From what i have read , i understood that its not really scalable.
So another solution would be to create a separate service which would contain a loop , a ConcurrentQueue, and all services would dispatch messages here.
This service would be the sole publisher to RabbitMQ
Publisher
public class Publisher
{
private CancellationTokenSource tcs=new CancellationTokenSource();
public BlockingCollection<byte[]> messages=new BlockingCollection<byte[]>();
private IModel channel;
private readonly string ExchangeName;
private Task loopTask;
public void Run()
{
this.loopTask=Task.Run(()=>Loop(tcs.Token),tcs.Token);
}
private void Loop(Cancellation token)
{
while(true)
{
token.ThrowIfCancellationRequested();
queue.Take(out byte[]data);
channel.BasicPublish(...,body:data);
}
}
public void Publish(byte[] message)
{
this.queue.Add(message);
}
}
Usage
public class SomeDIService
{
private IConnection connection;
SomeDIService(Publisher publisher)
{
this.publisher=publisher;
}
public void DoSomething(byte[] data)
{
//do something...
this.publisher.Publish(data);
}
}
I would prefer solution 1 but i do not know the performance penalty ,while i do not like solution 2 since i wanted to just publish messages directly to RabbitMQ.Now i have to deal with this long running Task too.
Is there any other solution , am i missing something ? Is there a simpler way?
Update
I mentioned concurrent access.I meant i need a way to publish messages from multiple endpoints (services) to RabbitMQ.
Real scenario
public class Controller1:Controller
{
private SomeDIService service; //uses Publisher
[HttpGet]
public void Endpoint1()
{
this.service.DoSomething();
}
[HttpPost]
public void Endpoint2()
{
this.service.DoSomething();
}
}
public class Controller2:Controller
{
private SomeDIService service;
[HttpGet]
public void Endpoint3()
{
this.service.DoSomething();
}
[HttpPost]
public void Endpoint4()
{
this.service.DoSomething();
}
}
after searching for long time i found this solution and it works very good for me
using Microsoft.Extensions.Options;
using RabbitMQ.Client;
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
namespace BSG.MessageBroker.RabbitMQ
{
public class Rabbit : IRabbit
{
private readonly EnvConfigModel EnvConfig;
private readonly string _hostname;
private readonly string _password;
private readonly string _exchangeName;
private readonly string _username;
private IConnection _connection;
private IModel _Model;
public Rabbit(IOptions<EnvConfigModel> appSettings)
{
EnvConfig = appSettings.Value;
_Logger = services;
_exchangeName = EnvConfig.Rabbit_ExchangeName;
_hostname = EnvConfig.Rabbit_Host;
_username = EnvConfig.Rabbit_Username;
_password = EnvConfig.Rabbit_Password;
CreateConnection();
_Model = _connection.CreateModel();
}
private void CreateConnection()
{
try
{
var factory = new ConnectionFactory
{
HostName = _hostname,
UserName = _username,
Password = _password,
AutomaticRecoveryEnabled = true,
TopologyRecoveryEnabled = true,
NetworkRecoveryInterval = TimeSpan.FromSeconds(3)
};
_connection = factory.CreateConnection();
}
catch (Exception ex)
{
Console.WriteLine($"Could not create connection: {ex.Message}");
}
}
private bool ConnectionExists()
{
if (_connection != null)
{
return true;
}
CreateShredderConnection();
return _connection != null;
}
public bool PushToQueue(string Message)
{
try
{
if (ConnectionExists())
{
byte[] body = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(Message));
_Model.BasicPublish(exchange: _exchangeName,
routingKey: 1001,
basicProperties: null,
body: body);
}
return true;
}
catch (Exception ex)
{
return false;
}
}
}
}

How to create an API with severals DB connections

I need to create an API in .NET to connect and verify some data in several database engines (MySQl, PostgreSQL, SQL Server).
I can make one connection, but I don't understand how to make more than one.
Here's my code:
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
#region MSSqlServer
var connection = Configuration.GetConnectionString("SQLSRV");
services
.AddDbContext<SQLSRVDBContext>
(options => options.UseSqlServer(connection));
services
.AddTransient<IService, Service>();
#endregion
services.AddMvc();
}
appsettings.json
"ConnectionStrings": {
"SQLSRV": "Server=localhost;Database= dbName;User Id=dbUser;Password=dbPassword;MultipleActiveResultSets=true",
},
Interface
namespace VerificaUsuarios.Interfaces
{
public interface IService
{
bool GetUsuarioSG(string userName, string password);
}
}
Implementation
namespace VerificaUsuarios.Services
{
using VerificaUsuarios.Interfaces;
using VerificaUsuarios.Models;
using VerificaUsuarios.Persistence;
using System.Linq;
using global::ADWS;
public class Service : IService
{
private readonly SQLSRVDBContext _sQLSRVDBContext;
public Service(SQLSRVDBContext sQLSRVDBContext)
{
_sQLSRVDBContext = sQLSRVDBContext;
}
public bool GetUsuarioSG(string userName, string password)
{
var result = new UsuariosSG();
var activeDirectory = new AD_WSClient();
try
{
bool isUsuario = activeDirectory.LoginAsync(userName, password).Result;
if(isUsuario)
{
try
{
result = _sQLSRVDBContext
.Usuarios
.Where(u => u.UsrLogin.Trim() == userName.Trim())
.First();
}
catch (System.Exception ex)
{
return false;
}
return true;
}
else
{
return false;
}
}
catch(System.Exception excep)
{
return false;
}
}
}
}
And the db context
namespace VerificaUsuarios.Persistence
{
using Microsoft.EntityFrameworkCore;
using VerificaUsuarios.Models;
public partial class SQLSRVDBContext : DbContext
{
public SQLSRVDBContext()
{
}
public virtual DbSet<UsuariosSG> Usuarios{ get; set; }
public SQLSRVDBContext(DbContextOptions<SQLSRVDBContext> options)
: base(options)
{ }
}
}
example of connection to different motors with validation against active directory
1) install the different EF Core Database Providers in VS
Entity Framework Core uses a provider model to access many different databases. EF Core includes providers as NuGet packages which you need to install.
The following lists database providers and NuGet packages for EF Core (NuGet package).
SQL Server Microsoft.EntityFrameworkCore.SqlServer
MySQL MySql.Data.EntityFrameworkCore
PostgreSQL Npgsql.EntityFrameworkCore.PostgreSQL
2)Perform the Scaffold-DbContext to the bd and tables that you want to use in the different engines.
PostgreSQL
Scaffold-DbContext "Host=myserver;Database=mydatabase;Username=myuser;Password=mypassword" Npgsql.EntityFrameworkCore.PostgreSQL -o Models -Table MyTablePSQL
MySql
Scaffold-DbContext "server=myserver;port=3306;user=myuser;password=mypass;database=mydb" MySql.Data.EntityFrameworkCore -OutputDir Models -f -Table MyTableMySQL
SqlServer
Scaffold-DbContext "Server=myserver;Database=mydb;User Id=myuser;Password=mypassword;MultipleActiveResultSets=true;" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models -Table MyTableSQL
3)add the different connection string in the appsettings.json
"ConnectionStrings": {
"SQLSRV": "Server=myserver;Database= mydb;User Id=myuser;Password=myPassword;MultipleActiveResultSets=true",
"MySql": "server=myserver;user id=myuser;password=mypassword;port=3306;database=mydb;",
"PSQL": "Host=myserver;Database=mydb;Username=myuser;Password=mypassword"
},
4) modify the DbContext generated by the Scaffold-DbContext
SQLSRVDBContext
namespace MyProject.Persistence
{
using Microsoft.EntityFrameworkCore;
using MyProject.Models;
public partial class SQLSRVDBContext : DbContext
{
public SQLSRVDBContext()
{
}
public virtual DbSet<MyTableSQL> Users{ get; set; }
public SQLSRVDBContext(DbContextOptions<SQLSRVDBContext> options)
: base(options)
{ }
}
}
MySQLDBContext
namespace MyProject.Persistence
{
using Microsoft.EntityFrameworkCore;
using MyProject.Models;
public partial class MySQLDBContext : DbContext
{
public MySQLDBContext()
{
}
public virtual DbSet<MyTableMySQL> Users { get; set; }
public MySQLDBContext(DbContextOptions<MySQLDBContext> options)
: base(options)
{ }
}
}
PostgreSQL
namespace MyProject.Models
{
using Microsoft.EntityFrameworkCore;
public partial class PostgreSQLDBContext : DbContext
{
public PostgreSQLDBContext()
{
}
public virtual DbSet<MyTablePSQL> Users { get; set; }
public PostgreSQLDBContext(DbContextOptions<PostgreSQLDBContext> options)
: base(options)
{
}
}
}
5)create Interfaces
SQLSRV
namespace MyProject.Interfaces
{
public interface IService
{
bool GetUserSQLSRV(string userName, string password);
}
}
MySQL
namespace MyProject.Interfaces
{
public interface IServiceMySQL
{
bool GetUserMySQL(string userName, string password);
}
}
PostgreSQL
namespace MyProject.Interfaces
{
public interface IServicePSQL
{
bool GetUserPSQL(string userName, string password);
}
}
6)create the Services
SQLSRV(SQLSRVDBContext)
namespace MyProject.Services
{
using MyProject.Interfaces;
using MyProject.Models;
using MyProject.Persistence;
using System.Linq;
using global::ADWS;
public class Service : IService
{
private readonly SQLSRVDBContext _sQLSRVDBContext;
public Service(SQLSRVDBContext sQLSRVDBContext)
{
_sQLSRVDBContext = sQLSRVDBContext;
}
public bool GetUserSQLSRV(string userName, string password)
{
var result = new MyTableSQL();
var activeDirectory = new AD_WSClient();
try
{
bool isUser = activeDirectory.LoginAsync(userName, password).Result;
if(isUser)
{
try
{
result = _sQLSRVDBContext
.Users
.Where(u => u.UsrLogin.Trim() == userName.Trim())
.First();
}
catch (System.Exception ex)
{
return false;
}
return true;
}
else
{
return false;
}
}
catch(System.Exception excep)
{
return false;
}
}
}
}
MySQL(MySQLDBContext)
namespace MyProject.Services
{
using MyProject.Interfaces;
using MyProject.Models;
using MyProject.Persistence;
using System.Linq;
using global::ADWS;
public class ServiceMySQL : IServiceMySQL
{
private readonly MySQLDBContext _mySQLDBContext;
public ServiceMySQL(MySQLDBContext mySQLDBContext)
{
_mySQLDBContext = mySQLDBContext;
}
public bool GetUserMySQL(string userName, string password)
{
var result = new MyTableMySQL();
var activeDirectory = new AD_WSClient();
try
{
bool isUser = activeDirectory.LoginAsync(userName, password).Result;
if(isUser)
{
try
{
result = _mySQLDBContext
.Users
.Where(u => u.UsrLogin.Trim() == userName.Trim())
.First();
}
catch (System.Exception ex)
{
return false;
}
return true;
}
else
{
return false;
}
}
catch(System.Exception excep)
{
return false;
}
}
}
}
PostgreSQL(PostgreSQLDBContext)
namespace MyProject.Services
{
using MyProject.Interfaces;
using MyProject.Models;
using MyProject.Persistence;
using System.Linq;
using global::ADWS;
public class ServicePSQL : IServicePSQL
{
private readonly PostgreSQLDBContext _postgreSQLDBContext;
public ServicePSQL(PostgreSQLDBContext postgreSQLDBContext)
{
_postgreSQLDBContext = postgreSQLDBContext;
}
public bool GetUserPSQL(string userName, string password)
{
var result = new MyTablePSQL();
var activeDirectory = new AD_WSClient();
try
{
bool isUser = activeDirectory.LoginAsync(userName, password).Result;
if(isUser)
{
try
{
result = _postgreSQLDBContext
.Users
.Where(u => u.UsrLogin.Trim() == userName.Trim())
.First();
}
catch (System.Exception ex)
{
return false;
}
return true;
}
else
{
return false;
}
}
catch(System.Exception excep)
{
return false;
}
}
}
}
7) configure the different services in Startup.cs
public void ConfigureServices(IServiceCollection services)
{
#region SQLSRV
var connection = Configuration.GetConnectionString("SQLSRV");
services
.AddDbContext<SQLSRVDBContext>
(options => options.UseSqlServer(connection));
services
.AddTransient<IService, Service>();
#endregion
#region MySql
var connectionMySql = Configuration.GetConnectionString("MySQL");
services
.AddDbContext<MySQLDBContext>
(options => options.UseMySQL(connectionMySql));
services
.AddTransient<IServiceMySQL, ServiceMySQL>();
#endregion
#region PostgreSQL
var connectionPSQL = Configuration.GetConnectionString("PSQL");
services
.AddDbContext<PostgreSQLDBContext>
(options => options.UseNpgsql(connectionPSQL));
services.AddTransient<IServicePSQL, ServicePSQL>();
#endregion
services.AddMvc();
}
8)creation of the different Controller
SQLSRV
namespace MyProject.Controllers
{
using Microsoft.AspNetCore.Mvc;
using MyProject.Interfaces;
[Route("api/GET/[controller]")]
public class UserSQLSRVController : Controller
{
private readonly IService _userSQLSRVService;
public UserSQLSRVController(IService userSQLSRVService)
{
_userSQLSRVService = userSQLSRVService;
}
[HttpGet]
public IActionResult GetUserSQLSRV(string userName, string password)
{
return Ok(
_userSQLSRVService.GetUserSQLSRV(userName, password));
}
}
}
MySQL
namespace MyProject.Controllers
{
using Microsoft.AspNetCore.Mvc;
using MyProject.Interfaces;
[Route("api/GET/[controller]")]
public class UserMySqlController : Controller
{
private readonly IServiceMySQL _userMySqlService;
public UserMySqlController(IServiceMySQL userMySqlService)
{
_userMySqlService = userMySqlService;
}
[HttpGet]
public IActionResult GetUserMySQL(string userName, string password)
{
return Ok(
_userMySqlService.GetUserMySQL(userName, password));
}
}
}
PSQL
namespace MyProject.Controllers
{
using Microsoft.AspNetCore.Mvc;
using MyProject.Interfaces;
[Route("api/GET/[controller]")]
public class UserPSQLController : Controller
{
private readonly IServicePSQL _userPSQLService;
public UserPSQLController(IServicePSQL userPSQLService)
{
_userPSQLService = userPSQLService;
}
[HttpGet]
public IActionResult GetUserPSQL(string userName, string password)
{
return Ok(
_userPSQLService.GetUserPSQL(userName, password));
}
}
}

How do I use my custom ServiceStack authentication provider with Redis?

I have implemented a custom CredentialsAuthProvider for my authentication and used it with the default in memory session storage.
Now I tried to change the session storage to Redis and added this to my Configure() method in the AppHost:
container.Register<IRedisClientsManager>(c =>
new PooledRedisClientManager("localhost:6379"));
container.Register<ICacheClient>(c => (ICacheClient)c
.Resolve<IRedisClientsManager>()
.GetCacheClient()).ReusedWithin(Funq.ReuseScope.None);
Now when I authenticate, I can see that a key with urn:iauthsession:... is added to my Redis server. But all routes with the [Authenticate] attribute give a 401 Unauthorized error.
The CustomCredentialsAuthProvider is implemented like this:
public class CustomCredentialsAuthProvider : CredentialsAuthProvider
{
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
if (userName != string.Empty && password != string.Empty)
{
// Database call ...
var session = (CustomSession)authService.GetSession();
session.ClientId = login.ClientId;
// Fill session...
authService.SaveSession(session, SessionExpiry);
return true;
}
return false;
}
}
ServiceStack Version: 3.9.71
EDIT :
I tried to override the CredentialsAuthProvider IsAuthorized method but without success.
But I'm inheriting my session object from AuthUserSession, which also has a IsAuthorized method. When I return true from this method the Redis session does work with the Authenticate Attribute.
public class CustomSession : AuthUserSession
{
public int ClientId { get; set; }
...
public override bool IsAuthorized(string provider)
{
return true;
}
}
The Authenticate attribute calls the IsAuthorized of the AuthUserSession class.
In my case to make it work with the Redis cache client, I've done the following
public override bool IsAuthorized(string provider)
{
string sessionKey = SessionFeature.GetSessionKey(this.Id);
ICacheClient cacheClient = AppHostBase.Resolve<ICacheClient>();
CustomUserSession session = cacheClient.Get<CustomUserSession>(sessionKey);
if (session == null)
{
return false;
}
return session.IsAuthenticated;
}
I couldn't figure out a way to get the [Authenticate] Attribute to work with Redis storage.
I had to write a custom [SessionAuth] Attribute
public class SessionAuthAttribute : RequestFilterAttribute
{
public ICacheClient cache { get; set; }
public string HtmlRedirect { get; set; }
public SessionAuthAttribute()
{
}
public override void Execute(IHttpRequest req, IHttpResponse res, object requestDto)
{
string sessionId = req.GetSessionId();
if (string.IsNullOrEmpty(sessionId))
{
HandleNoSession(req, res);
}
else
{
var session = cache.Get<CustomSession>("urn:iauthsession:" + sessionId);
if (session == null || !session.IsAuthenticated)
{
HandleNoSession(req, res);
}
}
}
private void HandleNoSession(IHttpRequest req, IHttpResponse res)
{
if (req.ResponseContentType.MatchesContentType(MimeTypes.Html))
{
res.RedirectToUrl(HtmlRedirect);
res.End();
}
res.StatusCode = (int)HttpStatusCode.Unauthorized;
res.Write("not authorized");
res.Close();
}
}
In my AppHost Configure() method I just register the SessionFeature and the IRedisClientsManager/ICacheClient:
Plugins.Add(new SessionFeature());
container.Register<IRedisClientsManager>(c => new PooledRedisClientManager("localhost:6379"));
container.Register<ICacheClient>(c => (ICacheClient)c.Resolve<IRedisClientsManager>()
.GetCacheClient()).ReusedWithin(Funq.ReuseScope.None);
The CustomSession class inherits from AuthUserSession:
public class CustomSession : AuthUserSession
{
public int ClientId { get; set; }
...
}
And I have a normal service route on /login/auth for the authentication part and a /login/logout route to remove the session:
public class LoginService : Service
{
public ICacheClient cache { get; set; }
public object Post(AuthRequest request)
{
string userName = request.UserName;
string password = request.Password;
// check login allowed
if (IsAllowed)
{
var session = SessionFeature.GetOrCreateSession<CustomSession>(cache);
session.ClientId = login.ClientId;
...
session.IsAuthenticated = true;
session.Id = SessionFeature.GetSessionId();
this.SaveSession(session, TimeSpan.FromSeconds(30 * 60));
return true;
}
return false;
}
[SessionAuth]
public object Any(LogoutRequest request)
{
this.RemoveSession();
return true;
}
}
}
I'm still interested in a solution that works with the normal [Authenticate] Attribute.

using RavenDB with ServiceStack

I read this post by Phillip Haydon about how to use NHibernate/RavenDB with ServiceStack.
I don't see the point about getting the IDocumentStore and open new session every time i need something from the db like this:
public class FooService : ServiceBase<Foo>
{
public IDocumentStore RavenStore{ get; set; }
protected override object Run(ProductFind request)
{
using (var session = RavenStore.OpenSession())
{
// Do Something...
return new FooResponse{/*Object init*/};
}
}
}
Why cant i just use one session per request and when the request is ended, commit the changes or roll them back according to the response status?
If my approach is fine, than how can i implement it?
here is my attempt:
I created this class:
public class RavenSession : IRavenSession
{
#region Data Members
private readonly IDocumentStore _store;
private IDocumentSession _innerSession;
#endregion
#region Properties
public IDocumentSession InnerSession
{
get { return _innerSession ?? (_innerSession = _store.OpenSession()); }
}
#endregion
#region Ctor
public RavenSession(IDocumentStore store)
{
_store = store;
}
#endregion
#region Public Methods
public void Commit()
{
if (_innerSession != null)
{
try
{
InnerSession.SaveChanges();
}
finally
{
InnerSession.Dispose();
}
}
}
public void Rollback()
{
if (_innerSession != null)
{
InnerSession.Dispose();
}
}
#endregion
#region IDocumentSession Delegation
public ISyncAdvancedSessionOperation Advanced
{
get { return InnerSession.Advanced; }
}
public void Delete<T>(T entity)
{
InnerSession.Delete(entity);
}
public ILoaderWithInclude<object> Include(string path)
{
return InnerSession.Include(path);
}
public ILoaderWithInclude<T> Include<T, TInclude>(Expression<Func<T, object>> path)
{
return InnerSession.Include<T, TInclude>(path);
}
public ILoaderWithInclude<T> Include<T>(Expression<Func<T, object>> path)
{
return InnerSession.Include(path);
}
public T Load<T>(string id)
{
return InnerSession.Load<T>(id);
}
public T[] Load<T>(params string[] ids)
{
return InnerSession.Load<T>(ids);
}
public T Load<T>(ValueType id)
{
return InnerSession.Load<T>(id);
}
public T[] Load<T>(IEnumerable<string> ids)
{
return InnerSession.Load<T>(ids);
}
public IRavenQueryable<T> Query<T, TIndexCreator>() where TIndexCreator : AbstractIndexCreationTask, new()
{
return InnerSession.Query<T, TIndexCreator>();
}
public IRavenQueryable<T> Query<T>()
{
return InnerSession.Query<T>();
}
public IRavenQueryable<T> Query<T>(string indexName)
{
return InnerSession.Query<T>(indexName);
}
public void Store(dynamic entity, string id)
{
InnerSession.Store(entity, id);
}
public void Store(object entity, Guid etag, string id)
{
InnerSession.Store(entity, etag, id);
}
public void Store(object entity, Guid etag)
{
InnerSession.Store(entity, etag);
}
public void Store(dynamic entity)
{
InnerSession.Store(entity);
}
#endregion
}
And now my service looks like this:
public class FooService : ServiceBase<Foo>
{
public IRavenSession RavenSession { get; set; }
protected override object Run(ProductFind request)
{
// Do Something with RavenSession...
return new FooResponse {/*Object init*/};
}
}
but i still need to find a way to know when the request is ended for commit/rollback the changes.
the best way i found is by using ResponseFilters:
public class AppHost : AppHostBase
{
public AppHost()
: base("", typeof (Foo).Assembly, typeof (FooService).Assembly)
{
}
public override void Configure(Container container)
{
// Some Configuration...
this.ResponseFilters.Add((httpReq, httpResp, respnseDto) =>
{
var currentSession = (RavenSession) this.Container.Resolve<IRavenSession>();
if (!httpResp.IsErrorResponse())
{
currentSession.Commit();
}
else
{
currentSession.Rollback();
}
});
// Some Configuration...
}
}
I am sure that there is a better way to do this but how?
I just included this on the Configure method for the AppHost
var store = new DocumentStore()
{
Url = "http://127.0.0.1:8080",
DefaultDatabase = "Test"
}.Initialize();
container.Register(store);
container.Register(c => c.Resolve<IDocumentStore>().OpenSession()).ReusedWithin(ReuseScope.Request);
You can put it aside on module and initialize it.
Then in your services just add a constructor that accepts IDocumentSession
public HelloService : Service {
private readonly IDocumentSession session;
public HelloService(IDocumentSession session) {
this.session = session;
}
}
And you're good to go.
Filtering the response in ServiceStack
The ways to introspect the Response in ServiceStack is with either:
The Response Filter or Response Filter Attributes or other custom hooks
Overriding AppHost.ServiceExceptionHandler or custom OnAfterExecute() hook
Some other notes that might be helpful:
ServiceStack's built-in IOC (Funq) now supports RequestScope
You can add IDisposable to a base class which gets called immediately after the service has finished executing, e.g. if you were to use an RDBMS:
public class FooServiceBase : IService, IDisposable
{
public IDbConnectionFactory DbFactory { get; set; }
private IDbConnection db;
public IDbConnection Db
{
get { return db ?? (db = DbFactory.OpenDbConnection()); }
}
public object Any(ProductFind request)
{
return new FooResponse {
Result = Db.Id<Product>(request.Id)
};
}
public void Dispose()
{
if (db != null) db.Dispose();
}
}
I tried the answer given by Felipe Leusin but it has not worked for me. The main thing that I want to achieve is having a single DocumentSession.SaveChanges call per request. After looking at the RacoonBlog DocumentSession lifecycle management and at ServiceStack request lifecycle events I put together a configuration that works for me:
public override void Configure(Funq.Container container)
{
RequestFilters.Add((httpReq, httpRes, requestDto) =>
{
IDocumentSession documentSession = Container.Resolve<IDocumentStore>().OpenSession();
Container.Register<IDocumentSession>(documentSession);
});
ResponseFilters.Add((httpReq, httpRes, requestDto) =>
{
using (var documentSession = Container.Resolve<IDocumentSession>())
{
if (documentSession == null)
return;
if (httpRes.StatusCode >= 400 && httpRes.StatusCode < 600)
return;
documentSession.SaveChanges();
}
});
var documentStore = new DocumentStore
{
ConnectionStringName = "RavenDBServer",
DefaultDatabase = "MyDatabase",
}.Initialize();
container.Register(documentStore);
I am using funq with RequestScope for my RavenSession, and now i update it to:
public class RavenSession : IRavenSession, IDisposable
{
#region Data Members
private readonly IDocumentStore _store;
private readonly IRequestContext _context;
private IDocumentSession _innerSession;
#endregion
#region Properties
public IDocumentSession InnerSession
{
get { return _innerSession ?? (_innerSession = _store.OpenSession()); }
}
#endregion
#region Ctor
public RavenSession(IDocumentStore store, IRequestContext context)
{
_store = store;
_context = context;
}
#endregion
#region IDocumentSession Delegation
public ISyncAdvancedSessionOperation Advanced
{
get { return InnerSession.Advanced; }
}
public void Delete<T>(T entity)
{
InnerSession.Delete(entity);
}
public ILoaderWithInclude<object> Include(string path)
{
return InnerSession.Include(path);
}
public ILoaderWithInclude<T> Include<T, TInclude>(Expression<Func<T, object>> path)
{
return InnerSession.Include<T, TInclude>(path);
}
public ILoaderWithInclude<T> Include<T>(Expression<Func<T, object>> path)
{
return InnerSession.Include(path);
}
public T Load<T>(string id)
{
return InnerSession.Load<T>(id);
}
public T[] Load<T>(params string[] ids)
{
return InnerSession.Load<T>(ids);
}
public T Load<T>(ValueType id)
{
return InnerSession.Load<T>(id);
}
public T[] Load<T>(IEnumerable<string> ids)
{
return InnerSession.Load<T>(ids);
}
public IRavenQueryable<T> Query<T, TIndexCreator>() where TIndexCreator : AbstractIndexCreationTask, new()
{
return InnerSession.Query<T, TIndexCreator>();
}
public IRavenQueryable<T> Query<T>()
{
return InnerSession.Query<T>();
}
public IRavenQueryable<T> Query<T>(string indexName)
{
return InnerSession.Query<T>(indexName);
}
public void Store(dynamic entity, string id)
{
InnerSession.Store(entity, id);
}
public void Store(object entity, Guid etag, string id)
{
InnerSession.Store(entity, etag, id);
}
public void Store(object entity, Guid etag)
{
InnerSession.Store(entity, etag);
}
public void Store(dynamic entity)
{
InnerSession.Store(entity);
}
#endregion
#region Implementation of IDisposable
public void Dispose()
{
if (_innerSession != null)
{
var httpResponse = _context.Get<IHttpResponse>();
try
{
if (!httpResponse.IsErrorResponse())
{
_innerSession.SaveChanges();
}
}
finally
{
_innerSession.Dispose();
}
}
}
#endregion
}
but this would not work because:
1) although i am using RequestScope, no one is register the IRequestContext of the request so funq cant resolve my RavenSession.
2) funq does not run the Dispose method after the request is done, which is odd.

Custom RoleProvider failing when AuthorizeAttribute applied with role

I'm having an issue with a custom role provider in ASP.net MVC4. I implemented a very light weight RoleProvider which seems to work fine right up until I change
[Authorize]
public class BlahController:....
}
to
[Authorize(Roles="Administrator")]
public class BlahController:....
}
as soon as I make that change users are no longer authenticated and I get 401 errors. This is odd because my RoleProvider basically returns true for IsUSerInRole and a list containing "Administrator" for GetUserRoles. I had breakpoints in place on every method in my custom RoleProvider and found that none of them were being called.
Next I implemented my own authorize attribute which inherited from AuthorizeAttribute. In this I put in break points so I could see what was going on. It turned out that User.IsInRole(), which is called by the underlying attribute was returning false.
I am confident that the role provider is properly set up. I have this in my config file
<roleManager enabled="true" defaultProvider="SimplicityRoleProvider">
<providers>
<clear />
<add name="SimplicityRoleProvider" type="Simplicity.Authentication.SimplicityRoleProvider" applicationName="Simplicity" />
</providers>
</roleManager>
and checking which role provider is the current one using the method described here: Reference current RoleProvider instance? yields the correct result. However User.IsInRole persists in returning false.
I am using Azure Access Control Services but I don't see how that would be incompatible with a custom role provider.
What can I do to correct the IPrincipal User such that IsInRole returns the value from my custom RoleProvider?
RoleProvider source:
public class SimplicityRoleProvider : RoleProvider
{
private ILog log { get; set; }
public SimplicityRoleProvider()
{
log = LogManager.GetLogger("ff");
}
public override void AddUsersToRoles(string[] usernames, string[] roleNames)
{
log.Warn(usernames);
log.Warn(roleNames);
}
public override string ApplicationName
{
get
{
return "Simplicity";
}
set
{
}
}
public override void CreateRole(string roleName)
{
}
public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
{
return true;
}
public override string[] FindUsersInRole(string roleName, string usernameToMatch)
{
log.Warn(roleName);
log.Warn(usernameToMatch);
return new string[0];
}
public override string[] GetAllRoles()
{
log.Warn("all roles");
return new string[0];
}
public override string[] GetRolesForUser(string username)
{
log.Warn(username);
return new String[] { "Administrator" };
}
public override string[] GetUsersInRole(string roleName)
{
log.Warn(roleName);
return new string[0];
}
public override bool IsUserInRole(string username, string roleName)
{
log.Warn(username);
log.Warn(roleName);
return true;
}
public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
{
}
public override bool RoleExists(string roleName)
{
log.Warn(roleName);
return true;
}
}
It seems that System.Web.Security.Roles.GetRolesForUser(Username) does not get automatically hooked up when you have a custom AuthorizeAttribute and a custom RoleProvider.
So, in your custom AuthorizeAttribute you need to retrieve the list of roles from your data source and then compare them against the roles passed in as parameters to the AuthorizeAttribute.
I have seen in a couple blog posts comments that imply manually comparing roles is not necessary but when we override AuthorizeAttribute it seems that we are suppressing this behavior and need to provide it ourselves.
Anyway, I'll walk through what worked for me. Hopefully it will be of some assistance.
I welcome comments on whether there is a better way to accomplish this.
Note that in my case the AuthorizeAttribute is being applied to an ApiController although I'm not sure that is a relevant piece of information.
public class RequestHashAuthorizeAttribute : AuthorizeAttribute
{
bool requireSsl = true;
public bool RequireSsl
{
get { return requireSsl; }
set { requireSsl = value; }
}
bool requireAuthentication = true;
public bool RequireAuthentication
{
get { return requireAuthentication; }
set { requireAuthentication = value; }
}
public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext ActionContext)
{
if (Authenticate(ActionContext) || !RequireAuthentication)
{
return;
}
else
{
HandleUnauthorizedRequest(ActionContext);
}
}
protected override void HandleUnauthorizedRequest(HttpActionContext ActionContext)
{
var challengeMessage = new System.Net.Http.HttpResponseMessage(HttpStatusCode.Unauthorized);
challengeMessage.Headers.Add("WWW-Authenticate", "Basic");
throw new HttpResponseException(challengeMessage);
}
private bool Authenticate(System.Web.Http.Controllers.HttpActionContext ActionContext)
{
if (RequireSsl && !HttpContext.Current.Request.IsSecureConnection && !HttpContext.Current.Request.IsLocal)
{
//TODO: Return false to require SSL in production - disabled for testing before cert is purchased
//return false;
}
if (!HttpContext.Current.Request.Headers.AllKeys.Contains("Authorization")) return false;
string authHeader = HttpContext.Current.Request.Headers["Authorization"];
IPrincipal principal;
if (TryGetPrincipal(authHeader, out principal))
{
HttpContext.Current.User = principal;
return true;
}
return false;
}
private bool TryGetPrincipal(string AuthHeader, out IPrincipal Principal)
{
var creds = ParseAuthHeader(AuthHeader);
if (creds != null)
{
if (TryGetPrincipal(creds[0], creds[1], creds[2], out Principal)) return true;
}
Principal = null;
return false;
}
private string[] ParseAuthHeader(string authHeader)
{
if (authHeader == null || authHeader.Length == 0 || !authHeader.StartsWith("Basic")) return null;
string base64Credentials = authHeader.Substring(6);
string[] credentials = Encoding.ASCII.GetString(Convert.FromBase64String(base64Credentials)).Split(new char[] { ':' });
if (credentials.Length != 3 || string.IsNullOrEmpty(credentials[0]) || string.IsNullOrEmpty(credentials[1]) || string.IsNullOrEmpty(credentials[2])) return null;
return credentials;
}
private bool TryGetPrincipal(string Username, string ApiKey, string RequestHash, out IPrincipal Principal)
{
Username = Username.Trim();
ApiKey = ApiKey.Trim();
RequestHash = RequestHash.Trim();
//is valid username?
IUserRepository userRepository = new UserRepository();
UserModel user = null;
try
{
user = userRepository.GetUserByUsername(Username);
}
catch (UserNotFoundException)
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.Unauthorized));
}
//is valid apikey?
IApiRepository apiRepository = new ApiRepository();
ApiModel api = null;
try
{
api = apiRepository.GetApi(new Guid(ApiKey));
}
catch (ApiNotFoundException)
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.Unauthorized));
}
if (user != null)
{
//check if in allowed role
bool isAllowedRole = false;
string[] userRoles = System.Web.Security.Roles.GetRolesForUser(user.Username);
string[] allowedRoles = Roles.Split(','); //Roles is the inherited AuthorizeAttribute.Roles member
foreach(string userRole in userRoles)
{
foreach (string allowedRole in allowedRoles)
{
if (userRole == allowedRole)
{
isAllowedRole = true;
}
}
}
if (!isAllowedRole)
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.Unauthorized));
}
Principal = new GenericPrincipal(new GenericIdentity(user.Username), userRoles);
Thread.CurrentPrincipal = Principal;
return true;
}
else
{
Principal = null;
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.Unauthorized));
}
}
}
The custom authorize attribute is governing the following controller:
public class RequestKeyAuthorizeTestController : ApiController
{
[RequestKeyAuthorizeAttribute(Roles="Admin,Bob,Administrator,Clue")]
public HttpResponseMessage Get()
{
return Request.CreateResponse(HttpStatusCode.OK, "RequestKeyAuthorizeTestController");
}
}
In the custom RoleProvider, I have this method:
public override string[] GetRolesForUser(string Username)
{
IRoleRepository roleRepository = new RoleRepository();
RoleModel[] roleModels = roleRepository.GetRolesForUser(Username);
List<string> roles = new List<string>();
foreach (RoleModel roleModel in roleModels)
{
roles.Add(roleModel.Name);
}
return roles.ToArray<string>();
}
So the issue is not how you implement the role provider, but rather how you configure your application to use it. I could not find any issues in your configuration, though. Please make sure this is indeed how you configure your application. This post may help: http://brianlegg.com/post/2011/05/09/Implementing-your-own-RoleProvider-and-MembershipProvider-in-MVC-3.aspx. If you use the default MVC template to create the project, please check the AccountController. According to that post, you may need to do a few modifications to make a custom membership provider work. But that would not affect role providers.
Best Regards,
Ming Xu.
I don't like the custom authorization attribute because I have to remind people to use it. I chose to implement the my own IIdentity/IPrincipal class and wire it up on authorization.
The custom UserIdentity that calls the default RoleProvider:
public class UserIdentity : IIdentity, IPrincipal
{
private readonly IPrincipal _original;
public UserIdentity(IPrincipal original){
_original = original;
}
public string UserId
{
get
{
return _original.Identity.Name;
}
}
public string AuthenticationType
{
get
{
return _original.Identity.AuthenticationType;
}
}
public bool IsAuthenticated
{
get
{
return _original.Identity.IsAuthenticated;
}
}
public string Name
{
get
{
return _original.Identity.Name;
}
}
public IIdentity Identity
{
get
{
return this;
}
}
public bool IsInRole(string role){
return Roles.IsUserInRole(role);
}
}
and added this to global.asax.cs:
void Application_PostAuthenticateRequest(object sender, EventArgs e)
{
if(false == HttpContext.Current.User is UserIdentity){
HttpContext.Current.User = new UserIdentity(HttpContext.Current.User);
}
}
What stimms wrote in his comment: "What I'm seeing is that the IPrincipal doesn't seem to have the correct RoleProvider set" got me looking at the implementation of my custom authentication attribute which inherits from Attribute and IAuthenticationFilter.
using System.Web.Security;
....
protected override async Task<IPrincipal> AuthenticateAsync(string userName, string password, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(userName) || string.IsNullOrWhiteSpace(password))
{
// No user with userName/password exists.
return null;
}
var membershipProvider = Membership.Providers["CustomMembershipProvider"];
if (membershipProvider != null && membershipProvider.ValidateUser(userName, password))
{
ClaimsIdentity identity = new GenericIdentity(userName, "Basic");
return new RolePrincipal("CustomRoleProvider", identity);
}
return null;
}
The key is in returning RolePrincipal, which points to your custom role provider.
Initially I returned new ClaimsPrincipal(identity), which gave me the problem described in the OP.