RCF allows you to apply access controls to individual servant bindings on your server. The access control is implemented as a user-defined callback function, in which you can apply application-specific logic to determine whether a client connection should be allowed to access a particular servant binding.
For example:
bool onHelloWorldAccess(int dispatchId) { // Return true to allow access, and false to deny access. // ... return true; }
HelloWorldImpl helloWorldImpl; RCF::RcfServer server( RCF::TcpEndpoint(50001) ); RCF::ServerBindingPtr bindingPtr = server.bind<I_HelloWorld>(helloWorldImpl); bindingPtr->setAccessControl( boost::bind(&onHelloWorldAccess, _1) ); server.start();
The access control callback will be invoked by the RcfServer
each time a client tries to call a method on that servant. From the access
control callback you can inspect the current session and determine whether
it should be granted access to the servant. Once authentication is granted,
you will probably want to store the authentication state in a session object,
so it can be easily reused on subsequent calls:
// App-specific authentication state. class HelloWorldAuthenticationState { public: HelloWorldAuthenticationState() : mAuthenticated(false) { } bool mAuthenticated; std::string mClientUsername; }; // Servant binding access control. bool onHelloWorldAccess(int dispatchId) { RCF::RcfSession & session = RCF::getCurrentRcfSession(); HelloWorldAuthenticationState & authState = session.getSessionObject<HelloWorldAuthenticationState>(true); if (!authState.mAuthenticated) { // Here we are checking that the client is using either NTLM or Kerberos. RCF::TransportProtocol tp = session.getTransportProtocol(); if (tp == RCF::Tp_Ntlm || tp == RCF::Tp_Kerberos) { authState.mAuthenticated = true; authState.mClientUsername = session.getClientUsername(); } } return authState.mAuthenticated; }
As the previous example indicates, typically you would use the access control callback to inspect the transport protocol the client is using, and use that to determine the identity of the client.
In some situations you may want the client to provide extra authentication
information, beyond what is available though the transport protocol. This
typically means having the equivalent of a Login()
method on the interface, that needs to
be called before any other method on the interface. The access control callback
can be used to verify that Login()
is called before any other method:
// App-specific login info. class LoginInfo { public: // ... void serialize(SF::Archive & ar) {} }; RCF_BEGIN(I_HelloWorld, "I_HelloWorld") RCF_METHOD_V1(void, Login, const LoginInfo &) RCF_METHOD_V1(void, Print, const std::string &) RCF_END(I_HelloWorld) // App-specific authentication state. class HelloWorldAuthenticationState { public: HelloWorldAuthenticationState() : mAuthenticated(false) { } bool mAuthenticated; std::string mClientUsername; LoginInfo mLoginInfo; }; // Servant object. class HelloWorldImpl { public: void Login(const LoginInfo & loginInfo) { RCF::RcfSession & session = RCF::getCurrentRcfSession(); HelloWorldAuthenticationState & authState = session.getSessionObject<HelloWorldAuthenticationState>(true); if (!authState.mAuthenticated) { RCF::TransportProtocol tp = session.getTransportProtocol(); if (tp == RCF::Tp_Ntlm || tp == RCF::Tp_Kerberos) { authState.mAuthenticated = true; authState.mClientUsername = session.getClientUsername(); authState.mLoginInfo = loginInfo; } } } void Print(const std::string & s) { std::cout << "I_HelloWorld service: " << s << std::endl; } }; // Servant binding access control. bool onHelloWorldAccess(int dispatchId) { if (dispatchId == 0) { // Calls to Login() are always allowed through. return true; } else { RCF::RcfSession & session = RCF::getCurrentRcfSession(); HelloWorldAuthenticationState & authState = session.getSessionObject<HelloWorldAuthenticationState>(true); return authState.mAuthenticated; } }
In this case, the access control callback allows calls to Login()
to go straight through, while for any other
method on the I_HelloWorld
interface, it checks for the existence of the authentication state that the
Login()
call creates.
The Login()
method is identified in the access control callback by its dispatch ID -
in this case 0, as it is the first method on the interface. Dispatch ID's
are assigned in incremental order, from 0, so if for example Login()
had been the third method on the interface, it would have had a dispatch
ID of 2.