Skip to content
Browse files

Add LDAP based authentication to the sinatra app.

  • Loading branch information...
1 parent 27fbd79 commit aa7f6887a92f6e71c171c74663d341a364c67f9d @dazoakley dazoakley committed
View
1 .gitignore
@@ -4,3 +4,4 @@ pkg
.bundle
Gemfile.lock
*.gem
+config/ldap.yml
View
0 config/.gitkeep
No changes.
View
8 config/ldap.yml.example
@@ -0,0 +1,8 @@
+:host: localhost
+:port: 3890
+:encryption: :start_tls
+:base: ou=People,dc=example,dc=com
+:username_attribute: uid
+:displayname_attribute: displayname
+:can_search_anonymously: true
+
View
1 gollum.gemspec
@@ -31,6 +31,7 @@ Gem::Specification.new do |s|
s.add_dependency('mustache', [">= 0.11.2", "< 1.0.0"])
s.add_dependency('sanitize', "~> 2.0.0")
s.add_dependency('nokogiri', "~> 1.4")
+ s.add_dependency('net-ldap', '~> 0.3.1')
s.add_development_dependency('RedCloth')
s.add_development_dependency('mocha')
View
66 lib/gollum/frontend/app.rb
@@ -7,10 +7,12 @@
require 'gollum/frontend/views/editable'
require File.expand_path '../uri_encode_component', __FILE__
+require File.expand_path '../ldap_authentication', __FILE__
module Precious
class App < Sinatra::Base
register Mustache::Sinatra
+ include LdapAuthentication
dir = File.dirname(File.expand_path(__FILE__))
@@ -40,6 +42,56 @@ class App < Sinatra::Base
enable :logging, :raise_errors, :dump_errors
end
+ # Deagol helper functions
+ helpers do
+ def authentication_required!
+ redirect '/login' unless @current_user = current_user
+ end
+
+ def current_user
+ email = request.cookies['email']
+ name = request.cookies['name']
+ token = request.cookies['token']
+
+ if email && name && token
+ if token_ok?(email,token)
+ { :email => email, :name => name, :token => token }
+ else
+ nil
+ end
+ else
+ nil
+ end
+ end
+ end
+
+ get '/login' do
+ mustache :login
+ end
+
+ post '/login' do
+ login = params[:login]
+ password = params[:password]
+
+ @current_user = authenticate(login, password)
+
+ if @current_user
+ response.set_cookie('email', @current_user[:email])
+ response.set_cookie('name', @current_user[:name])
+ response.set_cookie('token', @current_user[:token])
+ redirect '/'
+ else
+ redirect '/login'
+ end
+ end
+
+ get '/logout' do
+ response.set_cookie('email', false)
+ response.set_cookie('name', false)
+ response.set_cookie('token', false)
+ redirect '/login'
+ end
+
# Deagol helper - extract the path string that Gollum::Wiki expects
def extract_path(file_path)
path = file_path.dup
@@ -62,6 +114,7 @@ def extract_name(file_path)
end
get '/data/*' do
+ authentication_required!
@path = extract_path(params[:splat].first)
@name = extract_name(params[:splat].first)
wiki_options = settings.wiki_options.merge({ :page_file_dir => @path })
@@ -72,6 +125,7 @@ def extract_name(file_path)
end
get '/edit/*' do
+ authentication_required!
@path = extract_path(params[:splat].first)
@name = extract_name(params[:splat].first)
wiki_options = settings.wiki_options.merge({ :page_file_dir => @path })
@@ -97,6 +151,7 @@ def extract_name(file_path)
end
post '/edit/*' do
+ authentication_required!
@path = extract_path(params[:splat].first)
wiki_options = settings.wiki_options.merge({ :page_file_dir => @path })
wiki = Gollum::Wiki.new(settings.gollum_path, wiki_options)
@@ -115,6 +170,7 @@ def extract_name(file_path)
end
post '/create' do
+ authentication_required!
name = params[:page].split('/').last
path = extract_path(params[:page].dup)
wiki_options = settings.wiki_options.merge({ :page_file_dir => path })
@@ -137,6 +193,7 @@ def extract_name(file_path)
end
post '/revert/:page/:version_list' do
+ authentication_required!
@path = extract_path(params[:page].dup)
@name = params[:page].split('/').last
wiki_options = settings.wiki_options.merge({ :page_file_dir => @path })
@@ -159,6 +216,7 @@ def extract_name(file_path)
end
post '/preview' do
+ authentication_required!
wiki = Gollum::Wiki.new(settings.gollum_path, settings.wiki_options)
@name = "Preview"
@page = wiki.preview_page(@name, params[:content], params[:format])
@@ -168,6 +226,7 @@ def extract_name(file_path)
end
get '/history/*' do
+ authentication_required!
@path = extract_path(params[:splat].first)
@name = extract_name(params[:splat].first)
wiki_options = settings.wiki_options.merge({ :page_file_dir => @path })
@@ -179,6 +238,7 @@ def extract_name(file_path)
end
post '/compare/*' do
+ authentication_required!
@file = params[:splat].first
@versions = params[:versions] || []
if @versions.size < 2
@@ -192,6 +252,7 @@ def extract_name(file_path)
end
get '/compare/:name/:version_list' do
+ authentication_required!
@path = extract_path(params[:name].dup)
@name = params[:name].split('/').last
@versions = params[:version_list].split(/\.{2,3}/)
@@ -204,6 +265,7 @@ def extract_name(file_path)
end
get '/_tex.png' do
+ authentication_required!
content_type 'image/png'
formula = Base64.decode64(params[:data])
Gollum::Tex.render_formula(formula)
@@ -214,6 +276,7 @@ def extract_name(file_path)
end
get %r{/(.+?)/([0-9a-f]{40})} do
+ authentication_required!
file_path = params[:captures][0]
path = extract_path(file_path.dup)
name = file_path.split('/').last
@@ -232,6 +295,7 @@ def extract_name(file_path)
end
get '/search' do
+ authentication_required!
@query = params[:q]
wiki = Gollum::Wiki.new(settings.gollum_path, settings.wiki_options)
@results = wiki.search @query
@@ -240,6 +304,7 @@ def extract_name(file_path)
end
get '/pages*' do
+ authentication_required!
@path = extract_path(params[:splat].first)
wiki_options = settings.wiki_options.merge({ :page_file_dir => @path })
wiki = Gollum::Wiki.new(settings.gollum_path, wiki_options)
@@ -249,6 +314,7 @@ def extract_name(file_path)
end
get '/*' do
+ authentication_required!
show_page_or_file(params[:splat].first)
end
View
68 lib/gollum/frontend/ldap_authentication.rb
@@ -0,0 +1,68 @@
+require 'net/ldap'
+require 'yaml'
+require 'digest/md5'
+
+LDAP = YAML.load_file( File.expand_path('../../../../config/ldap.yml', __FILE__) )
+
+module Precious
+ module LdapAuthentication
+
+ AUTH_TOKEN_SEED = 'super freaking awesome secret seed'
+
+ def authenticate(login, password)
+ authenticated_user = ldap_login(login, password)
+
+ if authenticated_user
+ {
+ :email => authenticated_user['mail'].first,
+ :name => authenticated_user[LDAP[:displayname_attribute]].first,
+ :token => Digest::MD5.hexdigest(AUTH_TOKEN_SEED + authenticated_user['mail'].first)
+ }
+ else
+ false
+ end
+ end
+
+ def token_ok?(email,token)
+ token == Digest::MD5.hexdigest(AUTH_TOKEN_SEED+email)
+ end
+
+ private
+
+ # Returns a single Net::LDAP::Entry or false
+ def ldap_login(username, password)
+ ldap_session = new_ldap_session
+ bind_args = args_for(username, password)
+ authenticated_user = ldap_session.bind_as(bind_args)
+
+ authenticated_user ? authenticated_user.first : false
+ end
+
+ # This is where LDAP jumps up and punches you in the face, all the while
+ # screaming "You never gunna get this, your wasting your time!".
+ def args_for(username, password)
+ user_filter = "#{ LDAP[:username_attribute] }=#{ username }"
+ args = { :base => LDAP[:base],
+ :filter => "(#{ user_filter })",
+ :password => password }
+
+ unless LDAP[:can_search_anonymously]
+ # If you can't search your LDAP directory anonymously we'll try and
+ # authenticate you with your user dn before we try and search for your
+ # account (dn example. `uid=clowder,ou=People,dc=mycompany,dc=com`).
+ user_dn = [user_filter, LDAP[:base]].join(',')
+ args.merge({ :auth => { :username => user_dn, :password => password, :method => :simple } })
+ end
+
+ args
+ end
+
+ def new_ldap_session
+ Net::LDAP.new(:host => LDAP[:host],
+ :port => LDAP[:port],
+ :encryption => LDAP[:encryption],
+ :base => LDAP[:base])
+ end
+
+ end
+end
View
21 lib/gollum/frontend/public/gollum/css/gollum.css
@@ -696,3 +696,24 @@ ul.actions {
padding: 0.25em;
}
+/* @section login */
+
+.login label, .login input {
+ display: block;
+}
+
+.login label {
+ font-weight: bold;
+}
+
+.login input {
+ margin-bottom: 15px;
+ height: 2em;
+ width: 30em;
+}
+
+.login button {
+ width: 8em;
+ height: 2em;
+}
+
View
0 lib/gollum/frontend/public/gollum/favicon.ico
No changes.
View
14 lib/gollum/frontend/templates/login.mustache
@@ -0,0 +1,14 @@
+<div id="wiki-wrapper">
+ <div id="head">
+ <h1>Please Sign In...</h1>
+ </div>
+ <div id="wiki-content" class="login">
+ <form method='post' action='/login'>
+ <label for='login'>User:</label>
+ <input type='text' id='login' name='login'/>
+ <label for='password'>Password:</label>
+ <input type='password' id='password' name='password'/>
+ <button type='submit'>Sign In</button>
+ </form>
+ </div>
+</div>
View
10 lib/gollum/frontend/views/login.rb
@@ -0,0 +1,10 @@
+module Precious
+ module Views
+ class Login < Layout
+ def title
+ "Login"
+ end
+ end
+ end
+end
+

0 comments on commit aa7f688

Please sign in to comment.
Something went wrong with that request. Please try again.