Initial support for LDAP server authentication

Limitations as of this commit:

- tlsOptions can only be specified in config.json, not as env vars
- authentication failures are not yet gracefully handled by the UI
  - instead the error message is shown on a blank page (/auth/ldap)
- no email address is associated with the LDAP user's account
- no picture/profile URL is associated with the LDAP user's account
- we might have to generate our own access + refresh tokens,
  because we aren't using oauth. The currently generated
  tokens are just a placeholder.
- 'LDAP Sign in' needs to be translated to each locale
This commit is contained in:
alecdwm 2016-12-13 22:31:35 +01:00
parent 8095f8cc98
commit 02e9927714
11 changed files with 135 additions and 7 deletions

View file

@ -131,6 +131,13 @@ Environment variables (will overwrite other server configs)
| HMD_DROPBOX_CLIENTSECRET | no example | Dropbox API client secret | | HMD_DROPBOX_CLIENTSECRET | no example | Dropbox API client secret |
| HMD_GOOGLE_CLIENTID | no example | Google API client id | | HMD_GOOGLE_CLIENTID | no example | Google API client id |
| HMD_GOOGLE_CLIENTSECRET | no example | Google API client secret | | HMD_GOOGLE_CLIENTSECRET | no example | Google API client secret |
| HMD_LDAP_URL | ldap://example.com | url of LDAP server |
| HMD_LDAP_BINDDN | no example | bindDn for LDAP access |
| HMD_LDAP_BINDCREDENTIALS | no example | bindCredentials for LDAP access |
| HMD_LDAP_TOKENSECRET | supersecretkey | secret used for generating access/refresh tokens |
| HMD_LDAP_SEARCHBASE | o=users,dc=example,dc=com | LDAP directory to begin search from |
| HMD_LDAP_SEARCHFILTER | (uid={{username}}) | LDAP filter to search with |
| HMD_LDAP_SEARCHATTRIBUTES | no example | LDAP attributes to search with |
| HMD_IMGUR_CLIENTID | no example | Imgur API client id | | HMD_IMGUR_CLIENTID | no example | Imgur API client id |
| HMD_EMAIL | `true` or `false` | set to allow email register and signin | | HMD_EMAIL | `true` or `false` | set to allow email register and signin |
| HMD_IMAGE_UPLOAD_TYPE | `imgur`, `s3` or `filesystem` | Where to upload image. For S3, see our [S3 Image Upload Guide](docs/guides/s3-image-upload.md) | | HMD_IMAGE_UPLOAD_TYPE | `imgur`, `s3` or `filesystem` | Where to upload image. For S3, see our [S3 Image Upload Guide](docs/guides/s3-image-upload.md) |
@ -182,7 +189,7 @@ Third-party integration api key settings
| service | settings location | description | | service | settings location | description |
| ------- | --------- | ----------- | | ------- | --------- | ----------- |
| facebook, twitter, github, gitlab, dropbox, google | environment variables or `config.json` | for signin | | facebook, twitter, github, gitlab, dropbox, google, ldap | environment variables or `config.json` | for signin |
| imgur | environment variables or `config.json` | for image upload | | imgur | environment variables or `config.json` | for image upload |
| google drive, dropbox | `public/js/config.js` | for export and import | | google drive, dropbox | `public/js/config.js` | for export and import |

6
app.js
View file

@ -380,6 +380,12 @@ if (config.google) {
failureRedirect: config.serverurl + '/' failureRedirect: config.serverurl + '/'
})); }));
} }
// ldap auth
if (config.ldap) {
app.post('/auth/ldap', urlencodedParser,
passport.authenticate('ldapauth', { successRedirect: '/' })
);
}
// email auth // email auth
if (config.email) { if (config.email) {
app.post('/register', urlencodedParser, function (req, res, next) { app.post('/register', urlencodedParser, function (req, res, next) {

View file

@ -45,6 +45,18 @@
"clientID": "change this", "clientID": "change this",
"clientSecret": "change this" "clientSecret": "change this"
}, },
"ldap": {
"url": "ldap://change_this",
"bindDn": null,
"bindCredentials": null,
"tokenSecret": "change this",
"searchBase": "change this",
"searchFilter": "change this",
"searchAttributes": "change this",
"tlsOptions": {
"changeme": "See https://nodejs.org/api/tls.html#tls_tls_connect_options_callback"
}
},
"imgur": { "imgur": {
"clientID": "change this" "clientID": "change this"
} }

View file

@ -7,6 +7,7 @@ var GithubStrategy = require('passport-github').Strategy;
var GitlabStrategy = require('passport-gitlab2').Strategy; var GitlabStrategy = require('passport-gitlab2').Strategy;
var DropboxStrategy = require('passport-dropbox-oauth2').Strategy; var DropboxStrategy = require('passport-dropbox-oauth2').Strategy;
var GoogleStrategy = require('passport-google-oauth20').Strategy; var GoogleStrategy = require('passport-google-oauth20').Strategy;
var LdapStrategy = require('passport-ldapauth');
var LocalStrategy = require('passport-local').Strategy; var LocalStrategy = require('passport-local').Strategy;
var validator = require('validator'); var validator = require('validator');
@ -110,6 +111,36 @@ if (config.google) {
callbackURL: config.serverurl + '/auth/google/callback' callbackURL: config.serverurl + '/auth/google/callback'
}, callback)); }, callback));
} }
// ldap
if (config.ldap) {
passport.use(new LdapStrategy({
server: {
url: config.ldap.url || null,
bindDn: config.ldap.bindDn || null,
bindCredentials: config.ldap.bindCredentials || null,
searchBase: config.ldap.searchBase || null,
searchFilter: config.ldap.searchFilter || null,
searchAttributes: config.ldap.searchAttributes || null,
tlsOptions: config.ldap.tlsOptions || null
},
},
function(user, done) {
var profile = {
id: 'LDAP-' + user.uidNumber,
username: user.uid,
displayName: user.displayName,
emails: [],
avatarUrl: null,
profileUrl: null,
provider: 'ldap',
}
var stringifiedProfile = JSON.stringify(profile);
// TODO: Generate secure tokens for LDAP users
var accessToken = 'debug-access-token|LDAP-' + user.uidNumber + '|' + config.ldap.tokenSecret + '|' + new Date().getTime();
var refreshToken = 'debug-refresh-token|LDAP-' + user.uidNumber + '|' + config.ldap.tokenSecret + '|' + new Date().getTime();
callback(accessToken, refreshToken, profile, done);
}));
}
// email // email
if (config.email) { if (config.email) {
passport.use(new LocalStrategy({ passport.use(new LocalStrategy({
@ -130,4 +161,4 @@ if (config.email) {
return done(err); return done(err);
}); });
})); }));
} }

View file

@ -93,6 +93,31 @@ var google = (process.env.HMD_GOOGLE_CLIENTID && process.env.HMD_GOOGLE_CLIENTSE
clientID: process.env.HMD_GOOGLE_CLIENTID, clientID: process.env.HMD_GOOGLE_CLIENTID,
clientSecret: process.env.HMD_GOOGLE_CLIENTSECRET clientSecret: process.env.HMD_GOOGLE_CLIENTSECRET
} : config.google || false; } : config.google || false;
var ldap = config.ldap || (
process.env.HMD_LDAP_URL ||
process.env.HMD_LDAP_BINDDN ||
process.env.HMD_LDAP_BINDCREDENTIALS ||
process.env.HMD_LDAP_TOKENSECRET ||
process.env.HMD_LDAP_SEARCHBASE ||
process.env.HMD_LDAP_SEARCHFILTER ||
process.env.HMD_LDAP_SEARCHATTRIBUTES
) || false;
if (ldap == true)
ldap = {};
if (process.env.HMD_LDAP_URL)
ldap.url = process.env.HMD_LDAP_URL;
if (process.env.HMD_LDAP_BINDDN)
ldap.bindDn = process.env.HMD_LDAP_BINDDN;
if (process.env.HMD_LDAP_BINDCREDENTIALS)
ldap.bindCredentials = process.env.HMD_LDAP_BINDCREDENTIALS;
if (process.env.HMD_LDAP_TOKENSECRET)
ldap.tokenSecret = process.env.HMD_LDAP_TOKENSECRET;
if (process.env.HMD_LDAP_SEARCHBASE)
ldap.searchBase = process.env.HMD_LDAP_SEARCHBASE;
if (process.env.HMD_LDAP_SEARCHFILTER)
ldap.searchFilter = process.env.HMD_LDAP_SEARCHFILTER;
if (process.env.HMD_LDAP_SEARCHATTRIBUTES)
ldap.searchAttributes = process.env.HMD_LDAP_SEARCHATTRIBUTES;
var imgur = process.env.HMD_IMGUR_CLIENTID || config.imgur || false; var imgur = process.env.HMD_IMGUR_CLIENTID || config.imgur || false;
var email = process.env.HMD_EMAIL || config.email || false; var email = process.env.HMD_EMAIL || config.email || false;
@ -151,6 +176,7 @@ module.exports = {
gitlab: gitlab, gitlab: gitlab,
dropbox: dropbox, dropbox: dropbox,
google: google, google: google,
ldap: ldap,
imgur: imgur, imgur: imgur,
email: email, email: email,
imageUploadType: imageUploadType, imageUploadType: imageUploadType,

View file

@ -66,6 +66,7 @@ function showIndex(req, res, next) {
gitlab: config.gitlab, gitlab: config.gitlab,
dropbox: config.dropbox, dropbox: config.dropbox,
google: config.google, google: config.google,
ldap: config.ldap,
email: config.email, email: config.email,
signin: req.isAuthenticated(), signin: req.isAuthenticated(),
infoMessage: req.flash('info'), infoMessage: req.flash('info'),
@ -98,6 +99,7 @@ function responseHackMD(res, note) {
gitlab: config.gitlab, gitlab: config.gitlab,
dropbox: config.dropbox, dropbox: config.dropbox,
google: config.google, google: config.google,
ldap: config.ldap,
email: config.email email: config.email
}); });
} }

View file

@ -100,5 +100,6 @@
"Select From Available Snippets": "Select From Available Snippets", "Select From Available Snippets": "Select From Available Snippets",
"OR": "OR", "OR": "OR",
"Export to Snippet": "Export to Snippet", "Export to Snippet": "Export to Snippet",
"Select Visibility Level": "Select Visibility Level" "Select Visibility Level": "Select Visibility Level",
"LDAP Sign in": "LDAP Sign in"
} }

View file

@ -85,6 +85,7 @@
"passport-github": "^1.1.0", "passport-github": "^1.1.0",
"passport-gitlab2": "^2.2.0", "passport-gitlab2": "^2.2.0",
"passport-google-oauth20": "^1.0.0", "passport-google-oauth20": "^1.0.0",
"passport-ldapauth": "^0.6.0",
"passport-local": "^1.0.0", "passport-local": "^1.0.0",
"passport-twitter": "^1.0.4", "passport-twitter": "^1.0.4",
"passport.socketio": "^3.6.2", "passport.socketio": "^3.6.2",

View file

@ -57,7 +57,7 @@
<% if (errorMessage && errorMessage.length > 0) { %> <% if (errorMessage && errorMessage.length > 0) { %>
<div class="alert alert-danger" style="max-width: 400px; margin: 0 auto;"><%= errorMessage %></div> <div class="alert alert-danger" style="max-width: 400px; margin: 0 auto;"><%= errorMessage %></div>
<% } %> <% } %>
<% if(facebook || twitter || github || gitlab || dropbox || google || email) { %> <% if(facebook || twitter || github || gitlab || dropbox || google || ldap || email) { %>
<span class="ui-signin"> <span class="ui-signin">
<br> <br>
<a type="button" class="btn btn-lg btn-success ui-signin" data-toggle="modal" data-target=".signin-modal" style="min-width: 170px;"><%= __('Sign In') %></a> <a type="button" class="btn btn-lg btn-success ui-signin" data-toggle="modal" data-target=".signin-modal" style="min-width: 170px;"><%= __('Sign In') %></a>
@ -93,7 +93,7 @@
</div> </div>
<div id="history" class="section"<% if(!signin) { %> style="display:none;"<% } %>> <div id="history" class="section"<% if(!signin) { %> style="display:none;"<% } %>>
<% if(facebook || twitter || github || gitlab || dropbox || google || email) { %> <% if(facebook || twitter || github || gitlab || dropbox || google || ldap || email) { %>
<div class="ui-signin"> <div class="ui-signin">
<p><%= __('Below is the history from browser') %></p> <p><%= __('Below is the history from browser') %></p>
</div> </div>
@ -192,6 +192,7 @@
</div> </div>
</div> </div>
<%- include signin-modal %> <%- include signin-modal %>
<%- include signin-ldap-modal %>
<% if(useCDN) { %> <% if(useCDN) { %>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js" integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8=" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js" integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8=" crossorigin="anonymous"></script>

View file

@ -0,0 +1,35 @@
<!-- signin ldap modal -->
<div class="modal fade signin-ldap-modal" tabindex="-1" role="dialog" aria-labelledby="mySmallModalLabel" aria-hidden="true">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title" id="mySmallModalLabel"><%= __('LDAP Sign in') %></h4>
</div>
<div class="modal-body" style="text-align: center;">
<% if(ldap) { %>
<form data-toggle="validator" role="form" class="form-horizontal" method="post" enctype="application/x-www-form-urlencoded">
<div class="form-group">
<div class="col-sm-12">
<input type="username" class="form-control" name="username" placeholder="Username" required>
<span class="help-block control-label with-errors" style="display: inline;"></span>
</div>
</div>
<div class="form-group">
<div class="col-sm-12">
<input type="password" class="form-control" name="password" placeholder="Password" required>
<span class="help-block control-label with_errors" style="display: inline;"></span>
</div>
</div>
<div class="form-group">
<div class="col-sm-12">
<button type="submit" class="btn btn-primary" formaction="<%- url %>/auth/ldap">Sign in</button>
</div>
</div>
</form>
<% } %>
</div>
</div>
</div>
</div>

View file

@ -38,7 +38,13 @@
<i class="fa fa-google"></i> <%= __('Sign in via %s', 'Google') %> <i class="fa fa-google"></i> <%= __('Sign in via %s', 'Google') %>
</a> </a>
<% } %> <% } %>
<% if((facebook || twitter || github || gitlab || dropbox || google) && email) { %> <% if(ldap) { %>
<a type="button" class="btn btn-lg btn-block btn-social btn-reddit "data-toggle="modal" data-target=".signin-ldap-modal">
<i class="fa fa-book"></i> <%= __('Sign in via %s', 'LDAP') %>
</a>
<% } %>
<% if((facebook || twitter || github || gitlab || dropbox || google || ldap) && email) { %>
<hr> <hr>
<% }%> <% }%>
<% if(email) { %> <% if(email) { %>
@ -67,4 +73,4 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div>