-
Notifications
You must be signed in to change notification settings - Fork 15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve impersonate app with changes #16
Conversation
67551bf
to
d21e355
Compare
appinfo/app.php
Outdated
); | ||
$eventDispatcher->addListener( | ||
'OC\TemplateLayout::loadAdditionalScripts', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I grepped the core code but didn't find this hook. How can it work ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
controller/logoutcontroller.php
Outdated
* @return JSONResponse | ||
*/ | ||
public function logoutcontroller($userid) { | ||
$user = $this->userManager->get($_SESSION['oldUserId']); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be better to use OC::$server->getSession()->get('oldUserId')
, did you try it ?
Normally that's the way to get/set session values.
However from what I remember the session values are encrypted somehow with the user id and other user data. So since we are switching users the encryption could break.
Please try it out still.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and here I mean use this approach everywhere instead of $_SESSION
, not just on this line
controller/logoutcontroller.php
Outdated
return new JSONResponse("No user found for $user", Http::STATUS_NOT_FOUND); | ||
} else { | ||
$this->userSession->setUser($user); | ||
$this->logger->warning("Going to switch to a different user $oldUserId", ['app' => 'impersonate']); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use ->info
here. Please check the specs, that's what was stated there.
@@ -9,11 +9,19 @@ | |||
* @copyright Jörn Friedrich Dreyer 2015 | |||
*/ | |||
|
|||
\OCP\App::registerAdmin('impersonate', 'settings-admin'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we might need two versions of the app: 9.1 using this approach and keep this line.
On 10.0 the panels can/must be registered with a different approach. Let's reconsider this later in a separate PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd make this OC10 compatible first. We are not going to backport these changes to 9.1 as they rely on core changes, eg https://github.com/owncloud/core/pull/27629/files#diff-7afe0377257ef7362517b1e27bda7ee6R66
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sharidas already found a way to make the app work without these core changes being necessary.
controller/settingscontroller.php
Outdated
if ($user === null) { | ||
return new JSONResponse("No user found for $userid", Http::STATUS_NOT_FOUND); | ||
} else { | ||
$this->logger->warning("changing to user $userid", ['app' => 'impersonate']); | ||
$this->userSession->setUser($user); | ||
\OC::$server->getAppConfig()->setValue('impersonate', 'impersonating_user_id', $oldUserId); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The app config is global for all users. This means that if two admins are impersonating different users, their values will be overwritten here. A better approach would be to use the user preferences.
But still, I don't think we should write anything to the database. The impersonating is only valid within this one PHP session, not across all PHP sessions of the same admin user.
Any reason why you decided to write this value to the DB ? I see you already write the old user to oldUserId
in the session above.
js/impersonate.js
Outdated
@@ -1,38 +1,47 @@ | |||
(function(){ | |||
|
|||
$(document).ready(function() { | |||
$(window).load(function () { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Usually $(document).ready()
is the better way.
Did that not work correctly ?
js/impersonate.js
Outdated
$.post( | ||
OC.generateUrl('apps/impersonate/user'), | ||
{ userid: userid } | ||
).done(function( result ) { | ||
OC.AppConfig.setValue('impersonate','impersonating_user_id',currentUser); | ||
window.location = OC.generateUrl('apps/files'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use OC.redirect(url)
instead which makes it possible to write JS unit tests for that.
js/impersonate.js
Outdated
'</a></td>') | ||
.insertAfter('#userlist .name'); | ||
OC.AppConfig.getValue('impersonate','impersonate_include_groups_list',"[]", function (data) { | ||
data = eval(data); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ouch, please avoid eval
in JS at all costs.
If the data is JSON, please use the native JS JSON parser.
js/impersonate.js
Outdated
OC.AppConfig.getValue('impersonate','impersonate_include_groups_list',"[]", function (data) { | ||
data = eval(data); | ||
if (data.length !== 0) { | ||
var newColumn = $("#userlist").find("tr:first-child"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it is usually good practice to add a $
in the variable name when it's a jQuery object: $newColumn
js/impersonate.js
Outdated
var newColumn = $("#userlist").find("tr:first-child"); | ||
$('<th id="impersonateId" scope="col">Impersonate</th>').insertAfter(newColumn.find("#headerName")); | ||
for(var i = 0; i < data.length; i++) { | ||
var collectTr = $("#userlist tr"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would this also select the header column ? I suggest scoping the selector to the "tbody"
js/impersonate.js
Outdated
var textDisplayed = $(this).find('td.groups').text(); | ||
if ($.trim(textDisplayed) === $.trim(data[i])) { | ||
var addImpersonate ='<td><a class="action permanent impersonate" href="#" title="' + | ||
'Impersonate' + '">' + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
string needs to be translated: t('impersonate', 'Impersonate')
js/impersonate_logout.js
Outdated
).promise(); | ||
|
||
promisObj.done(function () { | ||
window.location = OC.generateUrl('apps/files'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OC.redirect(url)
js/impersonate_logout.js
Outdated
}); | ||
} | ||
|
||
$("#logout").on('click', function () { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this block the default logout click ? If not there is a risk of race condition: if the page unloads faster then this code might not run at all. I suggest to add a e.preventDefault()
in your handler or returning false
.
js/impersonate.js
Outdated
'<img class="svg permanent action" src="' + OC.imagePath('core','actions/user.svg') + '" />' + | ||
'</a></td>') | ||
.insertAfter('#userlist .name'); | ||
OC.AppConfig.getValue('impersonate','impersonate_include_groups_list',"[]", function (data) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are you reading this setting with JS / ajax ? I saw in the PHP template that you already appended the current value into the DOM so no need to reload it again.
Or if you prefer the ajax way, then remove the PHP template one.
I haven't tested this yet but see another potential issue: the users list is usually limited to the first page of users (50 I think). Then every time you scroll down it will load 10 more users. In this PR the code only seems to render the extra column to the already pre-rendered users. So when you scroll down, the newly appended rows will be missing the column. To solve this, I suggest to use a different approach: whenever the page is loaded, hijack the method Hijacking can be done this way: var oldAdd = UserList.add;
UserList.add = function() {
var $tr = oldAdd.apply(this, arguments);
if (!$tr) {
return;
}
// TODO: add here the code to inject a new column inside the $tr
return $tr
}; You need to make sure that this hijacking/overriding of the method runs early enough before the users page ajax load, but late enough after users.js is loaded. |
d21e355
to
67f6be1
Compare
@PVince81 @butonic I have made the changes in the new PR for review:
|
controller/logoutcontroller.php
Outdated
$user = $this->userManager->get($user); | ||
|
||
if($user === null) { | ||
return new JSONResponse("No user found for $user", Http::STATUS_NOT_FOUND); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The message is always "No user found for null", not very useful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops it should have been userid.
controller/logoutcontroller.php
Outdated
$newUserId = \OC::$server->getSession()->get('newUserId'); | ||
$oldUserId = \OC::$server->getSession()->get('oldUserId'); | ||
\OC::$server->getSession()->set('newUserId',$oldUserId); | ||
\OC::$server->getSession()->set('oldUserId',$newUserId); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should rather clear the old user id here. We don't want when the admin logs out again that they get switched back to the last impersonated user.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have tried with remove() on oldUserId. And in my next version of PR I will update the section with the same.
controller/logoutcontroller.php
Outdated
return new JSONResponse("No user found for $user", Http::STATUS_NOT_FOUND); | ||
} else { | ||
$this->userSession->setUser($user); | ||
$this->logger->info("Going to switch to a different user $userid", ['app' => 'impersonate']); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Switching back to previous user $userid" ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Switching back to previous user $userid" makes sense. Will be reflected in my next PR.
Just thought of a funny case, we need to make sure it doesn't cause problem:
Should log out go back to admin2 or admin1 ? |
.insertAfter('#userlist .name'); | ||
var includedGroups; | ||
OC.AppConfig.getValue('impersonate','impersonate_include_groups_list',"[]", function (data) { | ||
includedGroups = $.parseJSON(data); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is still a slight risk that the groups haven't finished loading if the users page loads faster, in which case the groups aren't available. Maybe might still need to find a way to pass these through the a PHP template.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure how to do this because it is not possible to hook into the users page PHP template.
The sad thing is that the users list entries are rendered both by the PHP template and also by ajax. (in the future I want this to be ajax-only: owncloud/core#10994).
We'd need a way to hook into the user list and prevent it to load until the setting above is also loaded.
Or, a bit more complex, use a promise to load the included group list. Then when the list is used, use its then()
callback to render the additional column.
Maybe this is too complex for now and can be done in a separate PR. (need to keep this in mind)
js/impersonate.js
Outdated
$('<th id="impersonateId" scope="col">Impersonate</th>').insertAfter($newColumn.find("#headerName")); | ||
UserList.add = function () { | ||
var $tr = oldAdd.apply(this,arguments); | ||
var groupsSelectedByUser = $tr.find('.groups').text(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm surprised that this is stored directly in the DOM and not in a data attribute, but ok
js/impersonate.js
Outdated
var groupsSelectedByUser = $tr.find('.groups').text(); | ||
var found = false; | ||
for(var i=0; i < includedGroups.length; i++) { | ||
if($.trim(includedGroups[i]) === $.trim(groupsSelectedByUser)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm, does this work if the user is in multiple groups ?
js/impersonate.js
Outdated
addImpersonate = '<td class="impersonateDisabled"><span></span></td>'; | ||
$(addImpersonate).insertAfter($tr.find('.name')); | ||
} | ||
if(!$tr) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do this at the beginning of the method, no need for all the complex above logic if there is no tr
@@ -0,0 +1,37 @@ | |||
$(document).ready(function () { | |||
|
|||
$("#logout").attr("href","#"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should only override the logout logic if the current user is being impersonated. (when oldUserId is defined)
Don't override logout when no impersonation is in progress.
Does logout still work in regular cases ?
appinfo/app.php
Outdated
@@ -9,11 +9,14 @@ | |||
* @copyright Jörn Friedrich Dreyer 2015 | |||
*/ | |||
|
|||
\OCP\App::registerAdmin('impersonate', 'settings-admin'); | |||
\OCP\Util::addScript('impersonate','impersonate_logout'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd say only add this script if impersonating is already in progress.
Else don't override logout.
I haven't tested this yet, just guessing from reading the code. |
Reply to : #16 (comment) |
|
67f6be1
to
8e29b70
Compare
js/impersonate.js
Outdated
var found = false; | ||
for(var i=0; i < includedGroups.length; i++) { | ||
if($.inArray($.trim(includedGroups[i]),groupsSelectedByUser) !== -1) { | ||
found = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please break
when found, this logic would add the button multiple times if multiple groups match.
Since you're touching this logic anyway, please also make sure that if no groups were selected on the settings page, then display the column for all users.
Did a quick test, works. Please fix my last comment https://github.com/owncloud/impersonate/pull/16/files#r111357943 then I think we can merge this. |
8e29b70
to
8e5f3bc
Compare
Below are the changes implemented: 1) Support of groups which can be impersonated. 2) Support of logout which switches back to orignal user. 3) Support to display the user logged as information as notification. 4) Removed the confirmation dialog to impersonate. Signed-off-by: Sujith H <sharidasan@owncloud.com>
8e5f3bc
to
acff904
Compare
I only tested a admin. The requirement was to make this especially work for subadmins, the console shows an error in this scenario:
Expected result: Can only impersonate for "user1", no errors |
Ok, so the bug in question seems to be due to the fact that the ajax endpoint for appconfig is only usable for admins, not subadmins. So the value can't be read that way. We need to find a different solution to expose the list of groups, which would also solve https://github.com/owncloud/impersonate/pull/16/files#r111325730 |
Merging for now. |
Below are the changes implemented:
Signed-off-by: Sujith H sharidasan@owncloud.com