Open perlhub opened 5 years ago
You could look are there related settings at: https://github.com/wekan/wekan/blob/devel/docker-compose.yml
Wekan LDAP code is at: https://github.com/wekan/wekan-ldap
Wekan can be run behind Apache: https://github.com/wekan/wekan/wiki/Apache
@Akuket
Do you know about this?
Thanks for the reply. I'm familiar with the links. I have a successful docker deployment with LDAP configured and running. Works great. But I want to take it a step further. Let's say I follow the third link above and setup a reverse proxy. I would add directives to fetch client certificates (if they have any):
# activate the client certificate authentication SSLCACertificateFile /etc/apache2/ssl/client-accepted-ca-chain.crt SSLVerifyClient optional SSLVerifyDepth 2
I would then create a session variable that forwards the certificate data to Wekan: RequestHeader set SSL_CLIENT_S_DN "%{SSL_CLIENT_S_DN}s"
My question now is how does wekan use SSL_CLIENT_S_DN to login (or create) the user? In other words, can Wekan automatically log-in users that have already been authenticated by the reverse proxy?
As a comparison, when I setup a MediaWiki server, I loaded the Extension:Auth remoteuser extension to add this functionality. I'm curious if this capability is available in Wekan?
I presume it's not yet in Wekan, and would require adding code for checking that header at layouts.* at https://github.com/wekan/wekan/tree/devel/client/components/main login page and/or wekan/server/authentication.js and then based on that check it similarly with Javascript code and login user, with similar code like in that PHP extension. Anyway, that extension PHP code is not long, and everything required to login is already in Wekan LDAP code, so this would require just someone that has time to look at the code, figure it out and add PR.
Hi! Do you have an update on this feature request ? I'm also interrested, same use case as @perlhub.
@adrienaury
Not yet. Do you have time to help?
Not for contributing to the code, but anything that can help you (test version, paste logs, ...). Thanks for your time @xet7
I gave a look at the code to see if I could develop it rapidly, but it seems to be impossible to read custom HTTP Headers from the Meteor server side. I'm not very familiar with Meteor, and I looked the entire web for a few hours without solution.
The problem is that Meteor filters HTTP Header based on a whitelist, and it's very obscure to me, and not documented.
Aside from this, I think I know how to implement it.
@adrienaury
How would you implement this?
Is there existing serverside implementation in some other programming language? For example Javascript or PHP?
I'm currently researching how to make non-Meteor version of Wekan.
Doh, now I noticed above the link to MediaWiki example code. I'll look at it.
@adrienaury
Is this Kanboard feature also about this Client-Side (Mutual) Authentication issue? https://docs.kanboard.org/en/latest/admin_guide/reverse_proxy_authentication.html
Both Wekan and Kanboard have MIT license. There is differences in web UI and other features.
So as I said I'm new to Meteor and maybe it's not the best way to do it, but I tested it with hard coded values and it works as expected.
In header-login.js, I created a new Authentication Handler :
Accounts.registerLoginHandler("headers", function(loginRequest) {
if (!loginRequest.checkHeaders) {
return undefined;
}
console.log("Handling login from headers");
console.log(loginRequest);
// HERE IS THE PROBLEM : we need to find a way to get the request headers to get values of loginId, loginEmail, loginFirstname and loginLastname.
var sessionData = this.connection || (this._session ? this._session.sessionData : this._sessionData);
// only a few headers are visible because of the whitelist filtering
console.log(this);
console.log(sessionData);
// so i used fixed values for testing
var loginId = "user";
var loginEmail = "user@domain.com";
var loginFirstname = "User";
var loginLastname = "User";
const serviceName = 'headers';
const serviceData = {};
const serviceOptions = {
profile: {}
};
serviceData.id = loginId; // unique
const result = Accounts.updateOrCreateUserFromExternalService(
serviceName,
serviceData,
serviceOptions
);
if (!result || !result.userId) {
throw new Meteor.Error('someError', 'Some message');
}
// update username if you have one
Accounts.setUsername(result.userId, loginId);
// add email (not verified)
Accounts.addEmail(result.userId, loginEmail);
var stampedToken = Accounts._generateStampedLoginToken();
var hashStampedToken = Accounts._hashStampedToken(stampedToken);
const user = Users.findOne({username: loginId});
if (user) {
console.log("Found user !!");
Meteor.users.update(user._id, {
$push: {
'services.resume.loginTokens': hashStampedToken
}
});
console.log("Everything OK !!");
return {
userId: user._id,
token: stampedToken.token
};
}
console.log("No user");
return undefined;
});
And then in layout.js I added this (didnt know where to insert it, so I put it inside Template.userFormsLayout.onCreated and it seems OK.
Accounts.callLoginMethod({
methodArguments: [{checkHeaders: true}],
userCallback(error, result) {
console.log("callLoginMethod returned!")
if (error) {
console.error(error);
}
else {
console.log("User logged");
FlowRouter.go('/')
}
},
});
});
@adrienaury
Is this Kanboard feature also about this Client-Side (Mutual) Authentication issue? https://docs.kanboard.org/en/latest/admin_guide/reverse_proxy_authentication.html
Both Wekan and Kanboard have MIT license. There is differences in web UI and other features.
Yes exactly !
@adrienaury
Did you find yet where header whitelist is?
I'm not sure could it be related to browser-policy? https://github.com/wekan/wekan/blob/devel/server/policy.js https://atmospherejs.com/meteor/browser-policy
This is how Wekan serverside sets some CORS headers: https://github.com/wekan/wekan/blob/devel/server/cors.js
There is also not yet merged PR for adding more CORS headers: https://github.com/wekan/wekan/pull/2429
For header login another issue: https://github.com/wekan/wekan/issues/2019 I originally tried this non-working code: https://github.com/wekan/wekan/commit/08db39d76a2454cdc42c225597863e982ca77e82
This maybe is how in plain javascript headers are read: https://stackoverflow.com/questions/220231/accessing-the-web-pages-http-headers-in-javascript
Does any of this give you ideas how to get it working?
@adrienaury
In that header login issue, here are how I planned to do it, that's the explanation of that non-working code: https://github.com/wekan/wekan/issues/2019#issuecomment-499502369
Did you find yet where header whitelist is?
No, I checked official online documentation/forums and some Stackoverflow posts but none said where it is located and how (if possible) to configure it.
I found some piece of info here : https://www.phusionpassenger.com/library/indepth/meteor/secure_http_headers.html
Meteor uses sockjs behind the scenes to transform requests into connection objects, and sockjs enforces a whitelist on headers, so using connection.httpHeaders won't work to access our secure headers from meteor.
I'm not sure could it be related to browser-policy?
No idea, I need to research more :(
Does any of this give you ideas how to get it working?
I tried some solution base on WebApp.rawConnectHandlers
and yes it was possible to read HTTP Headers without limitation with this solution. But I set it aside because it was difficult to make it works with Accounts.registerLoginHandler
which I thought is the proper way (maybe I'm wrong ?).
In that header login issue, here are how I planned to do it, that's the explanation of that non-working code
I'm aware of the other issue (#2019), actually I first landed on this issue when I started to dig it. For me this is a duplicate of this current issue we're discuting. I was planning to use the constants you already added in the code to get the correct headers. I think you can stille use HEADER_LOGIN_ID by the way, in my opinion HEADER_LOGIN_ID should match first, then check email with HEADER_LOGIN_EMAIL and if not match, give an option to update the user account ?
I originally tried this non-working code
I will look into it to see if I can be inspired :)
I tried some solution base on
WebApp.rawConnectHandlers
and yes it was possible to read HTTP Headers without limitation with this solution. But I set it aside because it was difficult to make it works withAccounts.registerLoginHandler
which I thought is the proper way (maybe I'm wrong ?).
Whoa, it was possible to read HTTP Headers without limitation? Then of course try to use it, to get it working. I did not even get that far that you did here.
@xet7 I'll try to continue tomorrow (it's evening here in France) and I keep you updated.
Thanks! That issue wekan/wekan#2019 has links to all commits I added when I tried to implement it.
From https://www.phusionpassenger.com/library/indepth/meteor/secure_http_headers.html
An HTTP header is considered secure if it begins with !~.
If the client-sent HTTP request contains any headers that begin with !~,
then Passenger will reject that request.
This prevents the client from spoofing any secure headers.
Only Passenger may send secure headers to the application.
This above description is so clear and simple about how this works.
It's not a problem that we use code to read all raw headers directly.
In front of Wekan is the webserver (Caddy/Nginx/Apache/Siteminder/etc) that will drop extra headers that are coming from client, preventing header spoofing. Then that webserver (Caddy/Nginx/Apache/Siteminder/etc) adds it's own allowed headers. Then in Wekan is defined what header names to read, and autologins to Wekan.
Sorry I wasn't able to make it work..
With WebApp.rawConnectHandlers
, I can read the headers, BUT I can't interact with Accounts and Users. Any calls to callLoginMethod or Users.find, fails.
A possible implementation would be to store the headers value from WebApp.rawConnectHandlers when it is accessible, and use them later. I tried to access some sort of Session storage, server side but private to the client making the request. But I didn't find anything working when the user is not yet logged...
Thanks for looking into this guys. It sounds like you were able to fetch the HTTP headers, but not auto-login with them.
I logged in to post another example link I found: https://grafana.com/docs/auth/auth-proxy/. It shows how Grafana does what we're trying to do.
Behind my Apache reverse proxy I have Grafana and Wekan running. Apache logs me in, then forwards a custom header (i.e. X-WEBAUTH-USER) to Grafana and Wekan. Grafana then logs me in with the header username. It would be great if Wekan did this too.
@perlhub
So does Grafana require only username in header to login? Is username same as e-mail address? Does it not use firstname and lastname at all?
I think for Wekan, only required header would probably be username (like nickname or email address). Having firstname, lastname, fullname, gravatar URL etc would be optional.
@xet7 Yea, only the username is required. But you can add additional headers. This is in their config file:
# Optionally define more headers to sync other user attributes # Example headers = Name:X-WEBAUTH-NAME Email:X-WEBAUTH-EMAIL headers =
Is there support for client-side authentication? For example, to authenticate the user you request a client's certificate. Accounts would be created if the certs are trusted (based on a configured trust store).
For additional security, Wekan can connect to LDAP, find the user based on cert info and reject access unless they are in a configured group. You never need to request a password from the user.
I have LDAP setup, but I would love to fetch their client certificate instead of asking them to provide their account credentials. This is how I've setup access in the past with Apache for other webapps.