Synchronize REST endpoint to wait for 2 calls with same parameter - java

I am creating an application in which two players should have an opportunity to compete with each other in writing code.
For example, for now one player can initiate a session creation:
#PostMapping("/prepareSession")
public UUID prepareSession(#RequestParam("taskName") String taskName) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String currentPrincipalName = authentication.getName();
User playerOne = userService.findOne(currentPrincipalName);
Task task = taskService.findOne(taskName);
UUID sessionId = UUID.randomUUID();
sessionService.save(new Session(sessionId, playerOne, null, task));
return sessionId;
}
Then, this session id he needs to send to a player who he wants to compete.
And then second player inputs sessionId and gets a task description.
#GetMapping("/connect")
public Task connect(#RequestParam("sessionId") String sessionId) throws InterruptedException {
Session session = sessionService.findOne(sessionId);
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String currentPrincipalName = authentication.getName();
User playerSecond = userService.findOne(currentPrincipalName);
session.setPlayerSecond(playerSecond);
sessionService.save(session);
return session.getTask();
}
I wonder how to make the rest endpoint to wait until both players with same sessionId calls it and then notify them with the task description.
I want them to write code within a one session, with a one code timer.
Please suggest how I should do that

What you are looking for can be achieve like this
You could use a DeferredResult result and store it in a map until a user with the same sessionId joins. Ex
Map<String, DeferredResult<ResponseEntity<?>>> unconnected = new HashMap<String, DeferredResult<ResponseEntity<?>>>();
User one would call the connect prepareSessionAPI to receive the sessionId
User one would then call the connect API. The connet api would store this request/defferred result in the hashmap until user 2 joins. Ex
DeferredResult<Task> unconnectedTask = new DeferredResult<Task>();
unconnected.put(sessionId, unconnectedTask);
Now, user one's request would be stored in an in memory map until user two joins
User one would send the sessionId to user two
User two would call the connect API. The connect API would look for the session in the HashMap, and if it exists, it would perform the operations needed, then set the result of the deferred result. Ex
DeferredResult<Task> unconnectedTask = unconnected.get(sessionId);
if(unconnectedTask != null) {
// Do work
unconnectedTask.setResult(task);
} else {
// This is user one's request
}
Please note, this is pseudo code.

put this code both of method,
please import spring transnational jar
#Transactional(propagation =Propagation.REQUIRED,isolation=Isolation.SERIALIZABLE,readOnly=false,transactionManager="transactionManager")
if any issue inform

Related

Azure Select Account shown when user already logged in session

We've migrated from adal4j to msal4j in our java web applications.
All works well but the big difference is that when the user is already logged (maybe in other applications but same browser session) we always see the "select user" page and the user is not logged automatically and redirected to redirect uri as before with adal4j.
This is how we redirect to autentication page:
private static void redirectToAuthorizationEndpoint(IdentityContextAdapter contextAdapter) throws IOException {
final IdentityContextData context = contextAdapter.getContext();
final String state = UUID.randomUUID().toString();
final String nonce = UUID.randomUUID().toString();
context.setStateAndNonce(state, nonce);
contextAdapter.setContext(context);
final ConfidentialClientApplication client = getConfidentialClientInstance();
AuthorizationRequestUrlParameters parameters = AuthorizationRequestUrlParameters
.builder(props.getProperty("aad.redirectURI"), Collections.singleton(props.getProperty("aad.scopes"))).responseMode(ResponseMode.QUERY)
.prompt(Prompt.SELECT_ACCOUNT).state(state).nonce(nonce).build();
final String authorizeUrl = client.getAuthorizationRequestUrl(parameters).toString();
contextAdapter.redirectUser(authorizeUrl);
}
I've tried to remove .prompt(Prompt.SELECT_ACCOUNT)
but I receive an error
Any ideas?
• You might be getting the option for selecting the user account after switching to MSAL4J in your browser even after the SSO is enabled because either clearing the token cache is enabled in your code or MsalInteractionRequiredException option is thrown and specified accordingly due to which the application asks for a token interactively.
Thus, please check which accounts information is stored in the cache as below: -
ConfidentialClientApplication pca = new ConfidentialClientApplication.Builder(
labResponse.getAppId()).
authority(TestConstants.ORGANIZATIONS_AUTHORITY).
build();
Set<IAccount> accounts = pca.getAccounts().join(); ’
Then, from the above information, if you want to remove the accounts whose prompts you don’t want to see during the user account selection such that the default account should get selected and signed in automatically, execute the below code by modifying the required information: -
Set<IAccount> accounts = pca.getAccounts().join();
IAccount accountToBeRemoved = accounts.stream().filter(
x -> x.username().equalsIgnoreCase(
UPN_OF_USER_TO_BE_REMOVED)).findFirst().orElse(null);
pca.removeAccount(accountToBeRemoved).join();
• And for the MsalInteractiveRequiredException class in the code, kindly refer to the below official documentation link for the AcquireTokenSilently and other reasons responsible for the behaviour. Also, refer to the sample code given below for your reference regarding the same: -
https://learn.microsoft.com/en-us/azure/active-directory/develop/msal-error-handling-java#msalinteractionrequiredexception
IAuthenticationResult result;
try {
ConfidentialClientApplication application =
ConfidentialClientApplication
.builder("clientId")
.b2cAuthority("authority")
.build();
SilentParameters parameters = SilentParameters
.builder(Collections.singleton("scope"))
.build();
result = application.acquireTokenSilently(parameters).join();
}
catch (Exception ex){
if(ex instanceof MsalInteractionRequiredException){
// AcquireToken by either AuthorizationCodeParameters or DeviceCodeParameters
} else{
// Log and handle exception accordingly
}
}

How can I get user roles from current session?

After login in my system with Keycloak I want to get the user role-mappings(defined on keycloak admin console) from the user I'm logged in.
I'm able to get the First Name, Last Name, Id, token-id, but when trying to get the roles I get an empty array:
private List<KeycloakOidcProfile> getUserData() {
final PlayWebContext context = new PlayWebContext(ctx(), playSessionStore);
final ProfileManager<KeycloakOidcProfile> profileManager = new ProfileManager(context);
System.out.println("Roles->>>"+ profileManager.get(true).get().getRoles()); //here i get -> []
System.out.println("FirstName->>>"+ profileManager.get(true).get().getFirstName());
System.out.println("Last Name->>>"+ profileManager.get(true).get().getFamilyName());
System.out.println("ID->>>"+ profileManager.get(true).get().getId());
return profileManager.getAll(true);
I believe that, you should implement your own AuthorizationGenerator and attach it to the client (to Keycloak client in this case) in order to map token roles to
KeycloakOidcProfile, but be sure that OIP sends the roles in his token.
There is some glue http://www.pac4j.org/docs/clients.html

Refreshing an Access Token for Client Credentials Flow

I was wondering what the best way is for me to refresh an access token that is obtained through the client credentials flow within OAuth 2.0. I've read over the spec, but I can't seem to be able to find an answer for my particular situation.
For my specific case, I am using the Spotify Web API for my Android app in order to search for content (tracks, albums, and/or artists). In order to perform a search, I need an access token. Since I'm not interested in a Spotify user's data, I can use the client credentials flow to obtain the access token, which is explain in Spotify's terms here.
Because the access token can eventually expire, I had to figure out a way to refresh it once expiration occurred. What I'm ultimately wondering is if my approach is valid and if there's any concern with how I've approached this.
First and foremost, I stored my access token within SharedPreferences. Within onCreate(), I have the following:
#Override
protected void onCreate(Bundle savedInstanceState) {
// A bunch of other stuff, views being initialized, etc.
mAccessToken = getAccessToken();
// If the access token is expired, then we will attempt to retrieve a new one
if (accessTokenExpired()) {
retrieveAccessToken();
}
}
I've defined accessTokenExpired() and retrieveAccessToken() as follows:
private boolean accessTokenExpired() {
// If mAccessToken hasn't yet been initialized, that means that we need to try to retrieve
// an access token. In this case, we will return true;
if (mAccessToken == null) {
return true;
}
SharedPreferences preferences = getPreferences(Context.MODE_PRIVATE);
long timeSaved = preferences.getLong(PREFERENCES_KEY_TOKEN_RESPONSE_TIME_SAVED, 0L);
long expiration = preferences.getLong(PREFERENCES_KEY_TOKEN_RESPONSE_EXPIRATION, 0L);
long now = System.currentTimeMillis()/1000;
long timePassed = Math.abs(now - timeSaved);
if (timePassed >= expiration) {
return true;
} else {
return false;
}
}
One thing worth noting about retrieveAccessToken() is that I'm using Retrofit for my HTTP request:
private void retrieveAccessToken() {
// First, we obtain an instance of SearchClient through our ClientGenerator class
mClient = ClientGenerator.createClient(SearchClient.class);
// We then obtain the client ID and client secret encoded in Base64.
String encodedString = encodeClientIDAndSecret();
// Finally, we initiate the HTTP request and hope to get the access token as a response
Call<TokenResponse> tokenResponseCall = mClient.getAccessToken(encodedString, "client_credentials");
tokenResponseCall.enqueue(new Callback<TokenResponse>() {
#Override
public void onResponse(Call<TokenResponse> call, Response<TokenResponse> response) {
Log.d(TAG, "on Response: response toString(): " + response.toString());
TokenResponse tokenResponse = null;
if (response.isSuccessful()) {
tokenResponse = response.body();
Log.d(TAG, tokenResponse.toString());
mAccessToken = tokenResponse.getAccessToken();
saveAccessToken(tokenResponse);
}
}
#Override
public void onFailure(Call<TokenResponse> call, Throwable t) {
Log.d(TAG, "onFailure: request toString():" + call.request().toString());
mAccessToken = "";
}
});
}
Finally, saveAccessToken(tokenResponse) is sort of the complement of accessTokenExpired(), where I'm saving the values from the token response into SharedPreferences rather than retrieving them.
Are there any concerns with how I'm doing this? I got the idea from this SO post and slightly modified it. Spotify doesn't provide a refresh token in their access token response. Therefore, I can't make use of it here to reduce the number of access token requests I make.
Any input on this would be greatly appreciated!
Two considerations are:
you probably want some error handling around the requests you make using the access token that can handle the token expiring and do retries. The two situations where this will help are
when the token expires between checking if it's valid and your usage of it
when in the cycle of check the token is valid -> make some requests with the token -> repeat, you spend over an hour using the token. Another way you can do it is to calculate now + expected_api_request_time > token_expiration_time where expected_api_request_time would be a constant you set, but I think handling token expiry as an exception is better practice (you probably want to be able to make retries anyway in cases of network instability).
you can perform the calculations to work out when the token expires either when you retrieve the timeSaved and expiration from your local storage, or just calculate the time the token will expire initially and save that. This is relatively minor, both this and the way you've done it are fine I think.

How to store values in a servlet without re initializing them each time a servlet is called

I'm making a queue management system. I've hit on stump here. I'm letting the customer/user select their desired service whose token they want to get, but the thing is each time the servlet is called it re-initializes the service-option objects and the token number for that chosen services goes back to 1. How can I store the token count so that it doesn't goes back to 1 again.
public class XmlServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
String Service_Option = request.getParameter("Service_Option");
out.println("You selected Service is: "+ Service_Option);
Customer_Console cc1 = new Customer_Console();
Customer_Console cc2 = new Customer_Console();
Customer_Console cc3 = new Customer_Console();
if(Service_Option.equals("Cash Withdrawal"))
{
cc1.setConsole(1,Service_Option);
Database_Manager.Insert(cc1);
}
else if(Service_Option.equals("Account Service"))
{
cc2.setConsole(2,Service_Option);
Database_Manager.Insert(cc2);
}
else
{
cc3.setConsole(3,Service_Option);
Database_Manager.Insert(cc3);
}
}
}
The Console class contains
private int serviceNum;
private String Service_Option;
private Token token;
and the setConsole method is
public void setConsole(int sNum,String sName)
{
serviceNum = sNum;
Service_Option = sName;
token.incrementToken();
}
UPDATE
I'm having problems with dealing the session for more then 1 customer consoles
HttpSession session = request.getSession(false);
if(session == null)
{
session = request.getSession(true);
Integer count = 0;
session.setAttribute("tokenCount",count);
}
if(Service_Option.equals("Cash Withdrawal"))
{
Integer count = (int)session.getAttribute("tokenCount");
count = new Integer(count.intValue() + 1);
cc1.setToken(count);
Database_Manager.Insert(cc1);
session.setAttribute("tokenCount",count);
}
Also how can I reset the session that every time I restart tomcat I get started with the token number 1.
If I'm supposed to use only one session for all three consoles then how can I do that?
I tried with placing the console objects
if(session == null)
{
session = request.getSession(true);
session.setAttribute("cc1", cc1);
}
if(Service_Option.equals("Cash Withdrawal"))
{
cc1.issueToken();
session.setAttribute("cc1", cc1);
cc1 = (Customer_Console)session.getAttribute("cc1");
Database_Manager.Insert(cc1);
}
But still it doesn't save the increments, again reinitializes the token, why is that?
Store Your service-option Object in session Scope So every time your servlet it called you can access the Object using
HttpSession session = request.getSession();
Console service-option=null;
service-option=(Console)session.getAttribute("console");
if(service-option==null){
service-option=new Console();
}
// After doing your work(setting some variables etc)of Console Class Object
// you can set that Object in Session Like this
session.setAttribute("console", service-option); // here "console" is key
In this way it is reinitialized only for the first time
As Java Docs Says
void setAttribute(String name,Object value)
Binds an object to this session, using the name specified. If an
object of the same name is already bound to the session, the object is
replaced.
Update
A HttpSession is created when calling request.getSession() and it get destroyed/invalidated when you call session.invalidate() or session timeout occurs .
Now coming to
Also how can I reset the session that every time I restart tomcat I
get started with the token number 1. If I'm supposed to use only one
session for all three consoles then how can I do that?
It will automatically get restarted but there will be no token number exist into it because it is fresh , if you need to access token number from your entire application then i suggest you to use Servlet Context
An object of ServletContext is created by the web container at time of
deploying the project.This object can be used to get configuration
information from web.xml file. There is only one ServletContext object
per web application.
If any information is shared to many servlet, it is better to provide
it from the web.xml file using the element.
Now your another confusion
I'm having problems with dealing the session for more then 1 customer
consoles
Please read Session Management in Java . As this topic is too broad so i have given you the link to read it yourself

How to accept buddy request properly in Android smack?

There is a openfire server and Android clients (smack). All clients can add each other to buddy/roster list (without authorization, I want user can see each other without accept buddy request). I have some problems of getting the Presence information of the buddy request sender.
Assume there are 2 users - User A, User B.
I can add User B to User A's Roster by:
Roster roster = xmppManager.connection.getRoster();
roster.setSubscriptionMode(Roster.SubscriptionMode.accept_all);
roster.createEntry("userB", "userB#abc.com", null);
I can see User B at User A's roster list. Everything is fine so far
There are few problems with User B. I state what is the problem at the code below:
//I have set the available and status of User A by:
//xmppManager.setStatus(available, bundle.getString("new_status"));
...
// That's how I get Roster and Presence of user A
Roster roster = connection.getRoster();
Collection<RosterEntry> entries = roster.getEntries();
for (RosterEntry entry : entries) {
Presence presence = roster.getPresence(entry.getUser());
// User A always not available even I set User A to available
Log.e(TAG, "presence.isAvailable() = " + presence.isAvailable());
// User A's status always empty
Log.e(TAG, "presence.getStatus() = " + presence.getStatus());
// User A's getName() always null
if (entry.getName() != null)
{
name = entry.getName();
}
else
Log.e(TAG, "GetName is null");
}
Do I need to createEntry() at User A? Or do I need to do something with the buddy request like this?
#Override
public void entriesAdded(Collection<String> collection) {
String user = "";
Iterator<String> it = collection.iterator();
if(it.hasNext()){
user=it.next();
}
Presence presence = new Presence(Presence.Type.subscribe);
presence.setTo(user);
connection.sendPacket(presence);
}
But it does not work. It seems that I need to do something to user B first. Any idea is welcome, thanks!
Okay, I toiled hard at this for a couple of days and finally got things working. I have implemented it with a manual subscription mode (ie. user needs to accept another user's request manually). There is no need to create entries yourself as the server handles this automatically according to the presences sent/received.
For your case (automatic subscription), simply send a subscribe and subscribed presence immediately instead of saving the request locally.
This is how it works:
User1 sends subscribe presence to User2.
Roster entry gets automatically created in User1's roster (but not in User2's roster).
User2 receives subscribe request from User1.
User2 sends back a subscribed presence to User2 (User2 > User1 subscription complete).
User2 checks if User1 is in User2's roster. User1 is not in User2's roster. User2 sends back a subscribe presence to User1.
Roster entry gets automatically created in User2's roster.
User1 receives subscribe presence from User2.
User1 checks if User2 is in User1's roster. User2 is in User1's roster. User1 sends back a subscribed presence to User2 (User2 > User1 subscription complete).
final Presence newPresence = (Presence) packet;
final Presence.Type presenceType = newPresence.getType();
final String fromId = newPresence.getFrom();
final RosterEntry newEntry = getRoster().getEntry(fromId);
if (presenceType == Presence.Type.subscribe)
{
//from new user
if (newEntry == null)
{
//save request locally for later accept/reject
//later accept will send back a subscribe & subscribed presence to user with fromId
//or accept immediately by sending back subscribe and unsubscribed right now
}
//from a user that previously accepted your request
else
{
//send back subscribed presence to user with fromId
}
}

Categories

Resources