owncloud / oauth2

🔐 Application for using OAuth 2.0 in ownCloud
GNU Affero General Public License v3.0
32 stars 24 forks source link

desktop clients cannot relogin #326

Closed butonic closed 2 years ago

butonic commented 2 years ago

Starting with v5.0.0 we return the username instead of the userid in the generateToken response https://github.com/owncloud/oauth2/pull/286#diff-c88f37beed53f2aeb9db529d2af61368ae49ec6052aa091a062023ae9cfcc670L249-R259

When the client tries to relogin / get a new access token it fails because is sends the userid as $user and then the comparison in https://github.com/owncloud/oauth2/pull/286#diff-70e5309fcd3311d771b3db9c93490ea270fd5894769093765ec37edb68e5dd9bL122-R122 fails, because we are comparing username to userid. This leads to a weird error: switch usery - same

Fixing the comparison with

diff --git a/lib/Controller/PageController.php b/lib/Controller/PageController.php
index c8bc4f4..028fcef 100755
--- a/lib/Controller/PageController.php
+++ b/lib/Controller/PageController.php
@@ -129,7 +129,7 @@ class PageController extends Controller {
                        );
                }

-               if ($user !== null && $user !== $this->userSession->getUser()->getUserName()) {
+               if ($this->isDifferentUser($user)) {
                        $logoutUrl = $this->urlGenerator->linkToRouteAbsolute(
                                'oauth2.page.logout',
                                [
@@ -388,4 +388,19 @@ class PageController extends Controller {
                $escapedDisplayName = Util::sanitizeHTML($displayName);
                return "<span class='hasTooltip' data-original-title='$userId'><strong>$escapedDisplayName</strong></span>";
        }
+
+       /**
+        * @param string $userId
+        * @return bool
+        */
+       private function isDifferentUser($userId) {
+               if (empty($userId)) {
+                       return false;
+               }
+               $userObj = $this->userManager->get($userId);
+               if ($userObj === null) {
+                       return true;
+               }
+               return $userObj->getUID() !== $this->userSession->getUser()->getUID();
+       }
 }

leads to a new problem: The desktop client (who identifies the user by userid) now chokes on the new access token becaues it uses the username instead of the userid: wrong user

The reason is the change in https://github.com/owncloud/oauth2/pull/286#diff-c88f37beed53f2aeb9db529d2af61368ae49ec6052aa091a062023ae9cfcc670L249-R259

I don't see a good way to fix this other than reverting https://github.com/owncloud/oauth2/pull/286

@vjt what exactly were you trying to fix. I know it is very bad UX for users to be see the uid... The desktop client uses the userid in the login request: https://cloud.example.org/index.php/apps/oauth2/authorize?response_type=code&client_id=xdXOt13JKxym1B1QcEncf2XDkLAexMBFwiT9j6EfhhHFJhs2KM9jbjTmf8JBXE69&redirect_uri=http://localhost:59937&code_challenge=secret&code_challenge_method=S256&scope=openid%20offline_access%20email%20profile&prompt=select_account%20consent&state=secret&login_hint={userid}&user={userid}

I'll check if we can replace that with the username so users don't get confused.

butonic commented 2 years ago

this might work:

diff --git a/lib/Controller/PageController.php b/lib/Controller/PageController.php
index c8bc4f4..64470f0 100755
--- a/lib/Controller/PageController.php
+++ b/lib/Controller/PageController.php
@@ -98,7 +98,7 @@ class PageController extends Controller {
         * @param string $client_id The client identifier.
         * @param string $redirect_uri The redirection URI.
         * @param string | null $state The state.
-        * @param string | null $user
+        * @param string | null $user The user id
         *
         * @return TemplateResponse | RedirectResponse The authorize view or the
         * authorize-error view with a redirection to the
@@ -129,11 +129,19 @@ class PageController extends Controller {
                        );
                }

-               if ($user !== null && $user !== $this->userSession->getUser()->getUserName()) {
+               // we need to translate the user id to username so we can show the username to the end user
+               $userObj = $this->userManager->get($user);
+               if ($userObj !== null) {
+                       $userName = $userObj->getUserName();
+               } else {
+                       $userName = $user;
+               }
+
+               if ($this->isDifferentUser($user)) {
                        $logoutUrl = $this->urlGenerator->linkToRouteAbsolute(
                                'oauth2.page.logout',
                                [
-                                       'user' => $user,
+                                       'user' => $userName,
                                        'requesttoken' => Util::callRegister(),
                                        'response_type' => $response_type,
                                        'client_id' => $client_id,
@@ -143,7 +151,7 @@ class PageController extends Controller {
                        );
                        $currentUser = $this->userSession->getUser();
                        $currentUser = $this->buildDisplayForUser($currentUser);
-                       $requestedUser = $this->buildDisplayForUser($user);
+                       $requestedUser = $this->buildDisplayForUser($userObj);
                        return new TemplateResponse(
                                $this->appName,
                                'switch-user',
                                @@ -195,7 +203,7 @@ class PageController extends Controller {
                $logoutUrl = $this->urlGenerator->linkToRouteAbsolute(
                        'oauth2.page.logout',
                        [
-                               'user' => $user,
+                               'user' => $userName,
                                'requesttoken' => Util::callRegister(),
                                'response_type' => $response_type,
                                'client_id' => $client_id,
@@ -347,12 +355,20 @@ class PageController extends Controller {
                // logout the current user
                $this->userSession->logout();

+               // we need to translate the user id to username so we can show the username to the end user
+               $userObj = $this->userManager->get($user);
+               if ($userObj !== null) {
+                       $userName = $userObj->getUserName();
+               } else {
+                       $userName = $user;
+               }
+
                $redirectUrl = $this->urlGenerator->linkToRoute('oauth2.page.authorize', [
                        'response_type' => $response_type,
                        'client_id' => $client_id,
                        'redirect_uri' => $redirect_uri,
                        'state' => $state,
-                       'user' => $user
+                       'user' => $userName
                ]);

                // redirect the browser to the login page and set the redirect_url to the authorize page of oauth2
                @@ -388,4 +404,19 @@ class PageController extends Controller {
                $escapedDisplayName = Util::sanitizeHTML($displayName);
                return "<span class='hasTooltip' data-original-title='$userId'><strong>$escapedDisplayName</strong></span>";
        }
+
+       /**
+        * @param string $userId
+        * @return bool
+        */
+       private function isDifferentUser($userId) {
+               if (empty($userId)) {
+                       return false;
+               }
+               $userObj = $this->userManager->get($userId);
+               if ($userObj === null) {
+                       return true;
+               }
+               return $userObj->getUID() !== $this->userSession->getUser()->getUID();
+       }
 }

This change would put the username back into the logout urls that will ultimately end up as the login_hint / in the username field.

butonic commented 2 years ago

The getUsername was originally introduced with https://github.com/owncloud/core/pull/32587 to make windows network drive pick up the username from LDAP, even if the owncloud internal username was using a UUID / GUID or the UPN.

butonic commented 2 years ago

The correct 'fix' is to use userid in the apis, but teach the clients about the user name ... which they currently cannot get, because there is no api that would allow them to get the username.

butonic commented 2 years ago

we might be able to expose the username in the ocs api https://demo.owncloud.com/ocs/v1.php/cloud/user:

<?xml version="1.0"?>
<ocs>
 <meta>
  <status>ok</status>
  <statuscode>100</statuscode>
  <message>OK</message>
  <totalitems></totalitems>
  <itemsperpage></itemsperpage>
 </meta>
 <data>
  <id>demo</id>
  <display-name>demo</display-name>
  <email/>
 </data>
</ocs>

we could add a <username> there ...

thatd would allow all clients to send a proper login_hint, eg: https://github.com/owncloud/client/issues/9506

butonic commented 2 years ago

related: https://github.com/owncloud/ocis/issues/3196

butonic commented 2 years ago

@vjt could you check if this PR and https://github.com/owncloud/oauth2/pull/328 work for you? Together, users should see their login when reauthenticating in the web ui.

vjt commented 2 years ago

Hi @butonic! I have left the company in which this is currently hsex, but I’ll ping an ex colleague of mine (@ScuotiVento) asking whether he can test. Could a branch be prepared to aid him in checking out the right code version?

Thanks!