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

LDAP authentication #165

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
source 'https://rubygems.org'

ruby '1.9.3'
ruby '2.4.0'

gem 'sinatra','~> 1.4.2'
gem 'redis','~> 3.0.4'
gem 'hiredis', '~> 0.4.5'
gem 'json', '~> 1.7.7'
gem 'ruby-hmac', '~> 0.4.0'
gem 'sinatra'
gem 'redis'
gem 'hiredis'
gem 'json', '~> 1.8.1'
gem 'ruby-hmac'
gem 'net-ldap'

group :development, :test do
gem 'rake'
Expand Down
20 changes: 14 additions & 6 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ GEM
specs:
diff-lcs (1.2.5)
hiredis (0.4.5)
json (1.7.7)
json (1.8.6)
net-ldap (0.16.1)
rack (1.5.2)
rack-protection (1.5.1)
rack
Expand All @@ -30,11 +31,18 @@ PLATFORMS
ruby

DEPENDENCIES
hiredis (~> 0.4.5)
json (~> 1.7.7)
hiredis
json (~> 1.8.1)
net-ldap
rack-test
rake
redis (~> 3.0.4)
redis
rspec
ruby-hmac (~> 0.4.0)
sinatra (~> 1.4.2)
ruby-hmac
sinatra

RUBY VERSION
ruby 2.4.0p0

BUNDLED WITH
1.14.6
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,15 @@ The representation is a simple Redis key in the form:

`auth:<lowercase_token>` -> User ID

LDAP Authentication
---

By configuring in `app_config.rb` how to connect with an ldap server,
username/password are checked against that directory.
registration is blocked as well the forget password link.
Users are still stored the same way and are created the first time a user logged succesful through ldap.


News
---

Expand Down
77 changes: 68 additions & 9 deletions app.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
require_relative 'about'
require 'openssl' if UseOpenSSL
require 'uri'
require 'net/ldap'

Version = "0.11.0"

Expand Down Expand Up @@ -222,13 +223,14 @@ def setup_redis(uri=RedisURL)
H.inputtext(:id => "username", :name => "username")+
H.label(:for => "password") {"password"}+
H.inputpass(:id => "password", :name => "password")+H.br+
H.checkbox(:name => "register", :value => "1")+
"create account"+H.br+
((!UseLDAP)? H.checkbox(:name => "register", :value => "1") + "create account": "")+
H.br+
H.submit(:name => "do_login", :value => "Login")
}
}+
H.div(:id => "errormsg"){}+
H.a(:href=>"/reset-password") {"reset password"}+
((!UseLDAP)? H.a(:href=>"/reset-password") {"reset password"} : "")+

H.script() {'
$(function() {
$("form[name=f]").submit(login);
Expand Down Expand Up @@ -661,7 +663,7 @@ def render_comment_subthread(comment,sep="")
end
end

get '/api/login' do
post '/api/login' do
content_type 'application/json'
if (!check_params "username","password")
return {
Expand All @@ -687,6 +689,12 @@ def render_comment_subthread(comment,sep="")

get '/api/reset-password' do
content_type 'application/json'
if(UseLDAP)
return {
:status => "err",
:error => "Cannot reset password when using LDAP as auth"
}.to_json
end
if (!check_params "username","email")
return {
:status => "err",
Expand Down Expand Up @@ -728,6 +736,12 @@ def render_comment_subthread(comment,sep="")

post '/api/create_account' do
content_type 'application/json'
if(UseLDAP)
return {
:status => "err",
:error => "Cannot create an account when using LDAP as auth"
}.to_json
end
if (!check_params "username","password")
return {
:status => "err",
Expand Down Expand Up @@ -1054,7 +1068,7 @@ def application_header
"logout"
}
else
H.a(:href => "/login") {"login / register"}
H.a(:href => "/login") {(UseLDAP)? "login": "login / register"}
end
}
menu_mobile = H.a(:href => "#", :id => "link-menu-mobile"){"<~>"}
Expand Down Expand Up @@ -1133,6 +1147,15 @@ def application_footer
#
# Return value: none, the function works by side effect.
def auth_user(auth)
remote_user = request.env[HttpAuthenticationHeader]
if remote_user
user = get_user_by_username(remote_user)
if user
auth = user['auth']
else
auth,apisecret,errmsg = create_user(remote_user,get_rand)
end
end
return if !auth
id = $r.get("auth:#{auth}")
return if !id
Expand Down Expand Up @@ -1259,13 +1282,49 @@ def get_user_by_username(username)
get_user_by_id(id)
end

# check given credentials aaginst the configured ldap server
# if the user isn't already in redis it's created with given password
def check_ldap_credentials(username, password)
ldap = Net::LDAP.new
ldap.host = LDAPHost
ldap.auth LDAPAdminUserDn, LDAPAdminUserPassword
begin
result = ldap.bind_as(:base => LDAPAdminUserBase,
:filter =>"(uid=#{username})",
:password => password)
rescue Net::LDAP::Error
# catch any connection error on ldap
return nil
end

return nil if !result

username = result.first.uid.first
user = get_user_by_username(username)

if !user
auth,apisecret,errmsg = create_user(username,password)
if errmsg
puts errmsg
return nil
end
return auth,apisecret
end

[user['auth'], user['apisecret']]
end

# Check if the username/password pair identifies an user.
# If so the auth token and form secret are returned, otherwise nil is returned.
def check_user_credentials(username,password)
user = get_user_by_username(username)
return nil if !user
hp = hash_password(password,user['salt'])
(user['password'] == hp) ? [user['auth'],user['apisecret']] : nil
if UseLDAP
check_ldap_credentials(username, password)
else
user = get_user_by_username(username)
return nil if !user
hp = hash_password(password,user['salt'])
(user['password'] == hp) ? [user['auth'],user['apisecret']] : nil
end
end

# Has the user submitted a news story in the last `NewsSubmissionBreak` seconds?
Expand Down
16 changes: 15 additions & 1 deletion app_config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,23 @@
SiteName = "Lamer News"
SiteUrl = "http://lamernews.com"
SiteDescription = "Programming News"
GoogleAnalytics = "XXX"

# Redis config
RedisURL = "redis://127.0.0.1:10000"
RedisURL = "redis://127.0.0.1:6379"

# LDAP Config
# Authentication with LDAP backend
# registration is blocked
UseLDAP = false
LDAPHost = "ldap.domain.com"
LDAPAdminUserDn = "cn=JonDoeAdmin,ou=people,dc=domain,dc=com"
LDAPAdminUserBase = "ou=people,dc=domain,dc=com"
LDAPAdminUserPassword = "XXX"

# Remote authentication
# Header with username to login/register coming from a remote Proxyed auth system, like mod_ldap on apache
HttpAuthenticationHeader = "HTTP_REMOTE_USER"

# Security
PBKDF2Iterations = 1000 # Set this to 5000 to improve security. But it is slow.
Expand Down
9 changes: 9 additions & 0 deletions page.rb
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,15 @@ def page()
self.body {
self.div(:class => "container") {
_header+H.div(:id => "content"){yield}+_footer
}+
self.script {
"(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');

ga('create', '#{GoogleAnalytics}', 'auto');
ga('send', 'pageview');"
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion public/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ function login() {
};
var register = $("input[name=register]").attr("checked");
$.ajax({
type: register ? "POST" : "GET",
type: "POST" ,
url: register ? "/api/create_account" : "/api/login",
data: data,
success: function(r) {
Expand Down