QuickFIX/J - Update an Acceptor session during runtime - quickfixj

I am using QuickFIX/J with Spring esanchezros/quickfixj-spring-boot-starter and I am trying to add a new session during the application runtime.
I create a Bean to initialise the ThreadSocketAcceptor then I add a SessionProvider. The application listens all the connections for FIX.4.2 sessions.
ThreadedSocketAcceptor threadedSocketAcceptor = new ThreadedSocketAcceptor(serverApplication, serverMessageStoreFactory, serverSessionSettings,
serverLogFactory, serverMessageFactory);
SessionID anySession = new SessionID(BEGINSTRING_FIX42, WILDCARD, WILDCARD);
threadedSocketAcceptor.setSessionProvider(
new InetSocketAddress("0.0.0.0", 9878),
new DynamicAcceptorSessionProvider(serverSessionSettings, anySession, serverApplication, serverMessageStoreFactory,
serverLogFactory, serverMessageFactory)
);
The application is running and during the runtime, I build the session settings and add the dynamic session
DefaultSessionFactory defaultSessionFactory = new DefaultSessionFactory(serverApplication, serverMessageStoreFactory, serverLogFactory, serverMessageFactory);
SessionSettings sessionSettings = buildSessionSettings(session.get(), sessionID);
Session quickfixSession = defaultSessionFactory.create(sessionID, sessionSettings);
threadedSocketAcceptor.addDynamicSession(quickfixSession);
OnLogon, I send a query to my database to know if the session is stored in my own table. If it is not present, I log out the session.
[4.] Update the session settings : removeDynamicSession (with logout and close) then addDynamicSession.
Is it the good way to proceed? Because, I can't update the settings for my session.

One (very) ugly way to do it is this:
Logout(), disconnect() and close() the old session.
Remove the dynamic session and clean up the old session settings:
sessionConnector.removeDynamicSession( sessionID );
try {
Dictionary dictionary = sessionConnector.getSettings().get( sessionID );
Map<Object, Object> map = dictionary.toMap();
Set<Object> keySet = map.keySet();
for ( Object object : keySet ) {
sessionConnector.getSettings().removeSetting( sessionID, object.toString() );
}
} catch ( ConfigError e ) {
// ignore
}
Then add the new session settings:
acceptor.getSettings().set( sessionID, newSessionSettings );
Then add the new session:
Class<?> clazz = acceptor.getClass().getSuperclass().getSuperclass();
Method createSessionMethod = clazz.getDeclaredMethod( "createSession", SessionID.class );
createSessionMethod.setAccessible( true );
Session newSession = ( Session )createSessionMethod.invoke( acceptor, sessionID );
acceptor.addDynamicSession( newSession );
Hope that works for you. For me it did. ;)

Related

"Failed to issue Dequeue" when using Twilio Task Router for non-phone related tasks

NOTE: Code snippets below are functional. The "dequeue" error mentioned in this post was based on an existing Assignment Callback external to these scripts. Once the URL was removed and the reservation.dequeue moved to this code, the error was resolved.
We are in the process of developing a chat application using Conversations between two people. I currently have it "wired" up with the following steps when a user initiates the chat:
Conversation is created.
User is created.
User is added to the conversation.
Task is created with conversation meta-data in attributes.
(followed by steps on the other user's session to accept the reservation, etc.)
These steps work as expected, but a "40140 - Failed to issue Dequeue instruction due to missing 'call_sid' property" is generated since the task isn't an incoming phone call. I tried putting the task into the "SMS" Task Channel, but that didn't stop the error.
I couldn't find any specific documentation on creating non-phone call-based tasks so I might be setting up the task routing incorrectly.
Here are code snippets showing how I create (in .NET) the conversation, user, and task, and how I accept (in TaskRouter.js) the reservation.
/***********************************************************************************************************
This code is server-side in .NET
***********************************************************************************************************/
public ConversationCredentials CreateConversation( string program, string name )
{
var memberId = DateTime.Now.ToString( "yyyyMMdd" ); // Temporary
TwilioClient.Init( _twilioAccountSid,_twilioAuthToken );
// If we decide to keep conversations on Twilio, we should replace the memberid with phiid, since member id might change
var conversation = ConversationResource.Create(
friendlyName: memberId + "_" + DateTime.Now.ToString( "HHmmss" )
);
var conversationCredentials = JoinConversation( conversation.Sid, name );
var taskSid = CreateTask( program, conversation.Sid, memberId );
conversationCredentials.taskSid = taskSid;
return conversationCredentials;
}
public ConversationCredentials JoinConversation( string conversationSid, string name )
{
var identity = name + "_" + DateTime.Now.ToString( "HHmmss" ); // Makes sure the user is unique, in case it's an employee joining more than one chat session)
TwilioClient.Init( _twilioAccountSid,_twilioAuthToken );
var participant = ParticipantResource.Create(
pathConversationSid: conversationSid,
identity: identity
);
var user = UserResource.Update(
pathSid: identity,
friendlyName: name
);
var token = GetJWT( _twilioConversationServiceSid, name ); // Conversation Service Sid
var conversationCredentials = new ConversationCredentials();
conversationCredentials.token = token;
conversationCredentials.conversationSid = conversationSid;
conversationCredentials.participantSid = participant.Sid;
conversationCredentials.participantName = name;
conversationCredentials.participantIdentity = participant.Identity;
return conversationCredentials;
}
public string CreateTask( string program, string conversationSid, string memberId )
{
TwilioClient.Init( _twilioAccountSid, _twilioAuthToken );
var attributes = JsonConvert.SerializeObject( new Dictionary<string,Object>()
{
{"conversationSid", conversationSid },
{"memberId", memberId },
{"program", program },
{"call_sid", "CHAT" }
}, Formatting.Indented);
var task = TaskResource.Create(
attributes: attributes,
workflowSid: _twilioWorkflowSid,
pathWorkspaceSid: _twilioWorkspaceSid_Nurses,
taskChannel: "Default"
);
return task.Sid;
}
/***********************************************************************************************************
This code is browser-side using TaskRouter.js
NOTE: This handles both voice (works fine) and conversations (the part in question)
***********************************************************************************************************/
registerTaskRouterCallbacks( _this ) : void {
this.worker.on('ready', function(worker) {
_this.updateButton( worker.activityName, "" );
});
this.worker.on("reservation.created", function(reservation) {
if ( reservation.task.attributes.type != "CHAT" )
{
_this.updateButton( "Call", reservation.task.attributes.from.replace( "+1", "" ) );
reservation.dequeue();
} else {
_this.updateButton( "Chat", reservation.task.attributes.memberId );
confirm("You have an incoming chat!");
reservation.accept();
// This is where the chat window would pop-up
}
});
this.worker.on("reservation.accepted", function(reservation) {
_this.worker.update({"ActivitySid": _this.activitySids["Busy"][0].sid});
_this.updateButton( "Busy", "" );
});
The "dequeue" error mentioned in this post was based on an existing Assignment Callback external to these scripts. Once the URL was removed and the reservation.dequeue moved to this code, the error was resolved.

Implicit grant SPA with identity server4 concurrent login

how to restrict x amount of login on each client app in specific the SPA client with grant type - implicit
This is out of scope within Identity server
Solutions tried -
Access tokens persisted to DB, however this approach the client kept updating the access token without coming to code because the client browser request is coming with a valid token though its expired the silent authentication is renewing the token by issues a new reference token ( that can be seen in the table persistGrants token_type 'reference_token')
Cookie event - on validateAsync - not much luck though this only works for the server web, we can't put this logic on the oidc library on the client side for SPA's.
Custom signInManager by overriding SignInAsync - but the the executing is not reaching to this point in debug mode because the IDM kept recognising the user has a valid toke ( though expired) kept re issueing the token ( please note there is no refresh token here to manage it by storing and modifying!!!)
Any clues how the IDM re issue the token without taking user to login screen, even though the access token is expired??(Silent authentication. ??
implement profile service overrride activeasync
public override async Task IsActiveAsync(IsActiveContext context)
{
var sub = context.Subject.GetSubjectId();
var user = await userManager.FindByIdAsync(sub);
//Check existing sessions
if (context.Caller.Equals("AccessTokenValidation", StringComparison.OrdinalIgnoreCase))
{
if (user != null)
context.IsActive = !appuser.VerifyRenewToken(sub, context.Client.ClientId);
else
context.IsActive = false;
}
else
context.IsActive = user != null;
}
startup
services.AddTransient<IProfileService, ProfileService>();
while adding the identity server service to collection under configure services
.AddProfileService<ProfileService>();
Update
Session.Abandon(); //is only in aspnet prior versions not in core
Session.Clear();//clears the session doesn't mean that session expired this should be controlled by addSession life time when including service.
I have happened to found a better way i.e. using aspnetuser securitystamp, every time user log-in update the security stamp so that any prior active session/cookies will get invalidated.
_userManager.UpdateSecurityStampAsync(_userManager.FindByEmailAsync(model.Email).Result).Result
Update (final):
On sign-in:-
var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberLogin, false);
if (result.Succeeded)
{
//Update security stamp to invalidate existing sessions
var user = _userManager.FindByEmailAsync(model.Email).Result;
var test= _userManager.UpdateSecurityStampAsync(user).Result;
//Refresh the cookie to update securitystamp on authenticationmanager responsegrant to the current request
await _signInManager.RefreshSignInAsync(user);
}
Profile service implementation :-
public class ProfileService : ProfileService<ApplicationUser>
{
public override async Task IsActiveAsync(IsActiveContext context)
{
if (context == null) throw new ArgumentNullException(nameof(context));
if (context.Subject == null) throw new ArgumentNullException(nameof(context.Subject));
context.IsActive = false;
var subject = context.Subject;
var user = await userManager.FindByIdAsync(context.Subject.GetSubjectId());
if (user != null)
{
var security_stamp_changed = false;
if (userManager.SupportsUserSecurityStamp)
{
var security_stamp = (
from claim in subject.Claims
where claim.Type =="AspNet.Identity.SecurityStamp"
select claim.Value
).SingleOrDefault();
if (security_stamp != null)
{
var latest_security_stamp = await userManager.GetSecurityStampAsync(user);
security_stamp_changed = security_stamp != latest_security_stamp;
}
}
context.IsActive =
!security_stamp_changed &&
!await userManager.IsLockedOutAsync(user);
}
}
}
*
Hook in the service collection:-
*
services.AddIdentityServer()
.AddAspNetIdentity<ApplicationUser>()
.AddProfileService<ProfileService>();
i.e. on every login, the security stamp of the user gets updated and pushed to the cookie, when the token expires, the authorize end point will verify on the security change, If there is any then redirects the user to login. This way we are ensuring there will only be one active session

Store authentification data in MVC

I have created a custom Authorize attribute where I use the Office Graph to get AAD groups the current user is member of, and based on those I reject or authorize the user. I want to save the groups, because the call to Office Graph takes some performance. What would be the correct way to save that kind of data? I can see some people saves it to a SQL server, but then I would need to ensure cleanup etc.
Also I can see in some threads the session state is stated to be a bad choice due to concurrency. So the question is what options do you have to store this kind of information?
All suggestions are welcome.
If you were only using the group_id info, there is no need to use Office Graph and store it at all. We can enable Azure AD issue the groups claims by change the manifest of Azure AD like below:(refer this code sample)
"groupMembershipClaims": "All",
And if you are also using other info about groups, you can store these info into claims. Here is a code sample that add the name of groups into claims for your reference:
AuthorizationCodeReceived = async context =>
{
ClientCredential credential = new ClientCredential(ConfigHelper.ClientId, ConfigHelper.AppKey);
string userObjectId = context.AuthenticationTicket.Identity.FindFirst(Globals.ObjectIdClaimType).Value;
AuthenticationContext authContext = new AuthenticationContext(ConfigHelper.Authority, new TokenDbCache(userObjectId));
AuthenticationResult result = await authContext.AcquireTokenByAuthorizationCodeAsync(
context.Code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, ConfigHelper.GraphResourceId);
ActiveDirectoryClient graphClient = new ActiveDirectoryClient(new Uri(ConfigHelper.GraphServiceRoot),
async () => { return await Task.FromResult(result.AccessToken); }
);
try
{
foreach (var groupClaim in context.AuthenticationTicket.Identity.FindAll("groups"))
{
var request = new HttpRequestMessage()
{
RequestUri = new Uri($"https://graph.windows.net/adfei.onmicrosoft.com/groups/{groupClaim.Value}?api-version=1.6"),
Method = HttpMethod.Get,
};
request.Headers.Authorization = new AuthenticationHeaderValue("bearer", result.AccessToken);
using (HttpClient httpClient = new HttpClient())
{
HttpResponseMessage httpResponse = httpClient.SendAsync(request).Result;
var retJSON = httpResponse.Content.ReadAsStringAsync().Result;
var dict = new JavaScriptSerializer().Deserialize<Dictionary<string, object>>(retJSON);
((ClaimsIdentity)context.AuthenticationTicket.Identity).AddClaim(new Claim("groupName", dict["displayName"].ToString()));
}
}
}
catch (Exception ex)
{
}
},
Then we can these info from controller using the code below:
ClaimsPrincipal.Current.FindAll("groupName")

Shiro / Vaadin loses session at page reload

I added Shiro session management (based on Kim's and Leif's webinar) to the Vaadin quick ticket dashboard demo application. When I do a browser reload in the application I get thrown back to the login page with no session. How / where can I prevent this.
I have a standard shiro.ini setup
Login button handler:
signin.addClickListener(new ClickListener() {
#Override
public void buttonClick(ClickEvent event) {
boolean loginOK = false;
Factory<SecurityManager> factory =
new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager((org.apache.shiro.mgt.SecurityManager)
securityManager);
Subject currentUser = SecurityUtils.getSubject();
//collect user principals and credentials in a gui specific manner
//such as username/password html form, X509 certificate, OpenID, etc.
//We'll use the username/password example here since it is the most common.
UsernamePasswordToken token =
new UsernamePasswordToken(username.getValue(), password.getValue());
//this is all you have to do to support 'remember me' (no config - built in!):
token.setRememberMe(true);
//currentUser.login(token);
try {
logger.log(Level.INFO, "trying login");
currentUser.login( token );
logger.log(Level.INFO, "login done");
//if no exception, that's it, we're done!
} catch ( Exception e ) {
logger.log(Level.INFO, "exception");
}
if ( currentUser.hasRole( "schwartz" ) ) {
loginOK = true;
} else {
loginOK = false;
}
if (loginOK) {
signin.removeShortcutListener(enter);
buildMainView();
} else {
if (loginPanel.getComponentCount() > 2) {
// Remove the previous error message
loginPanel.removeComponent(loginPanel.getComponent(2));
}
// Add new error message
Label error = new Label(
"Wrong username or password. <span>Hint: try empty values</span>",
ContentMode.HTML);
error.addStyleName("error");
error.setSizeUndefined();
error.addStyleName("light");
// Add animation
error.addStyleName("v-animate-reveal");
loginPanel.addComponent(error);
username.focus();
}
}
});
Use #preserveonRefresh annotation in UI init class
I recommend using Shiro's web filter. This way, your session will not be lost and you can prohibit unauthorized actions (e.g. Instantiating view objects) easily since Shiro's context is already set up when you display the login or any other view.

Sharing IClaimsPrincipal/FedAuth Cookie between servers/apps ID1006

I have an ASP.NET app that uses Azure ACS (and indirectly ADFS) for Authentication - which all works fine. Now I've been asked to pass the SessionToken to another backend service where it can be verified and the claims extracted. [Long Story and not my choice]
I'm having fits on the decryption side, and I'm sure I'm missing something basic.
To set the stage, the error upon decryption is:
ID1006: The format of the data is incorrect. The encryption key length is negative: '-724221793'. The cookie may have been truncated.
The ASP.NET website uses the RSA wrapper ala:
void WSFederationAuthenticationModule_OnServiceConfigurationCreated(object sender, ServiceConfigurationCreatedEventArgs e)
{
string thumbprint = "BDE74A3EB573297C7EE79EB980B0727D73987B0D";
X509Certificate2 certificate = GetCertificate(thumbprint);
List<CookieTransform> sessionTransforms = new List<CookieTransform>(new CookieTransform[]
{
new DeflateCookieTransform(),
new RsaEncryptionCookieTransform(certificate),
new RsaSignatureCookieTransform(certificate)
});
SessionSecurityTokenHandler sessionHandler = new SessionSecurityTokenHandler(sessionTransforms.AsReadOnly());
e.ServiceConfiguration.SecurityTokenHandlers.AddOrReplace(sessionHandler);
}
(the thumbprint is the same value as added by FedUtil in web.config.
I write the token with:
if (Microsoft.IdentityModel.Web.FederatedAuthentication.SessionAuthenticationModule.TryReadSessionTokenFromCookie(out token))
{
Microsoft.IdentityModel.Tokens.SessionSecurityTokenHandler th = new Microsoft.IdentityModel.Tokens.SessionSecurityTokenHandler();
byte[] results = th.WriteToken(token);
...
which gives me:
<?xml version="1.0" encoding="utf-8"?>
<SecurityContextToken p1:Id="_53382b9e-8c4b-490e-bfd5-de2e8c0f25fe-94C8D2D9079647B013081356972DE275"
xmlns:p1="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
xmlns="http://docs.oasis-open.org/ws-sx/ws-secureconversation/200512">
<Identifier>urn:uuid:54bd1bd7-1110-462b-847e-7f49c1043b32</Identifier>
<Instance>urn:uuid:0462b7d7-717e-4ce2-b942-b0d6a968355b</Instance>
<Cookie xmlns="http://schemas.microsoft.com/ws/2006/05/security">AQAAANCMnd blah blah 1048 bytes total
</Cookie>
</SecurityContextToken>
and, with the same Certificate on the other box (and the token read in as a file just for testing), I have:
public static void Attempt2(FileStream fileIn, X509Certificate2 certificate, out SecurityToken theToken)
{
List<CookieTransform> sessionTransforms = new List<CookieTransform>(new CookieTransform[]
{
new DeflateCookieTransform(),
new RsaSignatureCookieTransform(certificate),
new RsaEncryptionCookieTransform(certificate)
});
SessionSecurityTokenHandler sessionHandler = new SessionSecurityTokenHandler(sessionTransforms.AsReadOnly());
// setup
SecurityTokenResolver resolver;
{
var token = new X509SecurityToken(certificate);
var tokens = new List<SecurityToken>() { token };
resolver = SecurityTokenResolver.CreateDefaultSecurityTokenResolver(tokens.AsReadOnly(), false);
}
sessionHandler.Configuration = new SecurityTokenHandlerConfiguration();
sessionHandler.Configuration.IssuerTokenResolver = resolver;
using (var reader = XmlReader.Create(fileIn))
{
theToken = sessionHandler.ReadToken(reader);
}
}
and then ReadToken throws a FormatException of
ID1006: The format of the data is incorrect. The encryption key length is negative: '-724221793'. The cookie may have been truncated.
At this point, I can't tell if my overall approach is flawed or if I'm just missing the proverbial "one-line" that fixes all of this.
Oh, and I'm using VS2010 SP1 for the website (.NET 4.0) and I've tried both VS2010SP1 .NET 4.0 and VS2012 .NET 4.5 on the decoding side.
Thanks!
Does your app pool account for the backend service have read access to the certificate? If not give your app pool account for the backend service read access to the certificate. I had problems in the past with encryption/decryption because of this.
This might help, this will turn your FedAuth cookies into a readable XML string like:
<?xml version="1.0" encoding="utf-8"?>
<SecurityContextToken p1:Id="_548a372e-1111-4df8-b610-1f9f618a5687-953155F0C35B4862A5BCE4D5D0C5ADF0" xmlns:p1="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns="http://docs.oasis-open.org/ws-sx/ws-secureconversation/200512">
<Identifier>urn:uuid:c9f9b733-1111-4b01-8af3-23c8af3e19a6</Identifier>
<Instance>urn:uuid:ee955207-1111-4498-afa3-4b184e97d0be</Instance>
<Cookie xmlns="http://schemas.microsoft.com/ws/2006/05/security">long_string==</Cookie>
</SecurityContextToken>
Code:
private string FedAuthToXmlString(string fedAuthCombinedString)
{
// fedAuthCombinedString is from FedAuth + FedAuth1 cookies: just combine the strings
byte[] authBytes = Convert.FromBase64String(fedAuthCombinedString);
string decodedString = Encoding.UTF8.GetString(authBytes);
var store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
var thumbprint = "CERT_THUMBPRINT"; // from config
var cert = store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, false)[0];
var sessionTransforms = new List<System.IdentityModel.CookieTransform>(new System.IdentityModel.CookieTransform[]
{
new System.IdentityModel.DeflateCookieTransform(),
new System.IdentityModel.RsaSignatureCookieTransform(cert),
new System.IdentityModel.RsaEncryptionCookieTransform(cert)
});
SessionSecurityTokenHandler sessionHandler = new SessionSecurityTokenHandler(sessionTransforms.AsReadOnly());
SecurityTokenResolver resolver;
{
var token = new X509SecurityToken(cert);
var tokens = new List<SecurityToken>() { token };
resolver = SecurityTokenResolver.CreateDefaultSecurityTokenResolver(tokens.AsReadOnly(), false);
}
sessionHandler.Configuration = new SecurityTokenHandlerConfiguration();
sessionHandler.Configuration.IssuerTokenResolver = resolver;
var i = 0; // clear out invalid leading xml
while ((int)decodedString[i] != 60 && i < decodedString.Length - 1) i++; // while the first character is not <
store.Close();
return decodedString.Substring(i);
}

Resources