I have a GWT App where I need to call a webservice to check whether the user signed in is an administrator - and then set the admin Div to visible (hidden by default) if the web service returns true.
The problem is the program passes the check before the web service can return the result. It's looking something like this
public class ModelClass{
boolean isAdmin = false;
public ModelClass(){
//Call webservice in constructor, if returns true, set isAdmin to true via setter
}
}
Then, in my widget, I create an instance of the ModelClass and then in the last step before the page finishes loading, I check the isAdmin property to see if it's true, if so - set the Admin panel to visible. No matter how early I try to make the call, and how late I check the property, the admin check always happens before the web service response returns.
I've tried change listeners - but they only apply to widgets. I tried rigging the property as a label and using a click event by calling click() on the label from the web service response.
Nothing seems to work - does anyone have any ideas?
If you are using a callback mechanism, you will have to do it in the callback function.
e.g. If you are using the GWT's request builder, You will have to do it in onResponseReceived of your request callback:
public ModelClass() {
isAdmin();
}
private void isAdmin() {
RequestBuilder builder = new RequestBuilder(
RequestBuilder.GET, webserviceurl);
try {
request = builder.sendRequest(null, new RequestCallback() {
public void onResponseReceived(Request request,
Response response) {
int code = response.getStatusCode();
if(code >= 400) {
Window.alert(response.getStatusText());
return;
}
if(code == 200) {
// if admin is logged in
// hide your div
}
}
public void onError(Request request, Throwable exception) {
Window.alert("Error checking admin status");
}
});
}catch(RequestException re) {
Window.alert("Error checking admin status");
}
}
Related
I want to keep some information in Keycloak as custom user attributes.
Some of them should be managed by the user itself. Other attributes should be managed only by a Keycloak administrator. Attributes managed by the administrator should be read-only visible in the "Edit account" web page for the user.
I went through the guide to add custom user attributes in this page and customized the "Edit account" web page.
My question is:
Is it ensured that the user cannot change the attribute that is meant as read-only for the user? E.g. by submitting a form where he/she sends correct data that will be automatically mapped on the server side to the user attribute.
For what you've said, it seems that you have three choices.
One would be to keep the keycloak "Edit Account" page and use an update profile listener to check what attributes are stored or which ones are updated by who, something like this:
public class UpdateProfile implements RequiredActionProvider, RequiredActionFactory, DisplayTypeRequiredActionFactory {
#Override
public InitiatedActionSupport initiatedActionSupport() {
return InitiatedActionSupport.SUPPORTED;
}
#Override
public void evaluateTriggers(RequiredActionContext context) {
}
#Override
public void requiredActionChallenge(RequiredActionContext context) {
Response challenge = context.form()
.createResponse(UserModel.RequiredAction.UPDATE_PROFILE);
context.challenge(challenge);
}
// Check the custom attribute 1 not being modified by the user
#Override
public void processAction(RequiredActionContext context) {
EventBuilder event = context.getEvent();
event.event(EventType.UPDATE_PROFILE);
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
UserModel user = context.getUser();
KeycloakSession session = context.getSession();
RealmModel realm = context.getRealm();
String newYourCustomAttribute1 = formData.getFirst("yourCustomAttribute1");
String oldYourCustomAttribute1 = user.getFirstAttribute("yourCustomAttribute1")
if (!newYourCustomAttribute1.equals(oldYourCustomAttribute1)) {
Response challenge = context.form()
.setError("User cannot change the attribute")
.setFormData(formData)
.createResponse(UserModel.RequiredAction.UPDATE_PROFILE);
context.challenge(challenge);
return;
}
context.success();
}
#Override
public void close() {
}
#Override
public RequiredActionProvider create(KeycloakSession session) {
return this;
}
#Override
public RequiredActionProvider createDisplay(KeycloakSession session, String displayType) {
if (displayType == null) return this;
if (!OAuth2Constants.DISPLAY_CONSOLE.equalsIgnoreCase(displayType)) return null;
return ConsoleUpdateProfile.SINGLETON;
}
#Override
public void init(Config.Scope config) {
}
#Override
public void postInit(KeycloakSessionFactory factory) {
}
#Override
public String getDisplayText() {
return "Update Profile";
}
#Override
public String getId() {
return UserModel.RequiredAction.UPDATE_PROFILE.name();
}
}
What I don't know is if this listener will be called when you update the profile from your client application too. If it gets called, you'll need to check which is the logged in client, if it's the public client do not let update the attributes, if it's your service client, let it.
The second one would be to only let your service client update the user profiles and make a custom view in your application which sends a form POST to your client, instead of to keycloak directly. This way you can validate it in the service before sending it to keycloak.
The third one is to implement a FormAction interface, which would allow you to validate the incoming form at server side:
The core interface you have to implement is the FormAction interface. A FormAction is responsible for rendering and processing a portion of the page. Rendering is done in the buildPage() method, validation is done in the validate() method, post validation operations are done in success().
#Override
public void validate(ValidationContext context) {
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
UserModel user = context.getUser();
KeycloakSession session = context.getSession();
RealmModel realm = context.getRealm();
String newYourCustomAttribute1 = formData.getFirst("yourCustomAttribute1");
String oldYourCustomAttribute1 = user.getFirstAttribute("yourCustomAttribute1")
if (!newYourCustomAttribute1.equals(oldYourCustomAttribute1)) {
Response challenge = context.form()
.setError("User cannot change the attribute")
.setFormData(formData)
.createResponse(UserModel.RequiredAction.UPDATE_PROFILE);
context.challenge(challenge);
return;
}
context.success();
}
perform an update to version 12.0.4.
There were some issues < 12.0.4 with dropping all attributes if user updates his profile.
Additionally with 12.0.4 you can create user- and admin-read only attributes.
Check documentation: https://www.keycloak.org/docs/latest/server_admin/#_read_only_user_attributes
Cheers
I am developing a Blazor server app, where all clients will have a list of things, any one of these clients can update thing which should then trigger a callback telling all clients to call DbContext.Entry(thing).Reload() so they're up to date. This works all great and well until I refresh the page, then I get the Cannot access a disposed object error and I can't figure out how to get around it.
I have the follow services:
services.AddDbContextPool<MainDbContext>(...);
services.AddSingleton<RefreshService>();
RefreshService.cs:
public class RefreshService {
public Func<long, Task> OnRefreshThing { get; set; }
public void RefreshThing(long thingId) => OnRefreshThing?.Invoke(thingId);
}
Index.blazor:
protected override void OnInitialized() {
RefreshService.OnRefreshIssue += OnRefreshIssue;
}
private async Task OnRefreshThing(long thingId) {
// This works perfectly until I refresh the page & try to call it again
Thing thing = await MainDbContext.Things.FindAsync(thingId); // exception is thrown here
await MainDbContext.Entry(thing).ReloadAsync();
}
And here's an example of what triggers the error:
Thing thing = Things.Where(t => t.ThingId == 1);
thing.Name = "New name";
RefreshService.RefreshThing(thing.ThingId);
You can modify the RefreshService to be Scoped not Singleton
The AddDbContextPool does not dispose the instance of the dbcontext completely, it resets the instance to its default state and it could be because of the reset that it can't access it again
I started with dynatableref example of Request Factory. I read request factory document. but still I am unclear about life cycle or flow of client to server.
I want to make a call to server. Insert data and update grid also. It is easy with RPC call. But I don't understand how to do with Request Factory.
This is one method of request factory. It call persist method automatically of server. It refresh grid also automatically. can I anybody tell how is it working?
context.fire(new Receiver<Void>() {
#Override
public void onConstraintViolation(Set<ConstraintViolation<?>> errors) {
// Otherwise, show ConstraintViolations in the UI
dialog.setText("Errors detected on the server");
editorDriver.setConstraintViolations(errors);
}
#Override
public void onSuccess(Void response) {
// If everything went as planned, just dismiss the dialog box
dialog.hide();
}
});
I want to edit some data in to grid also. is this method help me? or I have to write other method.
I wrote other method like
requestFactory.schoolCalendarRequest().savePerson(personProxy).fire(new Receiver<PersonProxy>() {
#Override
public void onSuccess(PersonProxy person) {
// Re-check offset in case of changes while waiting for data
dialog.hide();
}
});
This is not refreshing grid. why?
The flow client-server of the RuequestFactory is similar to RPC or any XMLHTTP request
1) You invoke a remote method on the server.
2) You receive a response in the Receiver object (which is the Callback object). In onSeccess Method you get the returned object if everything went well. onFailure you get an error if something went wrong.
So to populate the Person table from data retrieved from the server the code should look something like this
requestFactory.schoolCalendarRequest().getPersonList(param1).fire(new Receiver<List<PersonProxy>>() {
#Override
public void onSuccess(List<PersonProxy> personList) {
personTable.getDataProvider().setList(personList);
}
});
Now when you edit a Person (e.g. name ) it's important to initialize and use the same RequestContext until you call fire on the request. So the part where you update the Person's name should look something like this
column.setFieldUpdater(new FieldUpdater<Person, String>() {
#Override
public void update(PersonProxy personProxy, String value) {
RequestContext requestContext = requestFactory.schoolCalendarRequest()
PersonProxy personProxy= requestContext.edit(personProxy);
personProxy.setName(value);
requestFactory.schoolCalendarRequest().savePerson(personProxy).fire(new Receiver<PersonProxy>() {
#Override
public void onSuccess(PersonProxy person) {
//Do something after the update
}
});
}
});
The interaction with the RequestFactory should be placed in a Presenter, so you should probably consider implementing a MVP pattern.
How can I show a success message and then redirect the user to another page after a timeout of e.g. 5 seconds?
I need this for the login page after a successful login. I tried the following and I can see the warning message on login failure, but not the success message on login success. It shows immediately the target page.
public String check(){
if (username.equals("test") && password.equals("test")) {
FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO,"Sample info message", "PrimeFaces rocks!"));
return "Success";
}else{
FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_WARN,"Sample warn message", "Watch out for PrimeFaces!"));
return "Failure";
}
}
I'm using Seam's PageFlow for navigation.
I have a
<p:messages id="messages" showDetail="true" autoUpdate="true" closable="true" />
on the login page.
It is one of the utilities of Flash. Instead of
FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO,"Sample info message", "PrimeFaces rocks!"));:
simply use this code
FacesContext facesContext = FacesContext.getCurrentInstance();
Flash flash = facesContext.getExternalContext().getFlash();
flash.setKeepMessages(true);
flash.setRedirect(true);
facesContext.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO,"Sample info message", "PrimeFaces rocks!"));
First of all, with the code you posted you won't see the FacesMessage before the redirect, you'll see it after the redirect. But also, in order to make that happen you'll need to add a filter, because messages are lost when you redirect. This is the code for the filter you need (don't forget to declare it in web.xml):
public class MultiPageMessagesSupport implements PhaseListener {
private static final long serialVersionUID = 1250469273857785274L;
private static final String sessionToken = "MULTI_PAGE_MESSAGES_SUPPORT";
#Override
public PhaseId getPhaseId() {
return PhaseId.ANY_PHASE;
}
/*
* Check to see if we are "naturally" in the RENDER_RESPONSE phase. If we
* have arrived here and the response is already complete, then the page is
* not going to show up: don't display messages yet.
*/
#Override
public void beforePhase(final PhaseEvent event) {
FacesContext facesContext = event.getFacesContext();
int msg = this.saveMessages(facesContext);
if (PhaseId.RENDER_RESPONSE.equals(event.getPhaseId())) {
if (!facesContext.getResponseComplete()) {
this.restoreMessages(facesContext);
}
}
}
/*
* Save messages into the session after every phase.
*/
#Override
public void afterPhase(final PhaseEvent event) {
if (event.getPhaseId() == PhaseId.APPLY_REQUEST_VALUES ||
event.getPhaseId() == PhaseId.PROCESS_VALIDATIONS ||
event.getPhaseId() == PhaseId.INVOKE_APPLICATION) {
FacesContext facesContext = event.getFacesContext();
int msg = this.saveMessages(facesContext);
}
}
#SuppressWarnings("unchecked")
private int saveMessages(final FacesContext facesContext) {
List<FacesMessage> messages = new ArrayList<FacesMessage>();
for (Iterator<FacesMessage> iter = facesContext.getMessages(null); iter.hasNext();) {
messages.add(iter.next());
iter.remove();
}
if (messages.isEmpty()) {
return 0;
}
Map<String, Object> sessionMap = facesContext.getExternalContext().getSessionMap();
List<FacesMessage> existingMessages = (List<FacesMessage>) sessionMap.get(sessionToken);
if (existingMessages != null) {
existingMessages.addAll(messages);
} else {
sessionMap.put(sessionToken, messages);
}
return messages.size();
}
#SuppressWarnings("unchecked")
private int restoreMessages(final FacesContext facesContext) {
Map<String, Object> sessionMap = facesContext.getExternalContext().getSessionMap();
List<FacesMessage> messages = (List<FacesMessage>) sessionMap.remove(sessionToken);
if (messages == null) {
return 0;
}
int restoredCount = messages.size();
for (Object element : messages) {
facesContext.addMessage(null, (FacesMessage) element);
}
return restoredCount;
}
}
If this doesn't work for you, and you need to show the message before, then you'll have to something like the following: make the method return void, invoke it through ajax, and after adding the success message invoke some javascript method that will wait a couple of seconds and then make the redirect (maybe by programmatically clicking a hidden button that redirects to next page).
In my opinion this is not worth the trouble, you will just delay the login process. Anyway user will know tha tlogin succeeded because he will be redirect to home page (or whatever page you send him to)
EDIT:
the messages are displayed in the page when the method finishes, so waiting in the managed bean method won't work. after adding the FacesMessage, use
RequestContext.getCurrentInstance().execute("waitAndRedirect()");
And in your xhtml, you'll need to have a javascript function similar to this:
function waitAndRedirect() {
setTimeout(function() {
hiddenButtonId.click();
}, 2000);
}
where hiddenButtonId is the ID of a p:button which redirects to home page and is hidden (display:none)
But again, this a nasty approach, in my opinion there's no need to do this, you will just delay the login process.
you can not declare MultiPageMessagesSupport in the web.xml you must declare MultiPageMessagesSupport in the faces-config.xml. por example:
enter code here
<lifecycle>
<phase-listener>your.package.MultiPageMessagesSupport</phase-listener>
</lifecycle>
I am trying to sign into my application. First I throw the RestartResponseAtInterceptPageException (this is in a WicketPanel on my BasePage):
add(new Link<String>("signin") {
#Override
public void onClick() {
throw new RestartResponseAtInterceptPageException(SignIn.class);
}
});
The SignIn Page class contains a form (an inner private class) for the sign in, with the following submit button:
add(new Button("signinButton") {
#Override
public void onSubmit() {
final User user = model.getObject();
final boolean result = MySession.get().authenticate(user);
if (result) {
if (!continueToOriginalDestination()) {
setResponsePage(MySession.get().getApplication().getHomePage());
}
} else {
error("Authentication failed");
}
}
});
When this button is clicked and the user is successfully authenticated, I am not redirected to the page where I clicked on the signIn link but instead I stay on the SignIn page? I've tried debugging this, but haven't been able to find out where things go wrong.
I am glad for any hints that lead to my finding the error of my ways.
This is wicket 1.5.1 by the way.
Small Update because I got the hint I needed from the answer, there is still a bit of explaining to do. The solution looks like this:
add(new Link<String>("signin") {
#Override
public void onClick() {
setResponsePage(new SignIn(getPage()));
}
});
The SignIn class gets a constructor that takes a page obviously and I simply set that page as with setResponsePage to return to where I started without all the continueToOriginalDestination and exception throwing.
RestartResponseAtInterceptPageException is meant to be used to redirect to an interception page while rendering a page. For example, in the constructor of a Page class ProtectedPage, if there is no user signed in, you throw new RestartResponseAtInterceptPageException(SignIn.class). When the SignIn page calls continueToOriginalDestination(), the user is taken back to the original ProtectedPage destination.
Your use is not a typical use of RestartResponseAtInterceptPageException since you throw it in a link handler. Why don't you do a setResponsePage(SignIn.class) directly instead? If you really want to return to the exact page you were on when the "signin" link is clicked, you could also try changing it to:
add(new Link<String>("signin") {
#Override
public void onClick() {
setResponsePage(getPage());
throw new RestartResponseAtInterceptPageException(SignIn.class);
}
});