OBPIH-5135 Browsers cannot change their password#4444
OBPIH-5135 Browsers cannot change their password#4444awalkowiak merged 4 commits intofeature/upgrade-to-grails-3.3.10from
Conversation
| 'update', 'importData', 'receive', 'showRecordInventory', 'withdraw', 'cancel', 'change', 'toggle', 'exportAsCsv'] | ||
| def static changeControllers = ['createProductFromTemplate'] | ||
|
|
||
| def static omittedControllersWithChangeActions = [ |
There was a problem hiding this comment.
I think it should be handled by the proper configuration of openboxes.security.rbac.rules in the runtime.groovy
|
Actually, I'm having second thoughts about approving this. I'll add some discussion to the ticket. |
| def userInstance = User.get(params.id) | ||
| if (userInstance) { | ||
| // user is not allowed to edit another user's account unless the user has at least an admin role | ||
| if (authService?.currentUser?.id != params.id && !userService?.isUserAdmin(authService?.currentUser)) { |
There was a problem hiding this comment.
@jmiranda Initially I suggested this as a further improvement to the issue here, but it still does not resolve the issue 100%, we still might have some potential problems with unwanted changes made by users to their accounts and their authorization parameters (through the postman or something like that).
I think that the easiest would be to introduce a separate user edit page stripped of the authorization tab for editing personal data and a password (or even only a simple "change password" form with dedicated action) that could be accessed by the user himself and an admin+ user and a separate (current version of) user edit page for admin+ users.
There was a problem hiding this comment.
What do you think? Or we are moving this into 9.1, and skipping this as a quick hotfix for now, since Manon said in a ticket that it is not as pressing as it was while Lesotho was going live?
There was a problem hiding this comment.
@awalkowiak Yeah, the separate form / action for changing the password is what I was thinking we could do for now. In fact, I think we should do this regardless of how we implement the record-level authorization rules.
Regarding the release, I would still move this to 0.9.1 because it doesn't seem urgent enough to hotfix. But that's just my vote. I would check again with Kelsey and Manon to see if they still agree that it can wait until March.
There was a problem hiding this comment.
We also need to add a ticket to think through how we want to handle the record-level authorization. I actually think this might be possible by adding a ROLE_SELF to the minimum required roles or adding a boolean property to the map to allow the user to edit their own profile and/or password.
Note: I imagine we still want to allow superusers (maybe admins) as well as the user themself to edit their password and update profile information. We also probably need to take user active flag into account.
So I guess for now we don't need to do anything fancy in the interceptor, we can just implement logic in the controller action. But consider delegating the auth logic to a service.
userService.canUserEditUser()
🤷
2f338f4 to
7ab1522
Compare
| } catch (ValidationException e) { | ||
| // This read function is used to avoid getting lazy initialization exceptions in | ||
| // rendering the edit page, it is done like in the update function above | ||
| user = User.read(params.id) |
There was a problem hiding this comment.
Good practices:
If we considered good practices, it shouldn't be the way to go (solving LazyInitializationException by re-fetching an entity), but join the needed associations at the query level.
Seeing this code, something tells me that we must have some kind of OSIV mechanism that is associated with a session, and maybe it is turned off when catching an exception? 🤔
I've mentioned it recently in my PR: #4435 (comment)
So in a nutshell - I would expect that when we have the OSIV filter disabled, we should get the Lazy every time we want to access some associations when not in a transaction boundary.
I'd consider creating a spike ticket to investigate this part, because it's weird that in the catch we lose the associations.
Or maybe it's maybe related to render vs redirect?
Could you check that?
Not considering good practices:
Obviously we won't experience any performance issues due to that additional fetch, so I won't force you to go with the join query route, especially if you had just followed the code from the method above.
| user.errors = e.errors | ||
| render(view: "edit", model: [userInstance: user]) | ||
| } catch (AuthenticationException e) { | ||
| flash.message = e.message |
There was a problem hiding this comment.
I think flash.error would fit better. Used in view like:
<g:if test="${flash.error}">
<div class="errors">${flash.error}</div>
</g:if>| // Needed to bypass the password == passwordConfirm validation | ||
| userInstance.passwordConfirm = userInstance.password | ||
| } | ||
|
|
There was a problem hiding this comment.
have you made sure this method is not used for imports where we probably wouldn't want to remove this part?
| if (user.password != password) { | ||
| user.password = password?.encodeAsPassword() | ||
| user.passwordConfirm = passwordConfirm?.encodeAsPassword() | ||
| user.save(failOnError: true) |
There was a problem hiding this comment.
an alternative way could be to use the save from the data service, as it throws validation exception when validation fails by default. (no need to change, just titbit).

description: https://pihemr.atlassian.net/browse/OBPIH-5135?focusedCommentId=148998