Skip to content
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

Merged
merged 1 commit into from
Apr 13, 2017
Merged

Improve impersonate app with changes #16

merged 1 commit into from
Apr 13, 2017

Conversation

sharidas
Copy link
Contributor

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

@CLAassistant
Copy link

CLAassistant commented Apr 12, 2017

CLA assistant check
All committers have signed the CLA.

@sharidas
Copy link
Contributor Author

@butonic @PVince81 Kindly review my changes.

appinfo/app.php Outdated
);
$eventDispatcher->addListener(
'OC\TemplateLayout::loadAdditionalScripts',
Copy link
Contributor

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 ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

* @return JSONResponse
*/
public function logoutcontroller($userid) {
$user = $this->userManager->get($_SESSION['oldUserId']);
Copy link
Contributor

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.

Copy link
Contributor

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

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']);
Copy link
Contributor

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');
Copy link
Contributor

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.

Copy link
Member

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

Copy link
Contributor

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.

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);
Copy link
Contributor

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.

@@ -1,38 +1,47 @@
(function(){

$(document).ready(function() {
$(window).load(function () {
Copy link
Contributor

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 ?

$.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');
Copy link
Contributor

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.

'</a></td>')
.insertAfter('#userlist .name');
OC.AppConfig.getValue('impersonate','impersonate_include_groups_list',"[]", function (data) {
data = eval(data);
Copy link
Contributor

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.

OC.AppConfig.getValue('impersonate','impersonate_include_groups_list',"[]", function (data) {
data = eval(data);
if (data.length !== 0) {
var newColumn = $("#userlist").find("tr:first-child");
Copy link
Contributor

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

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");
Copy link
Contributor

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"

var textDisplayed = $(this).find('td.groups').text();
if ($.trim(textDisplayed) === $.trim(data[i])) {
var addImpersonate ='<td><a class="action permanent impersonate" href="#" title="' +
'Impersonate' + '">' +
Copy link
Contributor

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')

).promise();

promisObj.done(function () {
window.location = OC.generateUrl('apps/files');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OC.redirect(url)

});
}

$("#logout").on('click', function () {
Copy link
Contributor

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.

'<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) {
Copy link
Contributor

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.

@PVince81
Copy link
Contributor

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 UserList.add and extend it to automatically add a new column. This way when the user list logic loads or adds new users, it will automatically run the logic for the new column.

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.

@butonic butonic changed the title Improvise impersonate app with changes Improve impersonate app with changes Apr 12, 2017
@sharidas
Copy link
Contributor Author

@PVince81 @butonic I have made the changes in the new PR for review:

  1. Removed the $_SESSION variable from the code and tried to use get and set methods from getSession.
  2. Removed the reference of impersonate from the core. And tried to use addScript in the app.php
  3. Used UserList.add. Replaced $(window).load with $(document).ready
  4. Removed window.location and used OC.redirect as mentioned.
  5. Removed the setValue of impersonating_user_id from the code
  6. Replaced logger->warning to logger->info
  7. String translation done.

$user = $this->userManager->get($user);

if($user === null) {
return new JSONResponse("No user found for $user", Http::STATUS_NOT_FOUND);
Copy link
Contributor

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.

Copy link
Contributor Author

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.

$newUserId = \OC::$server->getSession()->get('newUserId');
$oldUserId = \OC::$server->getSession()->get('oldUserId');
\OC::$server->getSession()->set('newUserId',$oldUserId);
\OC::$server->getSession()->set('oldUserId',$newUserId);
Copy link
Contributor

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.

Copy link
Contributor Author

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.

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']);
Copy link
Contributor

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" ?

Copy link
Contributor Author

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.

@PVince81
Copy link
Contributor

Just thought of a funny case, we need to make sure it doesn't cause problem:

  • login as admin1
  • impersonate admin2
  • now that you're admin2 you also have impersonate rights, impersonate user3
  • log out

Should log out go back to admin2 or admin1 ?
If possible I'd rather have log out go back to admin1.
But if currently it goes to admin2 it is fine and can be fixed in a separate PR.

.insertAfter('#userlist .name');
var includedGroups;
OC.AppConfig.getValue('impersonate','impersonate_include_groups_list',"[]", function (data) {
includedGroups = $.parseJSON(data);
Copy link
Contributor

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.

Copy link
Contributor

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)

$('<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();
Copy link
Contributor

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

var groupsSelectedByUser = $tr.find('.groups').text();
var found = false;
for(var i=0; i < includedGroups.length; i++) {
if($.trim(includedGroups[i]) === $.trim(groupsSelectedByUser)) {
Copy link
Contributor

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 ?

addImpersonate = '<td class="impersonateDisabled"><span></span></td>';
$(addImpersonate).insertAfter($tr.find('.name'));
}
if(!$tr) {
Copy link
Contributor

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","#");
Copy link
Contributor

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');
Copy link
Contributor

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.

@PVince81
Copy link
Contributor

I haven't tested this yet, just guessing from reading the code.
Please address my other comments and maybe we could merge first this if it works already.

@sharidas
Copy link
Contributor Author

Reply to : #16 (comment)
Yes in my latest PR ( yet to upload), it would logout back to admin2.

@PVince81
Copy link
Contributor

PVince81 commented Apr 13, 2017

  • TODO: if no groups were selected, show impersonate icon for every user
  • TODO ? don't show impersonate icon for currently logged user

var found = false;
for(var i=0; i < includedGroups.length; i++) {
if($.inArray($.trim(includedGroups[i]),groupsSelectedByUser) !== -1) {
found = true;
Copy link
Contributor

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.

@PVince81
Copy link
Contributor

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.

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>
@PVince81
Copy link
Contributor

PVince81 commented Apr 13, 2017

I only tested a admin.

The requirement was to make this especially work for subadmins, the console shows an error in this scenario:

  1. Create users "user1", "user2", "user3"
  2. Set "user1" to "group1", "group2" and "group3"
  3. Set "user2" to "group1"
  4. Set "user3" to "group2"
  5. Set "user1" as group admin of group "group1"
  6. Only allow impersonating for "group1"
  7. Login as "user1"
  8. Go to users page

Expected result:

Can only impersonate for "user1", no errors

Actual:
impersonate-subadmin-jserror

@PVince81
Copy link
Contributor

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

@PVince81
Copy link
Contributor

Merging for now.

@PVince81 PVince81 merged commit 7be11ac into master Apr 13, 2017
@PVince81 PVince81 deleted the impersantionV2 branch April 13, 2017 12:52
@PVince81 PVince81 mentioned this pull request Apr 13, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants