fisharebest / webtrees

Online genealogy
https://webtrees.net
GNU General Public License v3.0
455 stars 298 forks source link

External authentication #806

Open mtdcr opened 8 years ago

mtdcr commented 8 years ago

Hi,

It would be nice if webtrees could optionally use the REMOTE_USER server variable for external authentication, like many other projects do. One example with some explanation: https://docs.djangoproject.com/en/1.9/howto/auth-remote-user/ (Django).

See below for a quick and dirty way to do this unconditionally. But of course it would be better to make it optional.

Regards, Andreas

P.S.: I tried to attach it as a .patch or .txt file, but Github doesn't seem to allow it. FWIW, you get the idea, even if this patch below won't apply due to mangled white-space.

From b8249411f4a44cfd418e59c6bf645361fd66f509 Mon Sep 17 00:00:00 2001
From: Andreas Oberritter <obi@saftware.de>
Date: Sun, 17 Jan 2016 20:43:26 +0000
Subject: [PATCH] LOCAL ONLY: Use REMOTE_USER variable for SSO

---
 app/Auth.php | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/app/Auth.php b/app/Auth.php
index 7744bf6..5ca4dd5 100644
--- a/app/Auth.php
+++ b/app/Auth.php
@@ -152,7 +152,14 @@ class Auth {
     * @return string|null
     */
    public static function id() {
-       return Session::get('wt_user');
+       $wt_user = Session::get('wt_user');
+       if ($wt_user === null && !empty($_SERVER['REMOTE_USER'])) {
+           $user = User::findByIdentifier($_SERVER['REMOTE_USER']);
+           if ($user !== null) {
+               $wt_user = $user->getUserId();
+           }
+       }
+       return $wt_user;
    }

    /**
-- 
1.9.1
fisharebest commented 8 years ago

1) This requires that the username $_SERVER['REMOTE_USER'] also exists in the database table wt_user.

How do you keep these synchronised?

2) This will prevent the "logout" (and hence "login") links from working. I guess we should disable these when we use $_SERVER['REMOTE_USER'].

mtdcr commented 8 years ago

1) Yes, actually it's matched against username or email (findByIdentifier does this). Synchronisation happens manually (either the webtrees admin creates proper accounts or an external mechanism manipulates the database). 2.) Yes, maybe unless you're logged in as another user ("Masquerade as this user").

fisharebest commented 8 years ago

it's matched against username or email

  • I think we should only match against username. There are existing SSO implementations that use DOMAIN\USER, USER@DOMAIN, etc.
  • If the account does not exist, should we try to create it automatically? We could create it, and then redirect the user to the edit-account-details page? The admin will still need to set access permissions.
  • Your implementation activates after we examine $_SESSION. So, if you log off and log on using the SSO, then your webtrees login will not change. I think we should always use the SSO identity. This will break the webtrees masquerade - but if you are the server admin, I guess you can masquerade using SSO?

I think this is a good idea.

fisharebest commented 8 years ago

I think we should only match against username

Another reason is that a user could set their username to be the same as the admin's email address.

This user would then log in as the administrator.

fisharebest commented 8 years ago

With the latest code, does the following work for you?

        public static function id() {
+               if (!empty($_SERVER['REMOTE_USER'])) {
+                       $user = User::findByUserName($_SESSION['REMOTE_USER']);
+
+                       if ($user instanceof User) {
+                               return $user->getUserId();
+                       } else {
+                               return null;
+                       }
+               }
+
                return Session::get('wt_user');
        }
fisharebest commented 8 years ago

@mtdcr - have you had time to test this code?

mtdcr commented 8 years ago

I'm sorry for the long delay.

I'd prefer not to create accounts automatically. Also, for my use-case, I need to match against the mail address, because it is used as the ID for SSO. I'd like to keep the masquerading feature, in order to see how the page looks like for a newly created user account. It's not easy to masquerade externally without knowledge about the users' password.

Regarding your proposed code, which commits would I need to cherry-pick to 1.7.3 in order to test it?

For SSO logins (manually created by the admin), it might make sense to disable the users' ability to change his name or mail address. This would also prevent a user from setting his username to the admin's address.

fisharebest commented 8 years ago

I'm sorry for the long delay.

No problem.

which commits would I need to cherry-pick to 1.7.3 in order to test it?

None. Copy/paste from above.

It's not easy to masquerade externally without knowledge about the users' password.

Passwords are not used with SSO. Since you (presumably) control the server, you would masquerade using your SSO?

If the REMOTE_USER is set, should we also disable the login/logout links.

mtdcr commented 8 years ago

Passwords are not used with SSO. Since you (presumably) control the server, you would masquerade using your SSO?

I was referring to the external authentication password, not a webtrees password. There are many different ways to implement external authentication or "SSO". Some implementations might provide a way to act as a different user without knowledge about the password, but others don't (e.g. when authenticating apache users through SQL or SASL).

miqrogroove commented 8 years ago

Just want to add a "me too" here. My solution so far was to adjust the login page logic to check LOGON_USER without prompting for a password and then adjust the front page to automatically redirect to the login page if not logged in. I also adjusted the logout page logic to redirect to the system's logout URL.

Please note under IIS the variable REMOTE_USER is provided by the client and is considered unauthenticated.

boydhako commented 6 years ago

Bump...

It'd be nice if we could use something like wordpress site account to login. Less passwords to remember the better. And possibly make use of the native wordpress groups to some degree.

miqrogroove commented 5 years ago

As a further caution, the Apache mod_ssl does not even use the REMOTE_USER variable for integrated authentication. Here we would rely on various SSLCLIENT* values for the desired results.

The overall idea, I'd say, is that the authentication routines need to be centralized and pluggable. Make it easy to check a variable or two as desired.

mpfaff commented 4 years ago

I'd be really interested in OAuth2 support. More specifically, if OAuth2 support is implemented, I'd love for it to be generic so that the user can use whatever OAuth2 provider they choose. The user would need to be able to provide the authorization, access_token, and profile endpoints and, if necessary, which fields returned by the profile endpoint to use (I believe they vary between providers).

fanningert commented 3 years ago

Is there an changes/progress on this topic? I would like to test this with keycloak an vouch proxy. With this solution a OAuth2/OpenID Connect or SAML2 login should be possible. It also possible to login with email on keycloak and transfer the user-ID to the webapp. What I need to know, is webtree currently support the REMOTE_USER? Or have you some hints for me to search for the right spot for three points.

fisharebest commented 3 years ago

webtrees provides plenty of "hooks" to allow you to implement this in a module.

For example, a module can register new "request handlers" - i.e. the code that is run when you GET and POST to /login, /logout, /register, etc. These will have access to the HTTP request headers (e.g. the REMOTE_USER parameter).

miqrogroove commented 3 years ago

Looks like this was originally a v1 topic. @fisharebest Do you plan to add any documentation or examples for those hooks? Many of us who have been around since v1 don't have any experience or understanding of the 3rd party framework.

fisharebest commented 3 years ago

When you make a GET request to /login, the request is routed to the LoginPage request handler (in folder /app/Http/RequestHandlers).

This request handler creates a login form. When you submit the form, a POST request is made to /login, which is handled by LoginAction.

So, you could create a request handler which does something like this

public function handle(Request $request): Response {
  $remote_user = $request->getHeader('REMOTE_USER')
  // find or create a User object from $remote_user
  Auth::login($user);
  return redirect(route(HomePage::class);
}

You then create a module with a boot() method likethis:

public function boot() {
  app()->bind(LoginPage::class, MyAutoLoginClass::class);
}

Now, when webtrees tries to use the default loginpage object, it will get yours.

The result is that when you click login, you'll be logged in using the identity provided by your webserver.

fanningert commented 3 years ago

@fisharebest thanks. I will try to create a 3rd party module with OAuth (if is possible). But for the first test, I will create a simple module for the function autologin. :)

fanningert commented 3 years ago

@fisharebest So I created a working solution for REMOTE_USER and keycloak (HTTP_X_FORWARDED_PREFERRED_USERNAME). When any SERVER values are set, the default login screen will be displayed.

My last Problem is a error message for the methode AUTH:login($user).

Argument 1 passed to Fisharebest\Webtrees\Auth::login() must implement interface Fisharebest\Webtrees\Contracts\UserInterface, string given, called in …/modules_v4/simple_auto_login/src/Modules/SimpleAutoLoginPage.php on line 28 …/app/Auth.php:192

Here my code

<?php

declare(strict_types=1);

namespace at\fanninger\WebtreesModules\SimpleAutoLogin\Modules;

use Fisharebest\Webtrees\Http\RequestHandlers\LoginPage;
use Fisharebest\Webtrees\Http\RequestHandlers\HomePage;
use Fisharebest\Webtrees\Auth;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

use function route;

class SimpleAutoLoginPage extends LoginPage
{
  public function handle(ServerRequestInterface $request): ResponseInterface
  { 
    if (array_key_exists('REMOTE_USER', $_SERVER) && $_SERVER['REMOTE_USER'] != "") {
      $user = $_SERVER['REMOTE_USER'];
    }elseif (array_key_exists('HTTP_X_FORWARDED_PREFERRED_USERNAME', $_SERVER) && $_SERVER['HTTP_X_FORWARDED_PREFERRED_USERNAME'] != "") {
      $user = $_SERVER['HTTP_X_FORWARDED_PREFERRED_USERNAME'];  
    }

    if ($user != "") {
      Auth::login($user);
      return redirect(route(HomePage::class));
    }else{
      return parent::handle($request);
    }
  }
}

P.S.: I know the $_SERVER is dirty, but it is the first version. :)

fanningert commented 3 years ago

Ok, I found my mistake. I put everything in the LoginPage and don't split in LoginPage and LoginAction. How can I automatic execute the LoginAction after LoginPage? I will skip the display of the login form.

fisharebest commented 3 years ago

Auth::login() requires a user-object - not a string containing the username.

You create one using this:

$user = $this->user_service->findByIdentifier($username);

You can get a UserService object by type-hinting it in your constructor. See LoginAction.php for an example.

fanningert commented 3 years ago

@fisharebest Thanks, it is now working. I will also add a extented version of the logout class. So on Logout, the keycloak or oauth-service session (only the oauth-client session, not the complete session) can also be logout. Next step. Create a github projekt an publish this module (with documentation).

fisharebest commented 3 years ago

P.S.: I know the $_SERVER is dirty, but it is the first version. :)

Instead of $_SERVER, use $request->getServerParams();

fanningert commented 3 years ago

@fisharebest corrected. Thanks.

fanningert commented 3 years ago

Ok, first working release is online. I am open for feedback and I am currently working on a documentation.

https://github.com/fanningert/webtrees_simpleautologin

fanningert commented 3 years ago

@miqrogroove I add support for mod_ssl variable SSL_CLIENT_S_DN_CN

With version 0.0.2 the addon support following variables (in this order).

fisharebest commented 3 years ago

@fanningert - caution. All these headers can be set by a visitor to your site.

You should only use them if you are 100% certain that they were set by your webserver (or another trusted server).

There is a similar example in the webtrees code. Look at the ClientIp middleware and https://webtrees.net/admin/proxy/

We only trust the proxy headers if they have been explicitly authorised (in config.ini.php).

You could do something similar. e.g. define the header variable that contains the authorized user in external configuration.

In config.ini.php:

trusted_header_authenticated_user="REMOTE_USER";

In your code, the following steps:

$trusted_header = $request->getAttribute('trusted_header_authenticated_user');
$username = request->getServerParams($trusted_header);
$user = $this->user_service->findByIdentifier($username);
Auth::login($user);
fanningert commented 3 years ago

@fisharebest Thanks, I will change the code and extend it with your information. For information: oauth-proxy is removing possible user set "HTTP_X_FORWARDED_PREFERRED_USERNAME" values.

fanningert commented 3 years ago

@fisharebest changed and new version published

fanningert commented 3 years ago

@fisharebest sorry for my questions. Is there a existent function to call URLs in the background (example oauth request for logout), or to route a different url (subdirectory of the same domain)? I workihng currently on support of the logout link. So the logout function of webtrees also logout the authentification proxy.

fisharebest commented 3 years ago

To redirect to a different URL, your request handler should return redirect($url);. For example to return to the home page, use return redirect(route(HomePage::class));.

To fetch an external URL, webtrees includes the GuzzleHttp library. See here for an example.

fanningert commented 3 years ago

@fisharebest ok. But my Lougout.php class is not called, when I click on the "Logout" link on the webpage.

Add the same logic like LoginPage in the module.php.

...
    public function boot(): void
    {
        app()->bind(Logout::class, SimpleAutoLogout::class);
    }
...
fisharebest commented 3 years ago

Maybe upload your code to github so I can take a look?

fanningert commented 3 years ago

@fisharebest master branch has now the current version. https://github.com/fanningert/webtrees_simpleautologin

fanningert commented 3 years ago

@fisharebest I think the problem was on my site. I modified some code lines, an now my code is executed. Thanks

fanningert commented 3 years ago

The problem is a "Same Origin Policy" problem. I need to check the proxy server settings. Bildschirmfoto von 2021-07-17 18-48-37

reteP-riS commented 3 years ago

@fanningert - does your webtrees module work with WordPress (WP) and the WP module https://wordpress.org/plugins/oauth2-provider/? The free version of this WP module seems to provide support for what they call the grant type "Authorization Code" only, but they offer more options with a paid version. What they call the grant type "User Credentials" might relate to the trusted_header_authenticated_user="REMOTE_USER" configuration of your module.

fanningert commented 3 years ago

@reteP-riS My addon has no oauth logic currently included. It's only handle the autologin and using the passed trusted_header_authenticated_user http parameter. For oauth I use a oauth-Proxy before webtrees. Currently I am using https://github.com/oauth2-proxy/oauth2-proxy (with keycloak as backend) with this webtrees addon. I am working on a integration of oauth for webtress (extra addon), but this is really on the very beginning.

reteP-riS commented 3 years ago

Thanks for your quick reply. I am in deed looking for a single sign-on (authentication) feature and NOT for something that transfers or synchronizes access rights (authorizations) from WP to webtrees. I am thinking of the user logging on to WP and automatically being authenticated and logged on to webtrees and being logged off from webtrees when logging off from WP. That would still require the webtrees admin to first create and maintain that user in the webtrees control panel. No fancy features like automatic user creation.