[sagenb] 01/179: reapplied LDAP code to current upstream
felix salfelder
felix-guest at moszumanska.debian.org
Tue May 6 12:05:04 UTC 2014
This is an automated email from the git hooks/post-receive script.
felix-guest pushed a commit to branch master
in repository sagenb.
commit 3f47c7af260a611506c32ff768c6b61d744df742
Author: Robin Martinjak <rob at rmartinjak.de>
Date: Wed Jun 20 16:35:00 2012 +0200
reapplied LDAP code to current upstream
---
flask_version/worksheet.py | 6 +-
.../data/sage/html/notebook/worksheet_share.html | 33 +++++-
.../data/sage/html/settings/account_settings.html | 2 +-
sagenb/notebook/auth.py | 131 +++++++++++++++++++++
sagenb/notebook/notebook.py | 13 +-
sagenb/notebook/server_conf.py | 73 ++++++++++++
sagenb/notebook/user.py | 6 +
sagenb/notebook/user_manager.py | 94 +++++++++++++--
8 files changed, 346 insertions(+), 12 deletions(-)
diff --git a/flask_version/worksheet.py b/flask_version/worksheet.py
index f293418..9acb194 100644
--- a/flask_version/worksheet.py
+++ b/flask_version/worksheet.py
@@ -564,6 +564,10 @@ def worksheet_edit_published_page(worksheet):
def worksheet_share(worksheet):
return g.notebook.html_share(worksheet, g.username)
+ at worksheet_command('search_collab')
+def worksheet_search_collab(worksheet):
+ return g.notebook.html_share(worksheet, g.username, request.values.get('lookup'))
+
@worksheet_command('invite_collab')
def worksheet_invite_collab(worksheet):
owner = worksheet.owner()
@@ -573,7 +577,6 @@ def worksheet_invite_collab(worksheet):
if len(collaborators-old_collaborators)>500:
# to prevent abuse, you can't add more than 500 collaborators at a time
return current_app.message(_("Error: can't add more than 500 collaborators at a time"), cont=url_for_worksheet(worksheet))
- worksheet.set_collaborators(collaborators)
user_manager = g.notebook.user_manager()
# add worksheet to new collaborators
for u in collaborators-old_collaborators:
@@ -590,6 +593,7 @@ def worksheet_invite_collab(worksheet):
# user doesn't exist
pass
+ worksheet.set_collaborators(collaborators)
return redirect(url_for_worksheet(worksheet))
########################################################
diff --git a/sagenb/data/sage/html/notebook/worksheet_share.html b/sagenb/data/sage/html/notebook/worksheet_share.html
index 42ff38c..731d957 100644
--- a/sagenb/data/sage/html/notebook/worksheet_share.html
+++ b/sagenb/data/sage/html/notebook/worksheet_share.html
@@ -3,7 +3,8 @@
INPUT:
- worksheet - an instance of Worksheet
- username - a string containing a username
- - other_users - a list of strings containing other users names
+ - lookup - (optional) string used for user lookup
+ - lookup_result - (optional) list of users returned by user lookup
#}
{% block sharebar_title %}
@@ -13,6 +14,16 @@ INPUT:
{% set select = "share" %}
{% block after_sharebar %}
+<script type="text/javascript">
+function add_collab(u) {
+ var col = document.getElementById('collaborators');
+ if (col.value != "") {
+ col.value+= ", ";
+ }
+ col.value+=u;
+}
+</script>
+
{% if not (notebook.user_manager().user_is_admin(username) or username == worksheet.owner()) %}
{{ gettext('Only the owner of a worksheet is allowed to share it. You can do whatever you want if you <a href="copy">make your own copy</a>.') }}
{% else %}
@@ -23,5 +34,25 @@ INPUT:
<textarea name="collaborators" rows=5 cols=70 class="edit" id="collaborators" style="display:block; margin-bottom:1em;">{{ worksheet.collaborators()|join(', ') }}</textarea>
<input type="submit" title="{{ gettext('Give access to your worksheet to the above collaborators') }}" value="{{ gettext('Invite Collaborators') }}" />
</form>
+
+ <hr class="usercontrol" />
+ {% if lookup %}
+ <div>
+ <p>{{ gettext ('Search results:') }}{% if lookup_result %}
+ {% for u in lookup_result %}
+ <span class="users">
+ <a href="javascript:add_collab('{{ u }}');" class="users">{{ u }}</a>
+ </span>
+ {% endfor %}
+ {% else %} {{ gettext('No match found') }}
+ {% endif %}</p>
+ </div>
+ {% else %}
+ {{ gettext('Search Users') }}
+ {% endif %}
+ <form width=70% method="post" action="search_collab" style="margin-bottom:1em">
+ <input type="text" class="edit" id="lookup" name="lookup" value="{{ lookup if lookup else '' }}" />
+ <input type="submit" value="{{ gettext('Search') }}" />
+ </form>
{% endif %}
{% endblock %}
diff --git a/sagenb/data/sage/html/settings/account_settings.html b/sagenb/data/sage/html/settings/account_settings.html
index b0c46a1..5bc6158 100644
--- a/sagenb/data/sage/html/settings/account_settings.html
+++ b/sagenb/data/sage/html/settings/account_settings.html
@@ -20,6 +20,7 @@
</select>
</div>
</div>
+ {% if not external %}
<div class="section">
<h2>{{ gettext('Change Password') }}</h2>
<div id="passwd">
@@ -41,7 +42,6 @@
</div>
</div>
- {% if true %}
<div class="section">
<h2>{{ gettext('Change E-mail Address') }}</h2>
diff --git a/sagenb/notebook/auth.py b/sagenb/notebook/auth.py
new file mode 100644
index 0000000..f23650f
--- /dev/null
+++ b/sagenb/notebook/auth.py
@@ -0,0 +1,131 @@
+class AuthMethod():
+ """
+ Abstract class for authmethods that are used by ExtAuthUserManager
+ All auth methods must implement the following methods
+ """
+
+ def __init__(self, conf):
+ self._conf = conf
+
+ def user_lookup(self, search):
+ raise NotImplementedError
+
+ def check_user(self, username):
+ raise NotImplementedError
+
+ def check_password(self, username, password):
+ raise NotImplementedError
+
+ def get_attrib(self, username, attrib):
+ raise NotImplementedError
+
+
+class LdapAuth(AuthMethod):
+ """
+ Authentication via LDAP
+
+ User authentication:
+ 1a. bind to LDAP with either
+ - generic configured DN and password (simple bind)
+ - GSSAPI (e.g. Kerberos)
+ 1b. find the ldap object matching username.
+ (return None if more than 1 object is found)
+ 2. if 1 succeeds, try simple bind with the supplied user DN and password
+
+ User lookup:
+ wildcard-search all configured "user lookup attributes" for
+ the given search string
+ """
+ def __init__(self, conf):
+ AuthMethod.__init__(self, conf)
+
+ def _ldap_search(self, query, attrlist=None):
+ """
+ runs any ldap query passed as arg
+ """
+ import ldap
+ from ldap.sasl import gssapi
+ conn = ldap.initialize(self._conf['ldap_uri'])
+ try:
+ if self._conf['ldap_gssapi']:
+ token = gssapi()
+ conn.sasl_interactive_bind_s("", token)
+ else:
+ conn.simple_bind_s(self._conf['ldap_binddn'], self._conf['ldap_bindpw'])
+
+ result = conn.search_ext_s(self._conf['ldap_basedn'],
+ ldap.SCOPE_SUBTREE,
+ filterstr=query,
+ attrlist=attrlist,
+ timeout=self._conf['ldap_timeout'],
+ sizelimit=self._conf['ldap_sizelimit'])
+ except ldap.INVALID_CREDENTIALS:
+ raise ValueError, "invalid LDAP credentials"
+ except ldap.LDAPError, e:
+ raise ValueError, e
+ finally:
+ conn.unbind_s()
+
+ return result
+
+ def _get_ldapuser(self, username, attrlist=None):
+ from ldap.filter import filter_format
+ try:
+ result = self._ldap_search(filter_format("(%s=%s)", [self._conf['ldap_username_attrib'], username]), attrlist)
+ except ValueError, e:
+ print(e)
+ return None
+ # return None if more than 1 object found
+ return result[0] if len(result) == 1 else None
+
+ def user_lookup(self, search):
+ from ldap.filter import filter_format
+ from ldap import LDAPError
+
+ # build ldap OR query
+ q = "(|%s)" % ''.join([filter_format("(%s=*%s*)", [a, search]) for a in self._conf['ldap_lookup_attribs']])
+
+ try:
+ r = self._ldap_search(q, attrlist=[str(self._conf['ldap_username_attrib'])])
+ except ValueError, e:
+ print(e)
+ return []
+ except:
+ return []
+ # return a list of usernames
+ return [x[1][self._conf['ldap_username_attrib']][0].lower() for x in r if x[1].has_key(self._conf['ldap_username_attrib'])]
+
+ def check_user(self, username):
+ # LDAP is NOT case sensitive while sage is, so only lowercase names are allowed
+ if username != username.lower():
+ return False
+ return self._get_ldapuser(username) is not None
+
+ def check_password(self, username, password):
+ import ldap
+ # retrieve username's DN
+ try:
+ u = self._get_ldapuser(username)
+ #u[0] is DN, u[1] is a dict with all other attributes
+ userdn = u[0]
+ except ValueError:
+ return False
+
+ # try to bind with that DN
+ conn = ldap.initialize(uri=self._conf['ldap_uri'])
+ try:
+ conn.simple_bind_s(userdn, password)
+ return True
+ except ldap.INVALID_CREDENTIALS:
+ return False
+ finally:
+ conn.unbind_s()
+
+ def get_attrib(self, username, attrib):
+ # translate some common attribute names to their ldap equivalents, i.e. "email" is "mail
+ attrib = 'mail' if attrib == 'email' else attrib
+
+ u = self._get_ldapuser(username)
+ if u is not None:
+ a = u[1][attrib][0] #if u[1].has_key(attrib) else ''
+ return a
diff --git a/sagenb/notebook/notebook.py b/sagenb/notebook/notebook.py
index bdca928..8605f06 100644
--- a/sagenb/notebook/notebook.py
+++ b/sagenb/notebook/notebook.py
@@ -1368,7 +1368,7 @@ class Notebook(object):
username = username, rev = rev, prev_rev = prev_rev,
next_rev = next_rev, time_ago = time_ago)
- def html_share(self, worksheet, username):
+ def html_share(self, worksheet, username, lookup=None):
r"""
Return the HTML for the "share" page of a worksheet.
@@ -1389,10 +1389,19 @@ class Notebook(object):
sage: nb.html_share(W, 'admin')
u'...currently shared...add or remove collaborators...'
"""
+ lookup_result = self.user_manager().user_lookup(lookup) if lookup else None
+ if lookup_result is not None:
+ lookup_result.sort(lambda x,y: cmp(x.lower(), y.lower()))
+ if username in lookup_result:
+ lookup_result.remove(username)
+
+
return template(os.path.join("html", "notebook", "worksheet_share.html"),
worksheet = worksheet,
notebook = self,
- username = username)
+ username = username,
+ lookup = lookup,
+ lookup_result = lookup_result)
def html_download_or_delete_datafile(self, ws, username, filename):
r"""
diff --git a/sagenb/notebook/server_conf.py b/sagenb/notebook/server_conf.py
index eafd913..580efd5 100644
--- a/sagenb/notebook/server_conf.py
+++ b/sagenb/notebook/server_conf.py
@@ -44,11 +44,23 @@ defaults = {'word_wrap_cols':72,
'recaptcha_private_key':'',
'default_language': 'en_US',
'model_version': 0,
+
+ 'auth_ldap':False,
+ 'ldap_uri':'ldap://example.net:389/',
+ 'ldap_basedn':'ou=users,dc=example,dc=net',
+ 'ldap_binddn':'cn=manager,dc=example,dc=net',
+ 'ldap_bindpw': 'secret',
+ 'ldap_gssapi': False,
+ 'ldap_username_attrib': 'cn',
+ 'ldap_lookup_attribs': ['cn', 'sn', 'givenName', 'mail'],
+ 'ldap_timeout': 5,
+ 'ldap_sizelimit': 30,
}
G_APPEARANCE = _('Appearance')
G_AUTH = _('Authentication')
G_SERVER = _('Server')
+G_LDAP = _('LDAP')
defaults_descriptions = {
@@ -183,6 +195,67 @@ defaults_descriptions = {
GROUP : G_SERVER,
TYPE : T_INFO,
},
+
+ 'auth_ldap': {
+ POS : 1,
+ DESC : _('Enable LDAP Authentication'),
+ GROUP : G_LDAP,
+ TYPE : T_BOOL,
+ },
+ 'ldap_uri': {
+ POS : 2,
+ DESC : _('LDAP URI'),
+ GROUP : G_LDAP,
+ TYPE : T_STRING,
+ },
+ 'ldap_binddn': {
+ POS : 3,
+ DESC : _('Bind DN'),
+ GROUP : G_LDAP,
+ TYPE : T_STRING,
+ },
+ 'ldap_bindpw': {
+ POS : 4,
+ DESC : _('Bind Password'),
+ GROUP : G_LDAP,
+ TYPE : T_STRING,
+ },
+ 'ldap_gssapi': {
+ POS : 5,
+ DESC : _('Use GSSAPI instead of Bind DN/Password'),
+ GROUP : G_LDAP,
+ TYPE : T_BOOL,
+ },
+ 'ldap_basedn': {
+ POS : 6,
+ DESC : _('Base DN'),
+ GROUP : G_LDAP,
+ TYPE : T_STRING,
+ },
+ 'ldap_username_attrib': {
+ POS : 7,
+ DESC: _('Username Attribute (i.e. cn, uid or userPrincipalName)'),
+ GROUP : G_LDAP,
+ TYPE : T_STRING,
+ },
+ 'ldap_lookup_attribs': {
+ POS : 8,
+ DESC: _('Attributes for user lookup'),
+ GROUP : G_LDAP,
+ TYPE : T_LIST,
+ },
+ 'ldap_timeout': {
+ POS : 9,
+ DESC: _('Query timeout (seconds)'),
+ GROUP : G_LDAP,
+ TYPE : T_INTEGER,
+ },
+ 'ldap_sizelimit': {
+ POS : 9,
+ DESC: _('Max. number of search results'),
+ GROUP : G_LDAP,
+ TYPE : T_INTEGER,
+ },
}
diff --git a/sagenb/notebook/user.py b/sagenb/notebook/user.py
index 7eda777..d2fc6b1 100644
--- a/sagenb/notebook/user.py
+++ b/sagenb/notebook/user.py
@@ -289,6 +289,12 @@ class User(object):
False
"""
return self._account_type == 'guest'
+
+ def is_external(self):
+ return self._external_auth is not None
+
+ def external_auth(self):
+ return self._external_auth
def is_suspended(self):
"""
diff --git a/sagenb/notebook/user_manager.py b/sagenb/notebook/user_manager.py
index 2b7730f..e612418 100644
--- a/sagenb/notebook/user_manager.py
+++ b/sagenb/notebook/user_manager.py
@@ -112,7 +112,15 @@ class UserManager(object):
pass
raise KeyError, "no user '%s'"%username
-
+
+ def user_lookup(self, search):
+ r = [x for x in self.users().keys() if search in x]
+ try:
+ r += [u for u in self._user_lookup(search) if u not in r]
+ except AttributeError:
+ pass
+ return r
+
def valid_login_names(self):
"""
Return a list of users that can log in.
@@ -270,7 +278,7 @@ class UserManager(object):
return self._accounts
- def add_user(self, username, password, email, account_type="user", force=False):
+ def add_user(self, username, password, email, account_type="user", external_auth=None, force=False):
"""
Adds a new user to the user dictionary.
@@ -296,7 +304,7 @@ class UserManager(object):
us = self.users()
if us.has_key(username):
print "WARNING: User '%s' already exists -- and is now being replaced."%username
- U = user.User(username, password, email, account_type)
+ U = user.User(username, password, email, account_type, external_auth)
us[username] = U
self.set_password(username, password)
@@ -457,7 +465,7 @@ class SimpleUserManager(UserManager):
if username == "pub" or password == '':
return False
user_password = self.password(username)
- if user_password is None:
+ if user_password is None and not self.user(username).is_external():
print "User %s has None password"%username
return False
if user_password.find('$') == -1:
@@ -468,7 +476,12 @@ class SimpleUserManager(UserManager):
return False
else:
salt, user_password = user_password.split('$')[1:]
- return hashlib.sha256(salt + password).hexdigest() == user_password
+ if hashlib.sha256(salt + password).hexdigest() == user_password:
+ return True
+ try:
+ return self._check_password(username, password)
+ except AttributeError:
+ return False;
def get_accounts(self):
# need to use notebook's conf because those are already serialized
@@ -481,8 +494,68 @@ class SimpleUserManager(UserManager):
self._accounts = value
self._conf['accounts'] = value
-class OpenIDUserManager(SimpleUserManager):
+
+
+class ExtAuthUserManager(SimpleUserManager):
def __init__(self, accounts=None, conf=None):
+ SimpleUserManager.__init__(self, accounts=accounts, conf=conf)
+
+ from auth import LdapAuth
+
+ # keys must match to a T_BOOL option in server_config.py
+ # so we can turn this auth method on/off
+ self._auth_methods = {
+ 'auth_ldap': LdapAuth(self._conf),
+ }
+
+ def _user(self, username):
+ """
+ Check all auth methods that are enabled in the notebook's config.
+ If a valid username is found, a new User object will be created.
+ """
+ for a in self._auth_methods:
+ if self._conf[a]:
+ u = self._auth_methods[a].check_user(username)
+ if u:
+ try:
+ email = self._auth_methods[a].get_attrib(username, 'email')
+ except KeyError:
+ email = None
+
+ self.add_user(username, password='', email=email, account_type='user', external_auth=a, force=True)
+ return self.users()[username]
+
+ raise KeyError, "no user '%s'"%username
+
+ def _check_password(self, username, password):
+ """
+ Find auth method for user 'username' and
+ use that auth method to check username/password combination.
+ """
+ u = self.users()[username]
+ if u.is_external():
+ a = u.external_auth()
+ else:
+ return False
+
+ if self._conf[a]:
+ return self._auth_methods[a].check_password(username, password)
+
+ return False
+
+ def _user_lookup(self, search):
+ """
+ Returns a list of usernames that are found when calling user_lookup on all enabled auth methods
+ """
+ r = []
+ for a in self._auth_methods:
+ if self._conf[a]:
+ # avoid duplicates
+ r += [u for u in self._auth_methods[a].user_lookup(search) if u not in r]
+ return r
+
+class OpenIDUserManager(ExtAuthUserManager):
+ def __init__(self, accounts=True, conf=None):
"""
Creates an user_manager that supports OpenID identities
EXAMPLES:
@@ -492,7 +565,7 @@ class OpenIDUserManager(SimpleUserManager):
sage: UM.check_password('admin','passpass')
True
"""
- SimpleUserManager.__init__(self, accounts=accounts, conf=conf)
+ ExtAuthUserManager.__init__(self, accounts=accounts, conf=conf)
self._openid = {}
def load(self, datastore):
@@ -518,6 +591,9 @@ class OpenIDUserManager(SimpleUserManager):
sage: UM.get_username_from_openid('https://www.google.com/accounts/o8/id?id=AItdaWgzjV1HJTa552549o1csTDdfeH6_bPxF14')
'thedude'
"""
+ if not self._conf['openid']:
+ raise RuntimeError
+
try:
return self._openid[identity_url]
except KeyError:
@@ -534,10 +610,14 @@ class OpenIDUserManager(SimpleUserManager):
sage: UM.get_username_from_openid('https://www.google.com/accounts/o8/id?id=AItdaWgzjV1HJTa552549o1csTDdfeH6_bPxF14')
'thedude'
"""
+ if not self._conf['openid']:
+ raise RuntimeError
self._openid[identity_url] = username
def get_user_from_openid(self, identity_url):
"""
Return the user object corresponding ot a given identity_url
"""
+ if not self._conf['openid']:
+ raise RuntimeError
return self.user(self.get_username_from_openid(identity_url))
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-science/packages/sagenb.git
More information about the debian-science-commits
mailing list