-
-
Notifications
You must be signed in to change notification settings - Fork 7k
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
Add LDAP authentication #4032
Add LDAP authentication #4032
Conversation
Oops it looks like someone did it differently in the meantime already... |
duplicated with #3103? |
@ykzts: I didn't see this pull request before developping my version. The PR #3103 is incomplete in my opinion: in particular, it doesn't implement the "usual" way of implementing LDAP authentication. But if it is merged I can add the mentioned implementation afterwards, both are very similar. It's up to you to finish with #3103 and I'll rebase mine on top of it once it's merged. (Also, some of the problematic bullet points are adressed in my PR.) |
This PR is better than #3103, good job! This topic has been on a bit of a backburner for me. On my own PR, I got this comment, which I think makes sense given that there are outstanding requests for CAS authentication as well as more general OAuth: #3103 (comment) So I was considering eventually implementing that. Do you think that makes sense? |
if single_user_mode? || !Setting.open_registrations | ||
redirect_to root_path | ||
elsif Rails.configuration.x.use_ldap | ||
redirect_to new_user_session_path |
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.
this should probably be it's own method rather then overloading this one.
@@ -31,6 +31,7 @@ | |||
# last_emailed_at :datetime | |||
# otp_backup_codes :string is an Array | |||
# filtered_languages :string default([]), not null, is an Array | |||
# ldap_dn :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.
yeah, I agree with Gargron that putting this on the user model doesn't make a lot of sense. How does discourse do it with their pluggable authentication methods?
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 way I understand how it works, it's not how devise see things:
Different models should correspond to different roles, and not different authentication modes. If we create a LdapUser model, then we will have two models which share the same utility. That means everywhere you check that your user is logged in, you'll have to check if user is logged in or ldap_user is logged in.
An example (given in devise's documentation) of the use to have different models is to make a distinction between a simple user and an admin user, which clearly have different roles.
In our case, an ldap user and an user would have exactly the same role, it's only the way they authenticate that change. It's sort of the same change as the two factor authentications: it lies on the user object and not outside, because once logged in you are that 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.
ldap_dn is explicitely stated here, because there is no devise LDAP authenticator that "hides" the thing. If you look in the database schema, you'll see otp / 2FA related informations, which may be null in case it's not relevent to that user.
Here it's the same, except the attribute is stated explicitely in the model
protected | ||
|
||
def send_devise_notification(notification, *args) | ||
devise_mailer.send(notification, self, *args).deliver_later | ||
end | ||
|
||
def confirmation_required? | ||
if ldap_user? | ||
!Rails.configuration.x.ldap_skip_email_confirmation |
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.
super unless Rails.configuration.x.ldap_skip_email_configuration
seems equivalent?
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.
yep
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.
uh nope, it would be ldap_user? && !Rails.configuration.x.ldap_skip_email_confirmation || super
, but then it's much harder to read?
- if @instance_presenter.closed_registrations_message.blank? | ||
- if Rails.configuration.x.use_ldap | ||
%p= t('about.ldap_registrations') | ||
- elsif @instance_presenter.closed_registrations_message.blank? |
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.
so in this case, it's not possible to close registrations if you have ldap enabled? seems bad.
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 don't think LDAP registrations ever need to be closed, because they're not really registrations - these people already have logins in LDAP, so it is expected that they can login on a connected Mastodon.
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.
Yes, if you don't want you users to use mastodon, then you disable them from LDAP, it's no more up to mastodon to decide that. (At least that's how I see LDAP)
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.
that makes sense, but if ldap_only is false, then how would you prevent people from signing up with their email addresses? or is that handled elsewhere?
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.
Right, I just understood your point.
@@ -105,12 +107,24 @@ def session_active?(id) | |||
session_activations.active? id | |||
end | |||
|
|||
def ldap_user? | |||
Rails.configuration.x.use_ldap && ldap_dn.present? |
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.
ldap_dn :text
, so seems correct.
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.
yes, sorry, I was misreading. I was confused because of the attribute :ldap_username, above.
end | ||
|
||
def authenticate! | ||
config = Rails.configuration |
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.
maybe a better way to do this would be something like delegate :ldap_host, :ldap_port, :ldap_manager_dn, :ldap_manager_password, :ldap_manager_bind, :ldap_search_filter, :ldap_search_base, :ldap_mail_attribute, :ldap_username_attribute, to: Rails.configuration.x
, or something? so we have all of the expected config variables listed out explicitly.
Honestly, it doesn't feel like Rails.configuration.x is the right place to put these variables...
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 believe it is probably okay to simply read the ENV variables in this very class, instead of doing so in an initializer. If nothing but this class will use them.
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.
devise.rb uses it to decide which key to use (ldap_username or email). I need to figure out how to make it not sensible to ordering though
end | ||
end | ||
|
||
Rails.application.configure do |
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.
these two things should be in separate files.
@@ -0,0 +1,109 @@ | |||
require 'net/ldap' |
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 fact that this is required to run before initializers/devise feels like a code smell and a bad hack.
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.
Yep, but how can we do otherwise?
As far as I know (little experience with other projects), config/initializer files are supposed to be ordered, it's just luck that makes the ones in mastodon not necessarily ordered?
@@ -50,7 +51,11 @@ | |||
# session. If you need permissions, you should implement that in a before filter. | |||
# You can also supply a hash where the value is a boolean determining whether | |||
# or not authentication should be aborted when the value is not present. | |||
# config.authentication_keys = [:email] | |||
if Rails.configuration.x.use_ldap | |||
config.authentication_keys = [:ldap_username] |
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.
is there a way to delay this configuration until later, so we don't have to worry about the load order? (and remove the ugly 0_
hack)
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 tried in vain, but as I said I have little experience with that, if someone knows better...
@@ -50,7 +51,11 @@ | |||
# session. If you need permissions, you should implement that in a before filter. | |||
# You can also supply a hash where the value is a boolean determining whether | |||
# or not authentication should be aborted when the value is not present. | |||
# config.authentication_keys = [:email] | |||
if Rails.configuration.x.use_ldap |
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.
this doesn't handle ldap_only properly, if i'm reading this correctly.
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 "ldap_username" should be named "ldap_username_or_email" actually, but it seems very long. Or yes we could have three keys depending on "ldap_only", "ldap", "no_ldap" ?
@Gargron : yes, I saw the omniauth / Oauth thing, and to me it seems outside of the scope of that PR (or yours), even if of course the problem triggers now that we consider a new login process. We have two things:
Edit: It's hard to explain the difference in proper words. If it's not clear, it's the distinction between:
|
@gloaec: I have no idea what CAS or SAML are. Oauth or openid (if I understand/remember correctly) have completely different mecanisms and cannot be handled with only one POST from a form: the navigator need to contact itself the trusted third party to authenticate +challenge. In LDAP case, it's the job of the provider (mastodon) to get the credentials and pass them to the LDAP server. I think I understand now why you want to add an Identity for your case, but I need to dig and document more :p |
@immae, right. Unlike LDAP, SSO services like Shibboleth (SAML), CAS, facebook, google, twitter,... have their own login page, that's why there is a need for a middleware in this case :) |
Is there any progress on this? I'd be really happy to see this merged, and I'm sure, that I am not the only one. |
@jcgruenhage : I'm waiting for #3148 You may use the commit already, it's functionnal, but most probably the database schema won't be the same after #3148 so prepare to have some migration tasks |
I have merged PAM authentication (#5303). PAM is supposed to handle integrations with LDAP and CAS. Do you think that is enough and this PR is no longer necessary? |
I didn’t check the pam implementation, but there are two things that LDAP provides, authentication and account informations. Does your pam implementation take those two aspects? If so I think there is nothing more to add :p, otherwise it would be good to complete the pam to handle the missing one :) |
(There is actually not that much information, only email and uid, but I’m almost sure pam has this kind of feature and it’s nice to have) |
I think the PAM PR only did authentication (e-mail + username), nothing like display name or bio. So if there is a way of implementing that, you're welcome to add that (but I'm assuming in a different PR... Can I close this?) |
Yes, thanks for the work! |
@Gargron : I’d like to reopen it (and maybe one day redo a proper PR) if you agree, as I received some e-mail pointing out that PAM was not the solution to every problem, and could be a pain to configure properly. |
@immae You can open a new PR. It would be via omniauth-ldap and not net-ldap directly so not much code reuse from this PR except maybe the environment variables. |
This adds LDAP authentication to mastodon instances.
Two ways are possible:
This is mostly devise work (there exist an LDAP authenticator for devise, but it only supports the second way), but I don't have the required skills to write it for devise directly, maybe for another time when I understand better how it works.
I updated "en" and "fr" locales, I don't know how I should proceed to update the other locales
It fixes issue #942