Create Gentics Mesh User Profile node during JWT user mapping - causes failure - java

I'm working on an authentication plugin that uses JWT parsing to get details and update the user in Mesh.
I'd like to also create a new node and attach it to the User in Mesh, using the user.setNodeReference() // Is this how I associate a User to a node?
The problem is when I return the mapping result, if I create the user profile node, I see the mapToken() method invoked again with the same token as before, like it's looping. I've found this is due to the 'retry' capabilities in the router
If I dont attach a node to the user.nodeReference() then it proceeds as expected.
Thoughts?
#Override
public MappingResult mapToken(HttpServerRequest req, String uuid, JsonObject token) {
MappingResult result = new MappingResult();
if (uuid == null) {
log.info("First time login of the user");
} else {
log.info("Already synced user is logging in.");
}
log.info("Mapping user in plugin");
printToken(token);
String username = extractUsername(token).get();
UserUpdateRequest user = new UserUpdateRequest();
user.setUsername(username);
user.setEmailAddress(username);
user.setFirstname(token.getString("firstname", "firstname"));
user.setLastname(token.getString("lastname", "lastname"));
// TODO: Stop the infinite loop
if (uuid == null) {
log.info("Creating profile node");
user.setNodeReference(createProfileNode(username, token));
} else {
log.info("Updating profile node");
//updateProfileNode(uuid, token);
}
result.setUser(user);
...
}
private ExpandableNode createProfileNode(String username, JsonObject token) {
NodeCreateRequest nodeCreateRequest = new NodeCreateRequest()
.setLanguage("en")
.setSchemaName(getConfig().getProfileSchema())
.setParentNodeUuid(getConfig().getProfileParentUuid());
FieldMap fields = nodeCreateRequest.getFields();
fields.putString("name", username);
fillProfileFieldMappedValues(fields, token);
nodeCreateRequest.setFields(fields);
return this.adminClient.createNode(getConfig().getProjectName(), nodeCreateRequest).blockingGet();
}
Update
I checked the jti & iat - the token contains both.
I thought maybe if I subscribe to the USER_CREATED event, I could add a profile node after the user is created.
But I don't see this ever executed. I may be incorrectly subscribing to the local event bus.
getRxVertx().eventBus().localConsumer(MeshEvent.USER_CREATED.getAddress()).handler((message) -> {
try {
String uuid = JsonUtil.getMapper().readTree(message.body().toString()).get("uuid").asText();
adminClient().findUserByUuid(uuid).toSingle().doAfterSuccess(u -> {
u.setNodeReference(createProfileNode(u.getUuid()).getBody());
}).doOnError(e -> {
log.error("Failed to create user profile node: {}", e);
});
} catch (IOException e) {
log.error("Failed to deserialize user: {}", e);
}
});
Also, I don't need to set the user.setNodeReference() to reproduce the error, I only need to try creating a new node in the mapToken method. It will retry creating the user 10x then error out with an http 500.
I'll turn up logging to see if I can get more details.
Update
I've found that if I create the user first in the mapToken function, then create a node for the profile, I can add it to the user.setNodeReference() but I never see the node in the content browser [I create it at `{project}/profiles/{userProfileNode}], and I'm not able to see the node reference when I retrieve the user.
But the logs show the node was created successfully.

Does your token contain a token Id? (jti or iat). Mesh will use one of these values to determine whether the key mapping needs to be re-run for the token. The idea behind this is to avoid bogus mapping calls for tokens that have not changed. I suspect your token does not pass this check and will be passed always to the mapper plugin.
I might be able to give you more hints if I could see some logs.

Related

How to call `GetParam` method from a controller method with ResponseEntity<> return type within the same class

I am trying to call a GetParam method from another controller within the same Controller class to refresh the page after some set of operations. This is what I have tried.
#GetMapping("/inventories")
public String showInventories(Model model) {
model.addAttribute("inventories", inventoriesService.getUserInventory());
return "user_inventories";
}
#PostMapping(value = "create_inventory_record", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> createInventoryRecord(#RequestBody MultiValueMap<String, String> inventoryRecord)
throws Exception {
InventoryRecord newInventory = new InventoryRecord(
0,
inventoryRecord.getFirst("addProductType"),
inventoryRecord.getFirst("addProductDescription"),
inventoryRecord.getFirst("addProductCategory")
);
// Response HashMap
HashMap<String, String> inventoryCreationResponse = inventoryService
.createInventoryRecord(newInventory);
boolean inventoryCreated = Boolean.parseBoolean(limitCreationResponse.get("ItemCreated"));
boolean inventoryItemExists = Boolean.parseBoolean(limitCreationResponse.get("ItemAlreadyExists"));
// Check if item created/stored
if (!inventoryCreated) {
// Check if item already exists
if (!inventoryItemExists) {
// Throw exception if Inventory didn't exist but still record wasn't created
return ResponseEntity.unprocessableEntity().body("Inventory item not created!!");
} else {
// Inventory already exists, advice user on front-end
return ResponseEntity.ok(inventoryCreationResponse);
}
} else {
// Refresh inventories page
return ResponseEntity.status(HttpStatus.FOUND).location(URI.create("inventories")).build();
}
}
The showInventories(Model model) method shows/loads the inventory when the page is loaded. I have a button within the page that shows a form to get inputs for the inventory details. I then send a POST request to the springboot controller. The data is received and the whole process succeeds i.e., the data is stored in the database, I get a response on whether the record was created, if not it will notify for existing similar record, if not throw an exception.
Problem
The controller that receives the data before processing is of type ResponseEntity<?>, the one named createInventoryRecord. After checking for record creation, and if record exists, in case the record was stored successfully, I want to call the showInventories method with the #GetMapping("inventories").
I have tried using ResponseEntity as below to try calling that path but it does not work.
My problem is.
return ResponseEntity.status(HttpStatus.FOUND).location(URI.create("inventories")).build();
There is something I am missing, where am I going wrong, what should I do, or do better?
try returning with redirect:/, like this:
return "redirect:/inventories";

How to retrieve UID of other users and not the current user? [duplicate]

This question already has answers here:
How to exclude an element from a Firestore query?
(8 answers)
Closed 1 year ago.
I am trying to retrieve UID of all the users except of the current user. I tried to do FirebaseAuth.getInstance().getUid(); but it returns CurrentUserUid. Is there a way for NOT getting UID of the current user?
When you are using your app, you are considered as a normal user and not an admin or privileged user. Client SDKs cannot fetch information about other users. You would have to use a secure environment such as Cloud Functions or your own along with Admin SDK to retrieve information about other users.
You can create a Callable Function that fetched a specific user by email or UID like this:
export const getUser = functions.https.onCall(async (data, context) => {
// Verify if the user requesting data is authorized
const {email} = data;
const userRecord = await admin.auth().getUserByEmail(email)
return userRecord.uid
});
You can then call this function from your Android app like this:
private Task<String> getUserInfo(String userEmail) {
// Create the arguments to the callable function.
Map<String, Object> data = new HashMap<>();
data.put("email", userEmail);
return mFunctions
.getHttpsCallable("getUser")
.call(data)
.continueWith(new Continuation<HttpsCallableResult, String>() {
#Override
public String then(#NonNull Task<HttpsCallableResult> task) throws Exception {
// This continuation runs on either success or failure, but if the task
// has failed then getResult() will throw an Exception which will be
// propagated down.
String result = (String) task.getResult().getData();
return result;
}
});
}
Do note that you must check if the user calling the function and requesting the data is authorized. You can check custom claims, the UID or any identifier that decides who can view others users' information.
If your application requires anyone to view all information, then you would have to use any database that stores users' information and can be fetched on your app because you cannot fetch that data using Client Auth SDK.

Does public static AutocompleteSessionToken newInstance () return same instance?

Does this method behave on singleton pattern? or does it create new session for the user each time?
the below method is called on edittext change listener.
#NonNull
private ArrayList<PlaceAutocomplete> getPredictions(#NonNull CharSequence constraint) {
final ArrayList<PlaceAutocomplete> resultList = new ArrayList<>();
// Create a new token for the autocomplete session. Pass this to FindAutocompletePredictionsRequest,
// and once again when the user makes a selection (for example when calling fetchPlace()).
AutocompleteSessionToken token = AutocompleteSessionToken.newInstance();
//https://gist.github.com/graydon/11198540
// Use the builder to create a FindAutocompletePredictionsRequest.
FindAutocompletePredictionsRequest request = FindAutocompletePredictionsRequest.builder()
.setLocationBias(bounds)
.setCountry("PK")
.setSessionToken(token)
.setQuery(constraint.toString())
.build();
Task<FindAutocompletePredictionsResponse> autocompletePredictions = placesClient.findAutocompletePredictions(request);
// This method should have been called off the main UI thread. Block and wait for at most
// 60s for a result from the API.
try {
Tasks.await(autocompletePredictions, 60, TimeUnit.SECONDS);
} catch (#NonNull ExecutionException | InterruptedException | TimeoutException e) {
e.printStackTrace();
}
if (autocompletePredictions.isSuccessful()) {
FindAutocompletePredictionsResponse findAutocompletePredictionsResponse = autocompletePredictions.getResult();
if (findAutocompletePredictionsResponse != null)
for (AutocompletePrediction prediction : findAutocompletePredictionsResponse.getAutocompletePredictions()) {
Log.i(TAG, prediction.getPlaceId());
resultList.add(new PlaceAutocomplete(prediction.getPlaceId(), prediction.getPrimaryText(STYLE_NORMAL).toString(), prediction.getFullText(STYLE_BOLD).toString()));
}
return resultList;
} else {
return resultList;
}
}
the method calls on each text change in editText.
The AutocompleteSessionToken.newInstance() method returns a new instance, i.e. a new session token. Each time you call this method, you are creating a new session token.
Google explains how autocomplete sessions work here:
Session tokens group the query and selection phases of a user
autocomplete search into a discrete session for billing purposes. The
session begins when the user starts typing a query, and concludes when
they select a place. Each session can have multiple queries, followed
by one place selection. Once a session has concluded, the token is no
longer valid; your app must generate a fresh token for each session.
The text in editText changes when the user has selected a place from autocomplete predictions (the session ends, a new one will be created when the user selects a new place).
Hope this helps!

Forcing logout of a user in vaadin. How to show a message to force logged out user

In my vaadin web app, an admin user should be able to forcefully logout a currently logged in user. When a user is forcefully logged out, he should be immediately redirected to the login page and an error message should be shown to the user that he has been forcefully logged out.
So far, I have written following code, which successfully logs out the user to the login page.
try {
vaadinSession.lock(); //The session to be forcefully logged out
try {
vaadinSession.getUIs().forEach(ui -> {
if (ui.getPage() != null) {
ui.getPage().setLocation("");
ui.push();
Notification notification = new Notification("You have been forcefully logged out", Notification.Type.WARNING_MESSAGE);
notification.setDelayMsec(-1);
notification.show(ui.getPage());
ui.push();
}
});
} catch (Exception e) {
logger.error("Exception triggered when redirecting pages on forceDisconnect " + e.getLocalizedMessage(), e);
}
vaadinSession.close();
} finally {
vaadinSession.unlock();
}
But, the notification shown in the code is not actually shown to the user. I think it is because a new Vaadin session gets created when vaadinSession.close(); is called. If I show a notification in the new vaadin session, I think it will successfully get displayed.
But, I have no idea how I can access the new session after I call vaadinSession.close();.
Can someone point me a way how I can achieve this?
May not be ideal, but following is how I got this done finally.
In forceDisconnect() method, set the message as a session variable in the underlying session of VaadinSession
vaadinSession.getSession().setAttribute("PrevSessionError", "You have been forcefully logged out");
In attach() of the login view, show the message to the user if previously set variable was found.
#Override
public void attach() {
super.attach();
Object previousSessionError = getSession().getSession().getAttribute("PrevSessionError");
if (previousSessionError != null) {
Notification notification = new Notification(previousSessionError.toString(), Notification.Type.ERROR_MESSAGE);
notification.setDelayMsec(-1);
notification.show(getUI().getPage());
getSession().getSession().setAttribute("PrevSessionError", null);
}
}
This works because the underlying session does not change even when VaadinSession gets changed. I don't know whether this is reliable or not, but this is all I could do.

Parse getCurrentUser() is returning null after login successful

In an android app, which uses Parse, the login flow is like this...
We have our own logic to see if the user has entered the correct credentials. Once we verify that, signUpOrLoginOnParse() is called. Parse is used just to store data and handle sessions locally. Users can not access the api without the token.
private void signUpOrLogin(final String username, final String token) {
ParseUser user = new ParseUser();
user.setUsername(username);
user.setPassword(username);
user.signUpInBackground(new SignUpCallback() {
#Override
public void done(ParseException e) {
if (e == null) {
// sign up success. ParseUser.getCurrentUser() populated
saveTokenToCloud(token);
} else if ("condition to check if the user already exists") {
// existing user, login.
ParseUser.logInInBackground(username, username, new LogInCallback() {
#Override
public void done(ParseUser parseUser, ParseException e) {
// login was successful, ParseUser.getCurrentUser() populated
saveTokenToCloud(token);
}
});
} else {
showProgress(false);
e.printStackTrace();
}
}
});
}
private void saveTokenToCloud(String token) {
// saving token to cloud
ParseUser user = ParseUser.getCurrentUser();
user.put("token", token); // THIS IS WHERE I GET NULL POINTER EXCEPTION
user.saveEventually();
// link installation to user.
ParseInstallation parseInstallation = ParseInstallation.getCurrentInstallation();
parseInstallation.put("user", user);
parseInstallation.saveEventually();
// Starting next activity
Intent i = new Intent(this, NextActivity.class);
i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(i);
finish();
}
All good when I first run the app. Once, logout button is pressed (contains - Parse.logoutInBackground()), it shows the LoginActivity (current one). When trying to log in, everything succeeds but I get a NullPointerException at line 3 of saveTokenToCloud().
It says - trying to invoke virtual method .put() over a null object reference.
But, isn't Parse.currentUser() already populated since this method is called from callback of methods that do that ?
It works after restarting the app. But then the same continues if logout is pressed.
After calling logoutInBackground , future calls to getCurrentUser() will return null.
You will need to initialize the user again.
signUpInBackground will create a new ParseUser on the server, and also persist the session on disk so that you can access the user using ParseUser.getCurrentUser().
However i am not sure you should be calling it every single time you log in.
Instead of calling the getCurrentuser inside the saveToken method you can pass the user to the saveTokenMethod from the done callback parameter.
Separate you logic in distinct methods for sign up and logIn. I suggest you check before calling signUp, and not abusing it every time you want to login

Categories

Resources