forked from shaba/openuds
More pep8 related fixes, also some refactoring
This commit is contained in:
parent
dab3e26223
commit
89addaf585
@ -32,6 +32,8 @@
|
|||||||
|
|
||||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||||
'''
|
'''
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.utils.translation import ugettext_noop as _
|
from django.utils.translation import ugettext_noop as _
|
||||||
from uds.core.ui.UserInterface import gui
|
from uds.core.ui.UserInterface import gui
|
||||||
from uds.core import auths
|
from uds.core import auths
|
||||||
|
@ -36,8 +36,11 @@ from uds.core import auths
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
__updated__ = '2014-02-19'
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class SampleAuth(auths.Authenticator):
|
class SampleAuth(auths.Authenticator):
|
||||||
'''
|
'''
|
||||||
This class represents a sample authenticator.
|
This class represents a sample authenticator.
|
||||||
@ -63,52 +66,51 @@ class SampleAuth(auths.Authenticator):
|
|||||||
the string when it is sent to the administration client.
|
the string when it is sent to the administration client.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
#: Name of type, used at administration interface to identify this
|
# : Name of type, used at administration interface to identify this
|
||||||
#: authenticator (i.e. LDAP, SAML, ...)
|
# : authenticator (i.e. LDAP, SAML, ...)
|
||||||
#: This string will be translated when provided to admin interface
|
# : This string will be translated when provided to admin interface
|
||||||
#: using ugettext, so you can mark it as "_" at derived classes (using ugettext_noop)
|
# : using ugettext, so you can mark it as "_" at derived classes (using ugettext_noop)
|
||||||
#: if you want so it can be translated.
|
# : if you want so it can be translated.
|
||||||
typeName = _('Sample Authenticator')
|
typeName = _('Sample Authenticator')
|
||||||
|
|
||||||
#: Name of type used by Managers to identify this type of service
|
# : Name of type used by Managers to identify this type of service
|
||||||
#: We could have used here the Class name, but we decided that the
|
# : We could have used here the Class name, but we decided that the
|
||||||
#: module implementator will be the one that will provide a name that
|
# : module implementator will be the one that will provide a name that
|
||||||
#: will relation the class (type) and that name.
|
# : will relation the class (type) and that name.
|
||||||
typeType = 'SampleAuthenticator'
|
typeType = 'SampleAuthenticator'
|
||||||
|
|
||||||
#: Description shown at administration level for this authenticator.
|
# : Description shown at administration level for this authenticator.
|
||||||
#: This string will be translated when provided to admin interface
|
# : This string will be translated when provided to admin interface
|
||||||
#: using ugettext, so you can mark it as "_" at derived classes (using ugettext_noop)
|
# : using ugettext, so you can mark it as "_" at derived classes (using ugettext_noop)
|
||||||
#: if you want so it can be translated.
|
# : if you want so it can be translated.
|
||||||
typeDescription = _('Sample dummy authenticator')
|
typeDescription = _('Sample dummy authenticator')
|
||||||
|
|
||||||
|
# : Icon file, used to represent this authenticator at administration interface
|
||||||
#: Icon file, used to represent this authenticator at administration interface
|
# : This file should be at same folder as this class is, except if you provide
|
||||||
#: This file should be at same folder as this class is, except if you provide
|
# : your own :py:meth:uds.core.BaseModule.BaseModule.icon method.
|
||||||
#: your own :py:meth:uds.core.BaseModule.BaseModule.icon method.
|
|
||||||
iconFile = 'auth.png'
|
iconFile = 'auth.png'
|
||||||
|
|
||||||
#: Mark this authenticator as that the users comes from outside the UDS
|
# : Mark this authenticator as that the users comes from outside the UDS
|
||||||
#: database, that are most authenticator (except Internal DB)
|
# : database, that are most authenticator (except Internal DB)
|
||||||
#: True is the default value, so we do not need it in fact
|
# : True is the default value, so we do not need it in fact
|
||||||
# isExternalSource = True
|
# isExternalSource = True
|
||||||
|
|
||||||
#: If we need to enter the password for this user when creating a new
|
# : If we need to enter the password for this user when creating a new
|
||||||
#: user at administration interface. Used basically by internal authenticator.
|
# : user at administration interface. Used basically by internal authenticator.
|
||||||
#: False is the default value, so this is not needed in fact
|
# : False is the default value, so this is not needed in fact
|
||||||
#: needsPassword = False
|
# : needsPassword = False
|
||||||
|
|
||||||
#: Label for username field, shown at administration interface user form.
|
# : Label for username field, shown at administration interface user form.
|
||||||
userNameLabel = _('Fake User')
|
userNameLabel = _('Fake User')
|
||||||
|
|
||||||
# Label for group field, shown at administration interface user form.
|
# Label for group field, shown at administration interface user form.
|
||||||
groupNameLabel = _('Fake Group')
|
groupNameLabel = _('Fake Group')
|
||||||
|
|
||||||
#: Definition of this type of authenticator form
|
# : Definition of this type of authenticator form
|
||||||
#: We will define a simple form where we will use a simple
|
# : We will define a simple form where we will use a simple
|
||||||
#: list editor to allow entering a few group names
|
# : list editor to allow entering a few group names
|
||||||
|
|
||||||
groups = gui.EditableList(label=_('Groups'), values = ['Gods', 'Daemons', 'Mortals'])
|
groups = gui.EditableList(label=_('Groups'), values=['Gods', 'Daemons', 'Mortals'])
|
||||||
|
|
||||||
def initialize(self, values):
|
def initialize(self, values):
|
||||||
'''
|
'''
|
||||||
@ -192,7 +194,7 @@ class SampleAuth(auths.Authenticator):
|
|||||||
|
|
||||||
:note: groupsManager is an in/out parameter
|
:note: groupsManager is an in/out parameter
|
||||||
'''
|
'''
|
||||||
if username != credentials: # All users with same username and password are allowed
|
if username != credentials: # All users with same username and password are allowed
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Now the tricky part. We will make this user belong to groups that contains at leat
|
# Now the tricky part. We will make this user belong to groups that contains at leat
|
||||||
@ -232,22 +234,21 @@ class SampleAuth(auths.Authenticator):
|
|||||||
# Here there is a sample, commented out
|
# Here there is a sample, commented out
|
||||||
# In this sample, we will make a list of valid users, and when clicked,
|
# In this sample, we will make a list of valid users, and when clicked,
|
||||||
# it will fill up original form with username and same password, and submit it.
|
# it will fill up original form with username and same password, and submit it.
|
||||||
#res = ''
|
# res = ''
|
||||||
#for u in self.dbAuthenticator().users.all():
|
# for u in self.dbAuthenticator().users.all():
|
||||||
# res += '<a class="myNames" id="{0}" href="">{0}</a><br/>'.format(u.name)
|
# res += '<a class="myNames" id="{0}" href="">{0}</a><br/>'.format(u.name)
|
||||||
#
|
#
|
||||||
#res += '<script type="text/javascript">$(".myNames").click(function() { '
|
# res += '<script type="text/javascript">$(".myNames").click(function() { '
|
||||||
#res += '$("#id_user").val(this.id); $("#id_password").val(this.id); $("#loginform").submit(); return false;});</script>'
|
# res += '$("#id_user").val(this.id); $("#id_password").val(this.id); $("#loginform").submit(); return false;});</script>'
|
||||||
#return res
|
# return res
|
||||||
|
|
||||||
# I know, this is a bit ugly, but this is just a sample :-)
|
# I know, this is a bit ugly, but this is just a sample :-)
|
||||||
|
|
||||||
res = '<p>Login name: <input id="logname" type="text"/></p>'
|
res = '<p>Login name: <input id="logname" type="text"/></p>'
|
||||||
res +='<p><a href="" onclick="window.location.replace(\'' + self.callbackUrl() + '?user='
|
res += '<p><a href="" onclick="window.location.replace(\'' + self.callbackUrl() + '?user='
|
||||||
res += '\' + $(\'#logname\').val()); return false;">Login</a></p>'
|
res += '\' + $(\'#logname\').val()); return false;">Login</a></p>'
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
def authCallback(self, parameters, gm):
|
def authCallback(self, parameters, gm):
|
||||||
'''
|
'''
|
||||||
We provide this as a sample of callback for an user.
|
We provide this as a sample of callback for an user.
|
||||||
|
@ -34,5 +34,8 @@ take care of registering it as provider
|
|||||||
|
|
||||||
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
|
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
|
||||||
'''
|
'''
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from SampleAuth import SampleAuth
|
from SampleAuth import SampleAuth
|
||||||
|
|
||||||
|
__updated__ = '2014-02-19'
|
||||||
|
@ -32,33 +32,38 @@
|
|||||||
|
|
||||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||||
'''
|
'''
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.utils.translation import ugettext_noop as _
|
from django.utils.translation import ugettext_noop as _
|
||||||
from uds.core.ui.UserInterface import gui
|
from uds.core.ui.UserInterface import gui
|
||||||
from uds.core.auths import Authenticator
|
from uds.core.auths import Authenticator
|
||||||
import ldap
|
|
||||||
|
|
||||||
import logging
|
|
||||||
from uds.core.auths.Exceptions import AuthenticatorException
|
from uds.core.auths.Exceptions import AuthenticatorException
|
||||||
|
|
||||||
|
import ldap
|
||||||
|
import logging
|
||||||
|
|
||||||
|
__updated__ = '2014-02-19'
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
LDAP_RESULT_LIMIT = 50
|
LDAP_RESULT_LIMIT = 50
|
||||||
|
|
||||||
|
|
||||||
class SimpleLDAPAuthenticator(Authenticator):
|
class SimpleLDAPAuthenticator(Authenticator):
|
||||||
|
|
||||||
host = gui.TextField(length=64, label = _('Host'), order = 1, tooltip = _('Ldap Server IP or Hostname'), required = True)
|
host = gui.TextField(length=64, label=_('Host'), order=1, tooltip=_('Ldap Server IP or Hostname'), required=True)
|
||||||
port = gui.NumericField(length=5, label = _('Port'), defvalue = '389', order = 2, tooltip = _('Ldap port (389 for non ssl, 636 for ssl normally'), required = True)
|
port = gui.NumericField(length=5, label=_('Port'), defvalue='389', order=2, tooltip=_('Ldap port (389 for non ssl, 636 for ssl normally'), required=True)
|
||||||
ssl = gui.CheckBoxField(label = _('Use SSL'), order = 3, tooltip = _('If checked, will use a ssl connection to ldap (if port is 389, will use in fact port 636)'))
|
ssl = gui.CheckBoxField(label=_('Use SSL'), order=3, tooltip=_('If checked, will use a ssl connection to ldap (if port is 389, will use in fact port 636)'))
|
||||||
username = gui.TextField(length=64, label = _('Ldap User'), order = 4, tooltip = _('Username with read privileges on the base selected'), required = True)
|
username = gui.TextField(length=64, label=_('Ldap User'), order=4, tooltip=_('Username with read privileges on the base selected'), required=True)
|
||||||
password = gui.PasswordField(lenth=32, label = _('Password'), order = 5, tooltip = _('Password of the ldap user'), required = True)
|
password = gui.PasswordField(lenth=32, label=_('Password'), order=5, tooltip=_('Password of the ldap user'), required=True)
|
||||||
timeout = gui.NumericField(length=3, label = _('Timeout'), defvalue = '10', order = 6, tooltip = _('Timeout in seconds of connection to LDAP'), required = True)
|
timeout = gui.NumericField(length=3, label=_('Timeout'), defvalue='10', order=6, tooltip=_('Timeout in seconds of connection to LDAP'), required=True)
|
||||||
ldapBase = gui.TextField(length=64, label = _('Base'), order = 7, tooltip = _('Common search base (used for "users" and "groups"'), required = True)
|
ldapBase = gui.TextField(length=64, label=_('Base'), order=7, tooltip=_('Common search base (used for "users" and "groups"'), required=True)
|
||||||
userClass = gui.TextField(length=64, label = _('User class'), defvalue = 'posixAccount', order = 8, tooltip = _('Class for LDAP users (normally posixAccount)'), required = True)
|
userClass = gui.TextField(length=64, label=_('User class'), defvalue='posixAccount', order=8, tooltip=_('Class for LDAP users (normally posixAccount)'), required=True)
|
||||||
userIdAttr = gui.TextField(length=64, label = _('User Id Attr'), defvalue = 'uid', order = 9, tooltip = _('Attribute that contains the user id'), required = True)
|
userIdAttr = gui.TextField(length=64, label=_('User Id Attr'), defvalue='uid', order=9, tooltip=_('Attribute that contains the user id'), required=True)
|
||||||
userNameAttr = gui.TextField(length=64, label = _('User Name Attr'), defvalue = 'uid', order = 10, tooltip = _('Attributes that contains the user name (list of comma separated values)'), required = True)
|
userNameAttr = gui.TextField(length=64, label=_('User Name Attr'), defvalue='uid', order=10, tooltip=_('Attributes that contains the user name (list of comma separated values)'), required=True)
|
||||||
groupClass = gui.TextField(length=64, label = _('Group class'), defvalue = 'posixGroup', order = 11, tooltip = _('Class for LDAP groups (normally poxisGroup)'), required = True)
|
groupClass = gui.TextField(length=64, label=_('Group class'), defvalue='posixGroup', order=11, tooltip=_('Class for LDAP groups (normally poxisGroup)'), required=True)
|
||||||
groupIdAttr = gui.TextField(length=64, label = _('Group Id Attr'), defvalue = 'cn', order = 12, tooltip = _('Attribute that contains the group id'), required = True)
|
groupIdAttr = gui.TextField(length=64, label=_('Group Id Attr'), defvalue='cn', order=12, tooltip=_('Attribute that contains the group id'), required=True)
|
||||||
memberAttr = gui.TextField(length=64, label = _('Group membership attr'), defvalue = 'memberUid', order = 13, tooltip = _('Attribute of the group that contains the users belonging to it'), required = True)
|
memberAttr = gui.TextField(length=64, label=_('Group membership attr'), defvalue='memberUid', order=13, tooltip=_('Attribute of the group that contains the users belonging to it'), required=True)
|
||||||
|
|
||||||
typeName = _('SimpleLDAP Authenticator')
|
typeName = _('SimpleLDAP Authenticator')
|
||||||
typeType = 'SimpleLdapAuthenticator'
|
typeType = 'SimpleLdapAuthenticator'
|
||||||
@ -76,7 +81,7 @@ class SimpleLDAPAuthenticator(Authenticator):
|
|||||||
# Label for password field
|
# Label for password field
|
||||||
passwordLabel = _("Password")
|
passwordLabel = _("Password")
|
||||||
|
|
||||||
def __init__(self, dbAuth, environment, values = None):
|
def __init__(self, dbAuth, environment, values=None):
|
||||||
super(SimpleLDAPAuthenticator, self).__init__(dbAuth, environment, values)
|
super(SimpleLDAPAuthenticator, self).__init__(dbAuth, environment, values)
|
||||||
if values != None:
|
if values != None:
|
||||||
self._host = values['host']
|
self._host = values['host']
|
||||||
@ -91,7 +96,7 @@ class SimpleLDAPAuthenticator(Authenticator):
|
|||||||
self._userIdAttr = values['userIdAttr']
|
self._userIdAttr = values['userIdAttr']
|
||||||
self._groupIdAttr = values['groupIdAttr']
|
self._groupIdAttr = values['groupIdAttr']
|
||||||
self._memberAttr = values['memberAttr']
|
self._memberAttr = values['memberAttr']
|
||||||
self._userNameAttr = values['userNameAttr'].replace(' ', '') # Removes white spaces
|
self._userNameAttr = values['userNameAttr'].replace(' ', '') # Removes white spaces
|
||||||
else:
|
else:
|
||||||
self._host = None
|
self._host = None
|
||||||
self._port = None
|
self._port = None
|
||||||
@ -109,12 +114,13 @@ class SimpleLDAPAuthenticator(Authenticator):
|
|||||||
self._connection = None
|
self._connection = None
|
||||||
|
|
||||||
def valuesDict(self):
|
def valuesDict(self):
|
||||||
return { 'host' : self._host, 'port' : self._port, 'ssl' : gui.boolToStr(self._ssl),
|
return {
|
||||||
'username' : self._username, 'password' : self._password, 'timeout' : self._timeout,
|
'host': self._host, 'port': self._port, 'ssl': gui.boolToStr(self._ssl),
|
||||||
'ldapBase' : self._ldapBase, 'userClass' : self._userClass, 'groupClass' : self._groupClass,
|
'username': self._username, 'password': self._password, 'timeout': self._timeout,
|
||||||
'userIdAttr' : self._userIdAttr, 'groupIdAttr' : self._groupIdAttr, 'memberAttr' : self._memberAttr,
|
'ldapBase': self._ldapBase, 'userClass': self._userClass, 'groupClass': self._groupClass,
|
||||||
'userNameAttr' : self._userNameAttr
|
'userIdAttr': self._userIdAttr, 'groupIdAttr': self._groupIdAttr, 'memberAttr': self._memberAttr,
|
||||||
}
|
'userNameAttr': self._userNameAttr
|
||||||
|
}
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Ldap Auth: {0}:{1}@{2}:{3}, base = {4}, userClass = {5}, groupClass = {6}, userIdAttr = {7}, groupIdAttr = {8}, memberAttr = {9}, userName attr = {10}".format(
|
return "Ldap Auth: {0}:{1}@{2}:{3}, base = {4}, userClass = {5}, groupClass = {6}, userIdAttr = {7}, groupIdAttr = {8}, memberAttr = {9}, userName attr = {10}".format(
|
||||||
@ -124,24 +130,24 @@ class SimpleLDAPAuthenticator(Authenticator):
|
|||||||
def marshal(self):
|
def marshal(self):
|
||||||
return str.join('\t', ['v1',
|
return str.join('\t', ['v1',
|
||||||
self._host, self._port, gui.boolToStr(self._ssl), self._username, self._password, self._timeout,
|
self._host, self._port, gui.boolToStr(self._ssl), self._username, self._password, self._timeout,
|
||||||
self._ldapBase, self._userClass, self._groupClass, self._userIdAttr, self._groupIdAttr, self._memberAttr, self._userNameAttr ])
|
self._ldapBase, self._userClass, self._groupClass, self._userIdAttr, self._groupIdAttr, self._memberAttr, self._userNameAttr])
|
||||||
|
|
||||||
def unmarshal(self, str):
|
def unmarshal(self, str_):
|
||||||
data = str.split('\t')
|
data = str_.split('\t')
|
||||||
if data[0] == 'v1':
|
if data[0] == 'v1':
|
||||||
logger.debug("Data: {0}".format(data[1:]))
|
logger.debug("Data: {0}".format(data[1:]))
|
||||||
self._host, self._port, self._ssl, self._username, self._password, self._timeout, self._ldapBase, self._userClass, self._groupClass, self._userIdAttr, self._groupIdAttr, self._memberAttr, self._userNameAttr = data[1:]
|
self._host, self._port, self._ssl, self._username, self._password, self._timeout, self._ldapBase, self._userClass, self._groupClass, self._userIdAttr, self._groupIdAttr, self._memberAttr, self._userNameAttr = data[1:]
|
||||||
self._ssl = gui.strToBool(self._ssl)
|
self._ssl = gui.strToBool(self._ssl)
|
||||||
|
|
||||||
def __connection(self, username = None, password = None):
|
def __connection(self, username=None, password=None):
|
||||||
if self._connection is None or username is not None: # We want this method also to check credentials
|
if self._connection is None or username is not None: # We want this method also to check credentials
|
||||||
l = None
|
l = None
|
||||||
cache = False
|
cache = False
|
||||||
try:
|
try:
|
||||||
#ldap.set_option(ldap.OPT_DEBUG_LEVEL, 9)
|
# ldap.set_option(ldap.OPT_DEBUG_LEVEL, 9)
|
||||||
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
|
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
|
||||||
schema = self._ssl and 'ldaps' or 'ldap'
|
schema = self._ssl and 'ldaps' or 'ldap'
|
||||||
port = self._port != '389' and ':' + self._port or ''
|
port = self._port != '389' and ':' + self._port or ''
|
||||||
uri = "%s://%s%s" % (schema, self._host, port)
|
uri = "%s://%s%s" % (schema, self._host, port)
|
||||||
logger.debug('Ldap uri: {0}'.format(uri))
|
logger.debug('Ldap uri: {0}'.format(uri))
|
||||||
l = ldap.initialize(uri=uri)
|
l = ldap.initialize(uri=uri)
|
||||||
@ -153,63 +159,62 @@ class SimpleLDAPAuthenticator(Authenticator):
|
|||||||
username = self._username
|
username = self._username
|
||||||
password = self._password
|
password = self._password
|
||||||
|
|
||||||
l.simple_bind_s(who = username, cred = password)
|
l.simple_bind_s(who=username, cred=password)
|
||||||
except ldap.LDAPError, e:
|
except ldap.LDAPError, e:
|
||||||
str = _('Ldap connection error: ')
|
str_ = _('Ldap connection error: ')
|
||||||
if type(e.message) == dict:
|
if type(e.message) == dict:
|
||||||
str += e.message.has_key('info') and e.message['info'] + ',' or ''
|
str_ += 'info' in e.message and e.message['info'] + ',' or ''
|
||||||
str += e.message.has_key('desc') and e.message['desc'] or ''
|
str_ += 'desc' in e.message and e.message['desc'] or ''
|
||||||
else :
|
else:
|
||||||
str += str(e)
|
str_ += str_(e)
|
||||||
raise Exception(str)
|
raise Exception(str_)
|
||||||
if cache is True:
|
if cache is True:
|
||||||
self._connection = l
|
self._connection = l
|
||||||
else:
|
else:
|
||||||
return l # Do not cache nor overwrite "global" connection
|
return l # Do not cache nor overwrite "global" connection
|
||||||
return self._connection
|
return self._connection
|
||||||
|
|
||||||
def __getUser(self, username):
|
def __getUser(self, username):
|
||||||
try:
|
try:
|
||||||
con = self.__connection()
|
con = self.__connection()
|
||||||
filter = '(&(objectClass=%s)(%s=%s))' % (self._userClass, self._userIdAttr, username)
|
filter_ = '(&(objectClass=%s)(%s=%s))' % (self._userClass, self._userIdAttr, username)
|
||||||
attrlist = self._userNameAttr.split(',') + [self._userIdAttr]
|
attrlist = self._userNameAttr.split(',') + [self._userIdAttr]
|
||||||
logger.debug('Getuser filter: {0}, attr list: {1}'.format(filter, attrlist))
|
logger.debug('Getuser filter_: {0}, attr list: {1}'.format(filter_, attrlist))
|
||||||
res = con.search_ext_s(base = self._ldapBase, scope = ldap.SCOPE_SUBTREE,
|
res = con.search_ext_s(base=self._ldapBase, scope=ldap.SCOPE_SUBTREE,
|
||||||
filterstr = filter, attrlist = attrlist, sizelimit = LDAP_RESULT_LIMIT)[0]
|
filterstr=filter_, attrlist=attrlist, sizelimit=LDAP_RESULT_LIMIT)[0]
|
||||||
usr = dict(( k, '' ) for k in attrlist)
|
usr = dict((k, '') for k in attrlist)
|
||||||
usr.update(res[1])
|
usr.update(res[1])
|
||||||
usr.update( {'dn' : res[0], '_id' : username })
|
usr.update({'dn': res[0], '_id': username})
|
||||||
logger.debug('Usr: {0}'.format(usr))
|
logger.debug('Usr: {0}'.format(usr))
|
||||||
return usr
|
return usr
|
||||||
except Exception, e:
|
except Exception:
|
||||||
logger.exception('Exception:')
|
logger.exception('Exception:')
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def __getGroup(self, groupName):
|
def __getGroup(self, groupName):
|
||||||
try:
|
try:
|
||||||
con = self.__connection()
|
con = self.__connection()
|
||||||
filter = '(&(objectClass=%s)(%s=%s))' % (self._groupClass, self._groupIdAttr, groupName)
|
filter_ = '(&(objectClass=%s)(%s=%s))' % (self._groupClass, self._groupIdAttr, groupName)
|
||||||
attrlist = [self._memberAttr]
|
attrlist = [self._memberAttr]
|
||||||
logger.debug('Getgroup filter: {0}, attr list {1}'.format(filter, attrlist))
|
logger.debug('Getgroup filter_: {0}, attr list {1}'.format(filter_, attrlist))
|
||||||
res = con.search_ext_s(base = self._ldapBase, scope = ldap.SCOPE_SUBTREE,
|
res = con.search_ext_s(base=self._ldapBase, scope=ldap.SCOPE_SUBTREE,
|
||||||
filterstr = filter, attrlist = attrlist, sizelimit = LDAP_RESULT_LIMIT)[0]
|
filterstr=filter_, attrlist=attrlist, sizelimit=LDAP_RESULT_LIMIT)[0]
|
||||||
grp = dict(( k, [''] ) for k in attrlist)
|
grp = dict((k, ['']) for k in attrlist)
|
||||||
grp.update(res[1])
|
grp.update(res[1])
|
||||||
grp.update( {'dn' : res[0], '_id' : groupName })
|
grp.update({'dn': res[0], '_id': groupName})
|
||||||
logger.debug('Group: {0}'.format(grp))
|
logger.debug('Group: {0}'.format(grp))
|
||||||
return grp
|
return grp
|
||||||
except Exception, e:
|
except Exception:
|
||||||
logger.exception('Exception:')
|
logger.exception('Exception:')
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def __getGroups(self, usr):
|
def __getGroups(self, usr):
|
||||||
try:
|
try:
|
||||||
con = self.__connection()
|
con = self.__connection()
|
||||||
filter = '(&(objectClass=%s)(|(%s=%s)(%s=%s)))' % (self._groupClass, self._memberAttr, usr['_id'], self._memberAttr, usr['dn'])
|
filter_ = '(&(objectClass=%s)(|(%s=%s)(%s=%s)))' % (self._groupClass, self._memberAttr, usr['_id'], self._memberAttr, usr['dn'])
|
||||||
logger.debug('Filter: {0}'.format(filter))
|
logger.debug('Filter: {0}'.format(filter_))
|
||||||
res = con.search_ext_s(base = self._ldapBase, scope = ldap.SCOPE_SUBTREE, filterstr = filter, attrlist = [self._groupIdAttr],
|
res = con.search_ext_s(base=self._ldapBase, scope=ldap.SCOPE_SUBTREE, filterstr=filter_, attrlist=[self._groupIdAttr],
|
||||||
sizelimit = LDAP_RESULT_LIMIT)
|
sizelimit=LDAP_RESULT_LIMIT)
|
||||||
groups = {}
|
groups = {}
|
||||||
for g in res:
|
for g in res:
|
||||||
v = g[1][self._groupIdAttr]
|
v = g[1][self._groupIdAttr]
|
||||||
@ -223,13 +228,12 @@ class SimpleLDAPAuthenticator(Authenticator):
|
|||||||
except Exception:
|
except Exception:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
def __getUserRealName(self, usr):
|
def __getUserRealName(self, usr):
|
||||||
'''
|
'''
|
||||||
Tries to extract the real name for this user. Will return all atttributes (joint)
|
Tries to extract the real name for this user. Will return all atttributes (joint)
|
||||||
specified in _userNameAttr (comma separated).
|
specified in _userNameAttr (comma separated).
|
||||||
'''
|
'''
|
||||||
return ' '.join([ (type(usr.get(id_, '')) is list and ' '.join(( str(k) for k in usr.get(id_, ''))) or str(usr.get(id_, ''))) for id_ in self._userNameAttr.split(',') ]).strip()
|
return ' '.join([(type(usr.get(id_, '')) is list and ' '.join((str(k) for k in usr.get(id_, ''))) or str(usr.get(id_, ''))) for id_ in self._userNameAttr.split(',')]).strip()
|
||||||
|
|
||||||
def authenticate(self, username, credentials, groupsManager):
|
def authenticate(self, username, credentials, groupsManager):
|
||||||
'''
|
'''
|
||||||
@ -249,7 +253,7 @@ class SimpleLDAPAuthenticator(Authenticator):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# Let's see first if it credentials are fine
|
# Let's see first if it credentials are fine
|
||||||
self.__connection(usr['dn'], credentials) # Will raise an exception if it can't connect
|
self.__connection(usr['dn'], credentials) # Will raise an exception if it can't connect
|
||||||
|
|
||||||
groupsManager.validate(self.__getGroups(usr).keys())
|
groupsManager.validate(self.__getGroups(usr).keys())
|
||||||
|
|
||||||
@ -301,7 +305,6 @@ class SimpleLDAPAuthenticator(Authenticator):
|
|||||||
if res is None:
|
if res is None:
|
||||||
raise AuthenticatorException(_('Group not found'))
|
raise AuthenticatorException(_('Group not found'))
|
||||||
|
|
||||||
|
|
||||||
def getGroups(self, username, groupsManager):
|
def getGroups(self, username, groupsManager):
|
||||||
'''
|
'''
|
||||||
Looks for the real groups to which the specified user belongs
|
Looks for the real groups to which the specified user belongs
|
||||||
@ -317,13 +320,15 @@ class SimpleLDAPAuthenticator(Authenticator):
|
|||||||
try:
|
try:
|
||||||
con = self.__connection()
|
con = self.__connection()
|
||||||
res = []
|
res = []
|
||||||
for r in con.search_ext_s(base = self._ldapBase, scope = ldap.SCOPE_SUBTREE, filterstr = '(&(objectClass=%s)(%s=%s*))' % (self._userClass, self._userIdAttr, pattern), sizelimit=LDAP_RESULT_LIMIT):
|
for r in con.search_ext_s(base=self._ldapBase, scope=ldap.SCOPE_SUBTREE, filterstr='(&(objectClass=%s)(%s=%s*))' % (self._userClass, self._userIdAttr, pattern), sizelimit=LDAP_RESULT_LIMIT):
|
||||||
usrId = r[1].get(self._userIdAttr, '')
|
usrId = r[1].get(self._userIdAttr, '')
|
||||||
usrId = type(usrId) == list and usrId[0] or usrId
|
usrId = type(usrId) == list and usrId[0] or usrId
|
||||||
res.append( { 'id' : usrId,
|
res.append({
|
||||||
'name' : self.__getUserRealName(r[1]) } )
|
'id': usrId,
|
||||||
|
'name': self.__getUserRealName(r[1])
|
||||||
|
})
|
||||||
return res
|
return res
|
||||||
except Exception, e:
|
except Exception:
|
||||||
logger.exception("Exception: ")
|
logger.exception("Exception: ")
|
||||||
raise AuthenticatorException(_('Too many results, be more specific'))
|
raise AuthenticatorException(_('Too many results, be more specific'))
|
||||||
|
|
||||||
@ -331,17 +336,18 @@ class SimpleLDAPAuthenticator(Authenticator):
|
|||||||
try:
|
try:
|
||||||
con = self.__connection()
|
con = self.__connection()
|
||||||
res = []
|
res = []
|
||||||
for r in con.search_ext_s(base = self._ldapBase, scope = ldap.SCOPE_SUBTREE, filterstr = '(&(objectClass=%s)(%s=%s*))' % (self._groupClass, self._groupIdAttr, pattern), sizelimit=LDAP_RESULT_LIMIT):
|
for r in con.search_ext_s(base=self._ldapBase, scope=ldap.SCOPE_SUBTREE, filterstr='(&(objectClass=%s)(%s=%s*))' % (self._groupClass, self._groupIdAttr, pattern), sizelimit=LDAP_RESULT_LIMIT):
|
||||||
grpId = r[1].get(self._groupIdAttr, '')
|
grpId = r[1].get(self._groupIdAttr, '')
|
||||||
grpId = type(grpId) == list and grpId[0] or grpId
|
grpId = type(grpId) == list and grpId[0] or grpId
|
||||||
res.append( { 'id' : grpId,
|
res.append({
|
||||||
'name' : grpId } )
|
'id': grpId,
|
||||||
|
'name': grpId
|
||||||
|
})
|
||||||
return res
|
return res
|
||||||
except Exception, e:
|
except Exception:
|
||||||
logger.exception("Exception: ")
|
logger.exception("Exception: ")
|
||||||
raise AuthenticatorException(_('Too many results, be more specific'))
|
raise AuthenticatorException(_('Too many results, be more specific'))
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def test(env, data):
|
def test(env, data):
|
||||||
try:
|
try:
|
||||||
@ -358,12 +364,12 @@ class SimpleLDAPAuthenticator(Authenticator):
|
|||||||
return [False, str(e)]
|
return [False, str(e)]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
con.search_s(base = self._ldapBase, scope = ldap.SCOPE_BASE)
|
con.search_s(base=self._ldapBase, scope=ldap.SCOPE_BASE)
|
||||||
except Exception:
|
except Exception:
|
||||||
return [False, _('Ldap search base is incorrect')]
|
return [False, _('Ldap search base is incorrect')]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if len(con.search_ext_s(base = self._ldapBase, scope = ldap.SCOPE_SUBTREE, filterstr = '(objectClass=%s)' % self._userClass, sizelimit=1)) == 1:
|
if len(con.search_ext_s(base=self._ldapBase, scope=ldap.SCOPE_SUBTREE, filterstr='(objectClass=%s)' % self._userClass, sizelimit=1)) == 1:
|
||||||
raise Exception()
|
raise Exception()
|
||||||
return [False, _('Ldap user class seems to be incorrect (no user found by that class)')]
|
return [False, _('Ldap user class seems to be incorrect (no user found by that class)')]
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
@ -371,7 +377,7 @@ class SimpleLDAPAuthenticator(Authenticator):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if len(con.search_ext_s(base = self._ldapBase, scope = ldap.SCOPE_SUBTREE, filterstr = '(objectClass=%s)' % self._groupClass, sizelimit=1)) == 1:
|
if len(con.search_ext_s(base=self._ldapBase, scope=ldap.SCOPE_SUBTREE, filterstr='(objectClass=%s)' % self._groupClass, sizelimit=1)) == 1:
|
||||||
raise Exception()
|
raise Exception()
|
||||||
return [False, _('Ldap group class seems to be incorrect (no group found by that class)')]
|
return [False, _('Ldap group class seems to be incorrect (no group found by that class)')]
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
@ -379,7 +385,7 @@ class SimpleLDAPAuthenticator(Authenticator):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if len(con.search_ext_s(base = self._ldapBase, scope = ldap.SCOPE_SUBTREE, filterstr = '(%s=*)' % self._userIdAttr, sizelimit=1)) == 1:
|
if len(con.search_ext_s(base=self._ldapBase, scope=ldap.SCOPE_SUBTREE, filterstr='(%s=*)' % self._userIdAttr, sizelimit=1)) == 1:
|
||||||
raise Exception()
|
raise Exception()
|
||||||
return [False, _('Ldap user id attribute seems to be incorrect (no user found by that attribute)')]
|
return [False, _('Ldap user id attribute seems to be incorrect (no user found by that attribute)')]
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
@ -387,7 +393,7 @@ class SimpleLDAPAuthenticator(Authenticator):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if len(con.search_ext_s(base = self._ldapBase, scope = ldap.SCOPE_SUBTREE, filterstr = '(%s=*)' % self._groupIdAttr, sizelimit=1)) == 1:
|
if len(con.search_ext_s(base=self._ldapBase, scope=ldap.SCOPE_SUBTREE, filterstr='(%s=*)' % self._groupIdAttr, sizelimit=1)) == 1:
|
||||||
raise Exception()
|
raise Exception()
|
||||||
return [False, _('Ldap group id attribute seems to be incorrect (no group found by that attribute)')]
|
return [False, _('Ldap group id attribute seems to be incorrect (no group found by that attribute)')]
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
@ -396,7 +402,7 @@ class SimpleLDAPAuthenticator(Authenticator):
|
|||||||
|
|
||||||
# Now test objectclass and attribute of users
|
# Now test objectclass and attribute of users
|
||||||
try:
|
try:
|
||||||
if len(con.search_ext_s(base = self._ldapBase, scope = ldap.SCOPE_SUBTREE, filterstr = '(&(objectClass=%s)(%s=*))' % (self._userClass, self._userIdAttr), sizelimit=1)) == 1:
|
if len(con.search_ext_s(base=self._ldapBase, scope=ldap.SCOPE_SUBTREE, filterstr='(&(objectClass=%s)(%s=*))' % (self._userClass, self._userIdAttr), sizelimit=1)) == 1:
|
||||||
raise Exception()
|
raise Exception()
|
||||||
return [False, _('Ldap user class or user id attr is probably wrong (can\'t find any user with both conditions)')]
|
return [False, _('Ldap user class or user id attr is probably wrong (can\'t find any user with both conditions)')]
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
@ -405,12 +411,12 @@ class SimpleLDAPAuthenticator(Authenticator):
|
|||||||
|
|
||||||
# And group part, with membership
|
# And group part, with membership
|
||||||
try:
|
try:
|
||||||
res = con.search_ext_s(base = self._ldapBase, scope = ldap.SCOPE_SUBTREE, filterstr = '(&(objectClass=%s)(%s=*))' % (self._groupClass, self._groupIdAttr), attrlist = [self._memberAttr])
|
res = con.search_ext_s(base=self._ldapBase, scope=ldap.SCOPE_SUBTREE, filterstr='(&(objectClass=%s)(%s=*))' % (self._groupClass, self._groupIdAttr), attrlist=[self._memberAttr])
|
||||||
if len(res) == 0:
|
if len(res) == 0:
|
||||||
raise Exception(_('Ldap group class or group id attr is probably wrong (can\'t find any group with both conditions)'))
|
raise Exception(_('Ldap group class or group id attr is probably wrong (can\'t find any group with both conditions)'))
|
||||||
ok = False
|
ok = False
|
||||||
for r in res:
|
for r in res:
|
||||||
if r[1].has_key(self._memberAttr) is True:
|
if self._memberAttr in r[1]:
|
||||||
ok = True
|
ok = True
|
||||||
break
|
break
|
||||||
if ok is False:
|
if ok is False:
|
||||||
@ -418,7 +424,4 @@ class SimpleLDAPAuthenticator(Authenticator):
|
|||||||
except Exception, e:
|
except Exception, e:
|
||||||
return [False, str(e)]
|
return [False, str(e)]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return [True, _("Connection params seem correct, test was succesfully executed")]
|
return [True, _("Connection params seem correct, test was succesfully executed")]
|
||||||
|
|
@ -32,5 +32,8 @@
|
|||||||
|
|
||||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||||
'''
|
'''
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from Authenticator import SimpleLDAPAuthenticator
|
from Authenticator import SimpleLDAPAuthenticator
|
||||||
|
|
||||||
|
__updated__ = '2014-02-19'
|
||||||
|
@ -70,7 +70,7 @@ class Serializable(object):
|
|||||||
In that case, initialize the object with default values
|
In that case, initialize the object with default values
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
str _ : String readed from persistent storage to deseralilize
|
str_ _ : String readed from persistent storage to deseralilize
|
||||||
|
|
||||||
:note: This method must be overridden
|
:note: This method must be overridden
|
||||||
'''
|
'''
|
||||||
|
@ -30,6 +30,10 @@
|
|||||||
'''
|
'''
|
||||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||||
'''
|
'''
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
__updated__ = '2014-02-19'
|
||||||
|
|
||||||
|
|
||||||
class AuthsFactory(object):
|
class AuthsFactory(object):
|
||||||
'''
|
'''
|
||||||
@ -48,7 +52,7 @@ class AuthsFactory(object):
|
|||||||
'''
|
'''
|
||||||
Returns the factory that keeps the register of authentication providers.
|
Returns the factory that keeps the register of authentication providers.
|
||||||
'''
|
'''
|
||||||
if AuthsFactory._factory == None:
|
if AuthsFactory._factory == None:
|
||||||
AuthsFactory._factory = AuthsFactory()
|
AuthsFactory._factory = AuthsFactory()
|
||||||
return AuthsFactory._factory
|
return AuthsFactory._factory
|
||||||
|
|
||||||
|
@ -31,14 +31,19 @@ Base module for all authenticators
|
|||||||
|
|
||||||
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
|
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
|
||||||
'''
|
'''
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from uds.core import Module
|
from uds.core import Module
|
||||||
from django.utils.translation import ugettext_noop as _
|
from django.utils.translation import ugettext_noop as _
|
||||||
from GroupsManager import GroupsManager
|
from GroupsManager import GroupsManager
|
||||||
from Exceptions import InvalidUserException
|
from Exceptions import InvalidUserException
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
__updated__ = '2014-02-19'
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Authenticator(Module):
|
class Authenticator(Module):
|
||||||
'''
|
'''
|
||||||
This class represents the base interface to implement authenticators.
|
This class represents the base interface to implement authenticators.
|
||||||
@ -90,65 +95,64 @@ class Authenticator(Module):
|
|||||||
easier to understand.
|
easier to understand.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
#: Name of type, used at administration interface to identify this
|
# : Name of type, used at administration interface to identify this
|
||||||
#: authenticator (i.e. LDAP, SAML, ...)
|
# : authenticator (i.e. LDAP, SAML, ...)
|
||||||
#: This string will be translated when provided to admin interface
|
# : This string will be translated when provided to admin interface
|
||||||
#: using ugettext, so you can mark it as "_" at derived classes (using ugettext_noop)
|
# : using ugettext, so you can mark it as "_" at derived classes (using ugettext_noop)
|
||||||
#: if you want so it can be translated.
|
# : if you want so it can be translated.
|
||||||
typeName = _('Base Authenticator')
|
typeName = _('Base Authenticator')
|
||||||
|
|
||||||
#: Name of type used by Managers to identify this type of service
|
# : Name of type used by Managers to identify this type of service
|
||||||
#: We could have used here the Class name, but we decided that the
|
# : We could have used here the Class name, but we decided that the
|
||||||
#: module implementator will be the one that will provide a name that
|
# : module implementator will be the one that will provide a name that
|
||||||
#: will relation the class (type) and that name.
|
# : will relation the class (type) and that name.
|
||||||
typeType = 'BaseAuthenticator'
|
typeType = 'BaseAuthenticator'
|
||||||
|
|
||||||
#: Description shown at administration level for this authenticator.
|
# : Description shown at administration level for this authenticator.
|
||||||
#: This string will be translated when provided to admin interface
|
# : This string will be translated when provided to admin interface
|
||||||
#: using ugettext, so you can mark it as "_" at derived classes (using ugettext_noop)
|
# : using ugettext, so you can mark it as "_" at derived classes (using ugettext_noop)
|
||||||
#: if you want so it can be translated.
|
# : if you want so it can be translated.
|
||||||
typeDescription = _('Base Authenticator')
|
typeDescription = _('Base Authenticator')
|
||||||
|
|
||||||
|
# : Icon file, used to represent this authenticator at administration interface
|
||||||
#: Icon file, used to represent this authenticator at administration interface
|
# : This file should be at same folder as this class is, except if you provide
|
||||||
#: This file should be at same folder as this class is, except if you provide
|
# : your own :py:meth:uds.core.BaseModule.BaseModule.icon method.
|
||||||
#: your own :py:meth:uds.core.BaseModule.BaseModule.icon method.
|
|
||||||
iconFile = 'auth.png'
|
iconFile = 'auth.png'
|
||||||
|
|
||||||
#: Mark this authenticator as that the users comes from outside the UDS
|
# : Mark this authenticator as that the users comes from outside the UDS
|
||||||
#: database, that are most authenticator (except Internal DB)
|
# : database, that are most authenticator (except Internal DB)
|
||||||
#: So, isInternalSource means that "user is kept at database only"
|
# : So, isInternalSource means that "user is kept at database only"
|
||||||
isExternalSource = True
|
isExternalSource = True
|
||||||
|
|
||||||
#: If we need to enter the password for this user when creating a new
|
# : If we need to enter the password for this user when creating a new
|
||||||
#: user at administration interface. Used basically by internal authenticator.
|
# : user at administration interface. Used basically by internal authenticator.
|
||||||
needsPassword = False
|
needsPassword = False
|
||||||
|
|
||||||
#: Label for username field, shown at administration interface user form.
|
# : Label for username field, shown at administration interface user form.
|
||||||
userNameLabel = _('User name')
|
userNameLabel = _('User name')
|
||||||
|
|
||||||
#: Label for group field, shown at administration interface user form.
|
# : Label for group field, shown at administration interface user form.
|
||||||
groupNameLabel = _('Group name')
|
groupNameLabel = _('Group name')
|
||||||
|
|
||||||
#: Label for password field, , shown at administration interface user form.
|
# : Label for password field, , shown at administration interface user form.
|
||||||
#: Not needed for external authenticators (where credentials are stored with
|
# : Not needed for external authenticators (where credentials are stored with
|
||||||
#: an already existing user.
|
# : an already existing user.
|
||||||
passwordLabel = _('Password')
|
passwordLabel = _('Password')
|
||||||
|
|
||||||
#: If this authenticators casues a temporal block of an user on repeated login failures
|
# : If this authenticators casues a temporal block of an user on repeated login failures
|
||||||
blockUserOnLoginFailures = True
|
blockUserOnLoginFailures = True
|
||||||
|
|
||||||
from User import User
|
from User import User
|
||||||
from Group import Group
|
from Group import Group
|
||||||
|
|
||||||
#: The type of user provided, normally standard user will be enough.
|
# : The type of user provided, normally standard user will be enough.
|
||||||
#: This is here so if we need it in some case, we can write our own
|
# : This is here so if we need it in some case, we can write our own
|
||||||
#: user class
|
# : user class
|
||||||
userType = User
|
userType = User
|
||||||
|
|
||||||
#: The type of group provided, normally standard group will be enough
|
# : The type of group provided, normally standard group will be enough
|
||||||
#: This is here so if we need it in some case, we can write our own
|
# : This is here so if we need it in some case, we can write our own
|
||||||
#: group class
|
# : group class
|
||||||
groupType = Group
|
groupType = Group
|
||||||
|
|
||||||
def __init__(self, dbAuth, environment, values):
|
def __init__(self, dbAuth, environment, values):
|
||||||
@ -196,7 +200,7 @@ class Authenticator(Module):
|
|||||||
if self.isExternalSource == True:
|
if self.isExternalSource == True:
|
||||||
groupsManager = GroupsManager(self._dbAuth)
|
groupsManager = GroupsManager(self._dbAuth)
|
||||||
self.getGroups(user.name, groupsManager)
|
self.getGroups(user.name, groupsManager)
|
||||||
user.groups = [ g.dbGroup() for g in groupsManager.getValidGroups()]
|
user.groups = [g.dbGroup() for g in groupsManager.getValidGroups()]
|
||||||
|
|
||||||
def callbackUrl(self):
|
def callbackUrl(self):
|
||||||
'''
|
'''
|
||||||
@ -320,7 +324,7 @@ class Authenticator(Module):
|
|||||||
'''
|
'''
|
||||||
return username
|
return username
|
||||||
|
|
||||||
def internalAuthenticate(self,username, credentials, groupsManager):
|
def internalAuthenticate(self, username, credentials, groupsManager):
|
||||||
'''
|
'''
|
||||||
This method is provided so "plugins" (For example, a custom dispatcher), can test
|
This method is provided so "plugins" (For example, a custom dispatcher), can test
|
||||||
the username/credentials in an alternative way.
|
the username/credentials in an alternative way.
|
||||||
@ -535,7 +539,6 @@ class Authenticator(Module):
|
|||||||
'''
|
'''
|
||||||
raise InvalidUserException(_('Users can\'t be created inside this authenticator'))
|
raise InvalidUserException(_('Users can\'t be created inside this authenticator'))
|
||||||
|
|
||||||
|
|
||||||
def modifyUser(self, usrData):
|
def modifyUser(self, usrData):
|
||||||
'''
|
'''
|
||||||
This method is used when modifying an user to allow the authenticator:
|
This method is used when modifying an user to allow the authenticator:
|
||||||
@ -563,7 +566,6 @@ class Authenticator(Module):
|
|||||||
'''
|
'''
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def createGroup(self, groupData):
|
def createGroup(self, groupData):
|
||||||
'''
|
'''
|
||||||
This method is used when creating a new group to allow the authenticator:
|
This method is used when creating a new group to allow the authenticator:
|
||||||
@ -617,7 +619,6 @@ class Authenticator(Module):
|
|||||||
'''
|
'''
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def removeUser(self, username):
|
def removeUser(self, username):
|
||||||
'''
|
'''
|
||||||
Remove user is used whenever from the administration interface, or from other
|
Remove user is used whenever from the administration interface, or from other
|
||||||
|
@ -30,6 +30,10 @@
|
|||||||
'''
|
'''
|
||||||
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
|
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
|
||||||
'''
|
'''
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
__updated__ = '2014-02-19'
|
||||||
|
|
||||||
|
|
||||||
class AuthenticatorException(Exception):
|
class AuthenticatorException(Exception):
|
||||||
'''
|
'''
|
||||||
@ -37,26 +41,29 @@ class AuthenticatorException(Exception):
|
|||||||
'''
|
'''
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class InvalidUserException(AuthenticatorException):
|
class InvalidUserException(AuthenticatorException):
|
||||||
'''
|
'''
|
||||||
Invalid user specified. The user cant access the requested service
|
Invalid user specified. The user cant access the requested service
|
||||||
'''
|
'''
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class InvalidAuthenticatorException(AuthenticatorException):
|
class InvalidAuthenticatorException(AuthenticatorException):
|
||||||
'''
|
'''
|
||||||
Invalida authenticator has been specified
|
Invalida authenticator has been specified
|
||||||
'''
|
'''
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Redirect(Exception):
|
class Redirect(Exception):
|
||||||
'''
|
'''
|
||||||
This exception indicates that a redirect is required.
|
This exception indicates that a redirect is required.
|
||||||
Used in authUrlCallback to indicate that redirect is needed
|
Used in authUrlCallback to indicate that redirect is needed
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
class Logout(Exception):
|
class Logout(Exception):
|
||||||
'''
|
'''
|
||||||
This exceptions redirects logouts an user and redirects to an url
|
This exceptions redirects logouts an user and redirects to an url
|
||||||
'''
|
'''
|
||||||
|
|
@ -31,11 +31,15 @@
|
|||||||
'''
|
'''
|
||||||
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
|
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
|
||||||
'''
|
'''
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
__updated__ = '2014-02-19'
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Group(object):
|
class Group(object):
|
||||||
'''
|
'''
|
||||||
A group is simply a database group associated with its authenticator instance
|
A group is simply a database group associated with its authenticator instance
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
'''
|
'''
|
||||||
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
|
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
|
||||||
'''
|
'''
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from uds.core.util.State import State
|
from uds.core.util.State import State
|
||||||
from uds.models import Group as dbGroup
|
from uds.models import Group as dbGroup
|
||||||
@ -37,8 +38,11 @@ from Group import Group
|
|||||||
import inspect
|
import inspect
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
__updated__ = '2014-02-19'
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class GroupsManager(object):
|
class GroupsManager(object):
|
||||||
'''
|
'''
|
||||||
Manages registered groups for an specific authenticator.
|
Manages registered groups for an specific authenticator.
|
||||||
@ -67,14 +71,14 @@ class GroupsManager(object):
|
|||||||
'''
|
'''
|
||||||
self._dbAuthenticator = dbAuthenticator
|
self._dbAuthenticator = dbAuthenticator
|
||||||
self._groups = {} # We just get active groups, inactive aren't visible to this class
|
self._groups = {} # We just get active groups, inactive aren't visible to this class
|
||||||
for g in dbAuthenticator.groups.filter(state = State.ACTIVE, is_meta = False):
|
for g in dbAuthenticator.groups.filter(state=State.ACTIVE, is_meta=False):
|
||||||
self._groups[g.name.lower()] = { 'group': Group(g), 'valid': False }
|
self._groups[g.name.lower()] = {'group': Group(g), 'valid': False}
|
||||||
|
|
||||||
def contains(self, groupName):
|
def contains(self, groupName):
|
||||||
'''
|
'''
|
||||||
Returns true if this groups manager contains the specified group name (string)
|
Returns true if this groups manager contains the specified group name (string)
|
||||||
'''
|
'''
|
||||||
return self._groups.has_key(groupName.lower())
|
return groupName.lower() in self._groups
|
||||||
|
|
||||||
def getGroupsNames(self):
|
def getGroupsNames(self):
|
||||||
'''
|
'''
|
||||||
@ -96,11 +100,10 @@ class GroupsManager(object):
|
|||||||
# Now, get metagroups and also return them
|
# Now, get metagroups and also return them
|
||||||
for g in dbGroup.objects.filter(manager__id=self._dbAuthenticator.id, is_meta=True):
|
for g in dbGroup.objects.filter(manager__id=self._dbAuthenticator.id, is_meta=True):
|
||||||
gn = g.groups.filter(id__in=lst, state=State.ACTIVE).count()
|
gn = g.groups.filter(id__in=lst, state=State.ACTIVE).count()
|
||||||
if gn == g.groups.count(): # If a meta group is empty, all users belongs to it. we can use gn != 0 to check that if it is empty, is not valid
|
if gn == g.groups.count(): # If a meta group is empty, all users belongs to it. we can use gn != 0 to check that if it is empty, is not valid
|
||||||
# This group matches
|
# This group matches
|
||||||
yield Group(g)
|
yield Group(g)
|
||||||
|
|
||||||
|
|
||||||
def hasValidGroups(self):
|
def hasValidGroups(self):
|
||||||
'''
|
'''
|
||||||
Checks if this groups manager has at least one group that has been
|
Checks if this groups manager has at least one group that has been
|
||||||
@ -116,7 +119,7 @@ class GroupsManager(object):
|
|||||||
If this groups manager contains that group manager, it returns the
|
If this groups manager contains that group manager, it returns the
|
||||||
:py:class:uds.core.auths.Group.Group representing that group name.
|
:py:class:uds.core.auths.Group.Group representing that group name.
|
||||||
'''
|
'''
|
||||||
if self._groups.has_key(groupName.lower()):
|
if groupName.lower() in self._groups:
|
||||||
return self._groups[groupName.lower()]['group']
|
return self._groups[groupName.lower()]['group']
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
@ -137,7 +140,7 @@ class GroupsManager(object):
|
|||||||
for n in groupName:
|
for n in groupName:
|
||||||
self.validate(n)
|
self.validate(n)
|
||||||
else:
|
else:
|
||||||
if self._groups.has_key(groupName.lower()):
|
if groupName.lower() in self._groups:
|
||||||
self._groups[groupName.lower()]['valid'] = True
|
self._groups[groupName.lower()]['valid'] = True
|
||||||
|
|
||||||
def isValid(self, groupName):
|
def isValid(self, groupName):
|
||||||
@ -145,11 +148,9 @@ class GroupsManager(object):
|
|||||||
Checks if this group name is marked as valid inside this groups manager.
|
Checks if this group name is marked as valid inside this groups manager.
|
||||||
Returns True if group name is marked as valid, False if it isn't.
|
Returns True if group name is marked as valid, False if it isn't.
|
||||||
'''
|
'''
|
||||||
if self._groups.has_key(groupName.lower()):
|
if groupName.lower() in self._groups:
|
||||||
return self._groups[groupName.lower()]['valid']
|
return self._groups[groupName.lower()]['valid']
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Groupsmanager: {0}".format(self._groups)
|
return "Groupsmanager: {0}".format(self._groups)
|
||||||
|
|
||||||
|
|
||||||
|
@ -30,11 +30,15 @@
|
|||||||
'''
|
'''
|
||||||
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
|
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
|
||||||
'''
|
'''
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
__updated__ = '2014-02-19'
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class User(object):
|
class User(object):
|
||||||
'''
|
'''
|
||||||
An user represents a database user, associated with its authenticator (instance)
|
An user represents a database user, associated with its authenticator (instance)
|
||||||
@ -47,7 +51,6 @@ class User(object):
|
|||||||
self._dbUser = dbUser
|
self._dbUser = dbUser
|
||||||
self._groups = None
|
self._groups = None
|
||||||
|
|
||||||
|
|
||||||
def _groupsManager(self):
|
def _groupsManager(self):
|
||||||
'''
|
'''
|
||||||
If the groups manager for this user already exists, it returns this.
|
If the groups manager for this user already exists, it returns this.
|
||||||
@ -89,7 +92,6 @@ class User(object):
|
|||||||
self._groups = [Group(g) for g in usr.getGroups()]
|
self._groups = [Group(g) for g in usr.getGroups()]
|
||||||
return self._groups
|
return self._groups
|
||||||
|
|
||||||
|
|
||||||
def manager(self):
|
def manager(self):
|
||||||
'''
|
'''
|
||||||
Returns the authenticator instance
|
Returns the authenticator instance
|
||||||
@ -101,4 +103,3 @@ class User(object):
|
|||||||
Returns the database user
|
Returns the database user
|
||||||
'''
|
'''
|
||||||
return self._dbUser
|
return self._dbUser
|
||||||
|
|
||||||
|
@ -32,17 +32,20 @@ UDS authentication related interfaces and classes
|
|||||||
|
|
||||||
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
|
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
|
||||||
'''
|
'''
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from BaseAuthenticator import Authenticator
|
from BaseAuthenticator import Authenticator
|
||||||
from User import User
|
from User import User
|
||||||
from Group import Group
|
from Group import Group
|
||||||
from GroupsManager import GroupsManager
|
from GroupsManager import GroupsManager
|
||||||
import Exceptions
|
import Exceptions
|
||||||
|
|
||||||
|
__updated__ = '2014-02-19'
|
||||||
|
|
||||||
|
|
||||||
def factory():
|
def factory():
|
||||||
'''
|
'''
|
||||||
Returns factory for register/access to authenticators
|
Returns factory for register/access to authenticators
|
||||||
'''
|
'''
|
||||||
from AuthsFactory import AuthsFactory
|
from AuthsFactory import AuthsFactory
|
||||||
return AuthsFactory.factory()
|
return AuthsFactory.factory()
|
||||||
|
|
||||||
|
|
||||||
|
@ -49,23 +49,27 @@ from uds.models import User
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
__updated__ = '2014-02-19'
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
authLogger = logging.getLogger('authLog')
|
authLogger = logging.getLogger('authLog')
|
||||||
|
|
||||||
USER_KEY = 'uk'
|
USER_KEY = 'uk'
|
||||||
PASS_KEY = 'pk'
|
PASS_KEY = 'pk'
|
||||||
ROOT_ID = -20091204 # Any negative number will do the trick
|
ROOT_ID = -20091204 # Any negative number will do the trick
|
||||||
|
|
||||||
|
|
||||||
def getRootUser():
|
def getRootUser():
|
||||||
from uds.models import Authenticator
|
from uds.models import Authenticator
|
||||||
u = User(id=ROOT_ID, name=GlobalConfig.SUPER_USER_LOGIN.get(True), real_name=_('System Administrator'), state= State.ACTIVE, staff_member = True, is_admin = True )
|
u = User(id=ROOT_ID, name=GlobalConfig.SUPER_USER_LOGIN.get(True), real_name=_('System Administrator'), state=State.ACTIVE, staff_member=True, is_admin=True)
|
||||||
u.manager = Authenticator()
|
u.manager = Authenticator()
|
||||||
u.getGroups = lambda: []
|
u.getGroups = lambda: []
|
||||||
u.updateLastAccess = lambda: None
|
u.updateLastAccess = lambda: None
|
||||||
u.logout = lambda: None
|
u.logout = lambda: None
|
||||||
return u
|
return u
|
||||||
|
|
||||||
def getIp(request, translateProxy = True):
|
|
||||||
|
def getIp(request, translateProxy=True):
|
||||||
'''
|
'''
|
||||||
Obtains the IP of a Django Request, even behind a proxy
|
Obtains the IP of a Django Request, even behind a proxy
|
||||||
|
|
||||||
@ -73,12 +77,13 @@ def getIp(request, translateProxy = True):
|
|||||||
'''
|
'''
|
||||||
try:
|
try:
|
||||||
if translateProxy is False:
|
if translateProxy is False:
|
||||||
raise KeyError() # Do not allow HTTP_X_FORWARDED_FOR
|
raise KeyError() # Do not allow HTTP_X_FORWARDED_FOR
|
||||||
request.ip = request.META['HTTP_X_FORWARDED_FOR'].split(",")[0]
|
request.ip = request.META['HTTP_X_FORWARDED_FOR'].split(",")[0]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
request.ip = request.META['REMOTE_ADDR']
|
request.ip = request.META['REMOTE_ADDR']
|
||||||
return request.ip
|
return request.ip
|
||||||
|
|
||||||
|
|
||||||
# Decorator to make easier protect pages that needs to be logged in
|
# Decorator to make easier protect pages that needs to be logged in
|
||||||
def webLoginRequired(view_func):
|
def webLoginRequired(view_func):
|
||||||
'''
|
'''
|
||||||
@ -106,12 +111,13 @@ def webLoginRequired(view_func):
|
|||||||
logger.debug('No user found, redirecting to {0}'.format(url))
|
logger.debug('No user found, redirecting to {0}'.format(url))
|
||||||
return HttpResponseRedirect(url)
|
return HttpResponseRedirect(url)
|
||||||
# Refresh session duration
|
# Refresh session duration
|
||||||
#request.session.set_expiry(GlobalConfig.USER_SESSION_LENGTH.getInt())
|
# request.session.set_expiry(GlobalConfig.USER_SESSION_LENGTH.getInt())
|
||||||
request.user = user
|
request.user = user
|
||||||
getIp(request)
|
getIp(request)
|
||||||
return view_func(request, *args, **kwargs)
|
return view_func(request, *args, **kwargs)
|
||||||
return _wrapped_view
|
return _wrapped_view
|
||||||
|
|
||||||
|
|
||||||
# Decorator to protect pages that needs to be accessed from "trusted sites"
|
# Decorator to protect pages that needs to be accessed from "trusted sites"
|
||||||
def trustedSourceRequired(view_func):
|
def trustedSourceRequired(view_func):
|
||||||
'''
|
'''
|
||||||
@ -149,7 +155,7 @@ def __registerUser(authenticator, authInstance, username):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def authenticate(username, password, authenticator, useInternalAuthenticate = False):
|
def authenticate(username, password, authenticator, useInternalAuthenticate=False):
|
||||||
'''
|
'''
|
||||||
Given an username, password and authenticator, try to authenticate user
|
Given an username, password and authenticator, try to authenticate user
|
||||||
@param username: username to authenticate
|
@param username: username to authenticate
|
||||||
@ -217,6 +223,7 @@ def authenticateViaCallback(authenticator, params):
|
|||||||
|
|
||||||
return __registerUser(authenticator, authInstance, username)
|
return __registerUser(authenticator, authInstance, username)
|
||||||
|
|
||||||
|
|
||||||
def authCallbackUrl(authenticator):
|
def authCallbackUrl(authenticator):
|
||||||
'''
|
'''
|
||||||
Helper method, so we can get the auth call back url for an authenticator
|
Helper method, so we can get the auth call back url for an authenticator
|
||||||
@ -224,18 +231,20 @@ def authCallbackUrl(authenticator):
|
|||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
return reverse('uds.web.views.authCallback', kwargs={'authName': authenticator.name})
|
return reverse('uds.web.views.authCallback', kwargs={'authName': authenticator.name})
|
||||||
|
|
||||||
|
|
||||||
def authInfoUrl(authenticator):
|
def authInfoUrl(authenticator):
|
||||||
'''
|
'''
|
||||||
Helper method, so we can get the info url for an authenticator
|
Helper method, so we can get the info url for an authenticator
|
||||||
'''
|
'''
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
if isinstance(authenticator,unicode) or isinstance(authenticator, str):
|
if isinstance(authenticator, unicode) or isinstance(authenticator, str):
|
||||||
name = authenticator
|
name = authenticator
|
||||||
else:
|
else:
|
||||||
name = authenticator.name
|
name = authenticator.name
|
||||||
|
|
||||||
return reverse('uds.web.views.authInfo', kwargs={'authName': name})
|
return reverse('uds.web.views.authInfo', kwargs={'authName': name})
|
||||||
|
|
||||||
|
|
||||||
def webLogin(request, response, user, password):
|
def webLogin(request, response, user, password):
|
||||||
'''
|
'''
|
||||||
Helper function to, once the user is authenticated, store the information at the user session.
|
Helper function to, once the user is authenticated, store the information at the user session.
|
||||||
@ -243,7 +252,7 @@ def webLogin(request, response, user, password):
|
|||||||
'''
|
'''
|
||||||
from uds import REST
|
from uds import REST
|
||||||
|
|
||||||
if user.id != ROOT_ID: # If not ROOT user (this user is not inside any authenticator)
|
if user.id != ROOT_ID: # If not ROOT user (this user is not inside any authenticator)
|
||||||
manager_id = user.manager.id
|
manager_id = user.manager.id
|
||||||
else:
|
else:
|
||||||
manager_id = -1
|
manager_id = -1
|
||||||
@ -266,7 +275,8 @@ def webPassword(request):
|
|||||||
'''
|
'''
|
||||||
return CryptoManager.manager().xor(request.session.get(PASS_KEY), request.COOKIES['uds']).decode('utf-8')
|
return CryptoManager.manager().xor(request.session.get(PASS_KEY), request.COOKIES['uds']).decode('utf-8')
|
||||||
|
|
||||||
def webLogout(request, exit_url = None):
|
|
||||||
|
def webLogout(request, exit_url=None):
|
||||||
'''
|
'''
|
||||||
Helper function to clear user related data from session. If this method is not used, the session we be cleaned anyway
|
Helper function to clear user related data from session. If this method is not used, the session we be cleaned anyway
|
||||||
by django in regular basis.
|
by django in regular basis.
|
||||||
@ -278,7 +288,8 @@ def webLogout(request, exit_url = None):
|
|||||||
# Try to delete session
|
# Try to delete session
|
||||||
return HttpResponseRedirect(request.build_absolute_uri(exit_url))
|
return HttpResponseRedirect(request.build_absolute_uri(exit_url))
|
||||||
|
|
||||||
def authLogLogin(request, authenticator, userName, java, os, logStr = ''):
|
|
||||||
|
def authLogLogin(request, authenticator, userName, java, os, logStr=''):
|
||||||
'''
|
'''
|
||||||
Logs authentication
|
Logs authentication
|
||||||
'''
|
'''
|
||||||
@ -303,4 +314,3 @@ def authLogLogin(request, authenticator, userName, java, os, logStr = ''):
|
|||||||
def authLogLogout(request):
|
def authLogLogout(request):
|
||||||
log.doLog(request.user.manager, log.INFO, 'user {0} has logged out from {1}'.format(request.user.name, request.ip), log.WEB)
|
log.doLog(request.user.manager, log.INFO, 'user {0} has logged out from {1}'.format(request.user.name, request.ip), log.WEB)
|
||||||
log.doLog(request.user, log.INFO, 'has logged out from {0}'.format(request.ip), log.WEB)
|
log.doLog(request.user, log.INFO, 'has logged out from {0}'.format(request.ip), log.WEB)
|
||||||
|
|
||||||
|
@ -7,8 +7,11 @@ Author:
|
|||||||
from django.db import models, connection
|
from django.db import models, connection
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
__updated__ = '2014-02-19'
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
# Table locking in mysql at least is based on thread requesting it, so we should not have problems with a bit of care
|
# Table locking in mysql at least is based on thread requesting it, so we should not have problems with a bit of care
|
||||||
class LockingManager(models.Manager):
|
class LockingManager(models.Manager):
|
||||||
""" Add lock/unlock functionality to manager.
|
""" Add lock/unlock functionality to manager.
|
||||||
@ -57,17 +60,17 @@ class LockingManager(models.Manager):
|
|||||||
con = connection
|
con = connection
|
||||||
cursor = con.cursor()
|
cursor = con.cursor()
|
||||||
table = self.model._meta.db_table
|
table = self.model._meta.db_table
|
||||||
#logger.debug("Locking table %s" % table)
|
# logger.debug("Locking table %s" % table)
|
||||||
cursor.execute("LOCK TABLES %s WRITE" % table)
|
cursor.execute("LOCK TABLES %s WRITE" % table)
|
||||||
row = cursor.fetchone()
|
row = cursor.fetchone()
|
||||||
return row
|
return row
|
||||||
|
|
||||||
def unlock(self):
|
def unlock(self):
|
||||||
""" Unlock the table. """
|
""" Unlock the table. """
|
||||||
#logger.debug("Unlocked tables")
|
# logger.debug("Unlocked tables")
|
||||||
con = connection
|
con = connection
|
||||||
cursor = con.cursor()
|
cursor = con.cursor()
|
||||||
#table = self.model._meta.db_table
|
# table = self.model._meta.db_table
|
||||||
cursor.execute("UNLOCK TABLES")
|
cursor.execute("UNLOCK TABLES")
|
||||||
row = cursor.fetchone()
|
row = cursor.fetchone()
|
||||||
return row
|
return row
|
@ -30,3 +30,4 @@
|
|||||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
__updated__ = '2014-02-19'
|
||||||
|
@ -30,13 +30,16 @@
|
|||||||
'''
|
'''
|
||||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||||
'''
|
'''
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from uds.core.Environment import Environmentable
|
from uds.core.Environment import Environmentable
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
__updated__ = '2014-02-19'
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class DelayedTask(Environmentable):
|
class DelayedTask(Environmentable):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
'''
|
'''
|
||||||
|
@ -35,19 +35,22 @@ from __future__ import unicode_literals
|
|||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from uds.models import DelayedTask as dbDelayedTask, getSqlDatetime
|
from uds.models import DelayedTask as dbDelayedTask, getSqlDatetime
|
||||||
from uds.core.util.Decorators import retryOnException
|
|
||||||
from ..Environment import Environment
|
from ..Environment import Environment
|
||||||
from socket import gethostname
|
from socket import gethostname
|
||||||
from pickle import loads, dumps
|
from pickle import loads, dumps
|
||||||
from datetime import datetime, timedelta
|
from datetime import timedelta
|
||||||
import threading, time
|
import threading
|
||||||
|
import time
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
__updated__ = '2014-02-19'
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class DelayedTaskThread(threading.Thread):
|
class DelayedTaskThread(threading.Thread):
|
||||||
def __init__(self, taskInstance):
|
def __init__(self, taskInstance):
|
||||||
super(DelayedTaskThread,self).__init__()
|
super(DelayedTaskThread, self).__init__()
|
||||||
self._taskInstance = taskInstance
|
self._taskInstance = taskInstance
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
@ -56,6 +59,7 @@ class DelayedTaskThread(threading.Thread):
|
|||||||
except Exception, e:
|
except Exception, e:
|
||||||
logger.debug("Exception in thread {0}: {1}".format(e.__class__, e))
|
logger.debug("Exception in thread {0}: {1}".format(e.__class__, e))
|
||||||
|
|
||||||
|
|
||||||
class DelayedTaskRunner(object):
|
class DelayedTaskRunner(object):
|
||||||
CODEC = 'base64' # Can be zip, hez, bzip, base64, uuencoded
|
CODEC = 'base64' # Can be zip, hez, bzip, base64, uuencoded
|
||||||
# How often tasks r checked
|
# How often tasks r checked
|
||||||
@ -74,7 +78,7 @@ class DelayedTaskRunner(object):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def runner():
|
def runner():
|
||||||
if DelayedTaskRunner._runner == None:
|
if DelayedTaskRunner._runner == None:
|
||||||
DelayedTaskRunner._runner = DelayedTaskRunner()
|
DelayedTaskRunner._runner = DelayedTaskRunner()
|
||||||
return DelayedTaskRunner._runner
|
return DelayedTaskRunner._runner
|
||||||
|
|
||||||
@ -84,7 +88,7 @@ class DelayedTaskRunner(object):
|
|||||||
# If next execution is before now or last execution is in the future (clock changed on this server, we take that task as executable)
|
# If next execution is before now or last execution is in the future (clock changed on this server, we take that task as executable)
|
||||||
taskInstance = None
|
taskInstance = None
|
||||||
try:
|
try:
|
||||||
with transaction.atomic(): # Encloses
|
with transaction.atomic(): # Encloses
|
||||||
task = dbDelayedTask.objects.select_for_update().filter(filt).order_by('execution_time')[0]
|
task = dbDelayedTask.objects.select_for_update().filter(filt).order_by('execution_time')[0]
|
||||||
task.delete()
|
task.delete()
|
||||||
taskInstance = loads(task.instance.decode(self.CODEC))
|
taskInstance = loads(task.instance.decode(self.CODEC))
|
||||||
@ -100,17 +104,17 @@ class DelayedTaskRunner(object):
|
|||||||
|
|
||||||
def __insert(self, instance, delay, tag):
|
def __insert(self, instance, delay, tag):
|
||||||
now = getSqlDatetime()
|
now = getSqlDatetime()
|
||||||
exec_time = now + timedelta(seconds = delay)
|
exec_time = now + timedelta(seconds=delay)
|
||||||
cls = instance.__class__
|
cls = instance.__class__
|
||||||
instanceDump = dumps(instance).encode(self.CODEC)
|
instanceDump = dumps(instance).encode(self.CODEC)
|
||||||
typeName = str(cls.__module__ + '.' + cls.__name__)
|
typeName = str(cls.__module__ + '.' + cls.__name__)
|
||||||
|
|
||||||
logger.debug('Inserting delayed task {0} with {1} bytes ({2})'.format(typeName, len(instanceDump), exec_time))
|
logger.debug('Inserting delayed task {0} with {1} bytes ({2})'.format(typeName, len(instanceDump), exec_time))
|
||||||
|
|
||||||
dbDelayedTask.objects.create(type = typeName, instance = instanceDump,
|
dbDelayedTask.objects.create(type=typeName, instance=instanceDump,
|
||||||
insert_date = now, execution_delay = delay, execution_time = exec_time, tag = tag)
|
insert_date=now, execution_delay=delay, execution_time=exec_time, tag=tag)
|
||||||
|
|
||||||
def insert(self, instance, delay, tag = ''):
|
def insert(self, instance, delay, tag=''):
|
||||||
retries = 3
|
retries = 3
|
||||||
while retries > 0:
|
while retries > 0:
|
||||||
retries -= 1
|
retries -= 1
|
||||||
@ -119,7 +123,7 @@ class DelayedTaskRunner(object):
|
|||||||
break
|
break
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
logger.info('Exception inserting a delayed task {0}: {1}'.format(str(e.__class__), e))
|
logger.info('Exception inserting a delayed task {0}: {1}'.format(str(e.__class__), e))
|
||||||
time.sleep(1) # Wait a bit before next try...
|
time.sleep(1) # Wait a bit before next try...
|
||||||
# If retries == 0, this is a big error
|
# If retries == 0, this is a big error
|
||||||
if retries == 0:
|
if retries == 0:
|
||||||
logger.error("Could not insert delayed task!!!! {0} {1} {2}".format(instance, delay, tag))
|
logger.error("Could not insert delayed task!!!! {0} {1} {2}".format(instance, delay, tag))
|
||||||
@ -141,7 +145,7 @@ class DelayedTaskRunner(object):
|
|||||||
number = 0
|
number = 0
|
||||||
try:
|
try:
|
||||||
number = dbDelayedTask.objects.filter(tag=tag).count()
|
number = dbDelayedTask.objects.filter(tag=tag).count()
|
||||||
except Exception as e:
|
except Exception:
|
||||||
logger.error('Exception looking for a delayed task tag {0}'.format(tag))
|
logger.error('Exception looking for a delayed task tag {0}'.format(tag))
|
||||||
return number > 0
|
return number > 0
|
||||||
|
|
||||||
|
@ -30,16 +30,20 @@
|
|||||||
'''
|
'''
|
||||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||||
'''
|
'''
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from uds.core import Environmentable
|
from uds.core import Environmentable
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
__updated__ = '2014-02-19'
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Job(Environmentable):
|
class Job(Environmentable):
|
||||||
# Default frecuency, once a day. Remenber that precision will be based on "granurality" of Scheduler
|
# Default frecuency, once a day. Remenber that precision will be based on "granurality" of Scheduler
|
||||||
# If a job is used for delayed execution, this attribute is in fact ignored
|
# If a job is used for delayed execution, this attribute is in fact ignored
|
||||||
frecuency = 24*3600+3
|
frecuency = 24 * 3600 + 3
|
||||||
friendly_name = 'Unknown'
|
friendly_name = 'Unknown'
|
||||||
|
|
||||||
def __init__(self, environment):
|
def __init__(self, environment):
|
||||||
@ -51,7 +55,7 @@ class Job(Environmentable):
|
|||||||
def execute(self):
|
def execute(self):
|
||||||
try:
|
try:
|
||||||
self.run()
|
self.run()
|
||||||
except Exception, e:
|
except Exception:
|
||||||
logger.exception('Job {0} raised an exception:'.format(self.__class__))
|
logger.exception('Job {0} raised an exception:'.format(self.__class__))
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
@ -30,11 +30,16 @@
|
|||||||
'''
|
'''
|
||||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||||
'''
|
'''
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
__updated__ = '2014-02-19'
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class JobsFactory(object):
|
class JobsFactory(object):
|
||||||
_factory = None
|
_factory = None
|
||||||
|
|
||||||
@ -43,7 +48,7 @@ class JobsFactory(object):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def factory():
|
def factory():
|
||||||
if JobsFactory._factory == None:
|
if JobsFactory._factory == None:
|
||||||
JobsFactory._factory = JobsFactory()
|
JobsFactory._factory = JobsFactory()
|
||||||
return JobsFactory._factory
|
return JobsFactory._factory
|
||||||
|
|
||||||
@ -68,18 +73,17 @@ class JobsFactory(object):
|
|||||||
# We use database server datetime
|
# We use database server datetime
|
||||||
now = getSqlDatetime()
|
now = getSqlDatetime()
|
||||||
next_ = now
|
next_ = now
|
||||||
job = Scheduler.objects.create(name = name, frecuency = type_.frecuency, last_execution = now, next_execution = next_, state = State.FOR_EXECUTE)
|
job = Scheduler.objects.create(name=name, frecuency=type_.frecuency, last_execution=now, next_execution=next_, state=State.FOR_EXECUTE)
|
||||||
except Exception: # already exists
|
except Exception: # already exists
|
||||||
logger.debug('Already added {0}'.format(name))
|
logger.debug('Already added {0}'.format(name))
|
||||||
job = Scheduler.objects.get(name=name)
|
job = Scheduler.objects.get(name=name)
|
||||||
job.frecuency = type_.frecuency
|
job.frecuency = type_.frecuency
|
||||||
if job.next_execution > job.last_execution + datetime.timedelta(seconds = type_.frecuency):
|
if job.next_execution > job.last_execution + datetime.timedelta(seconds=type_.frecuency):
|
||||||
job.next_execution = job.last_execution + datetime.timedelta(seconds = type_.frecuency);
|
job.next_execution = job.last_execution + datetime.timedelta(seconds=type_.frecuency)
|
||||||
job.save()
|
job.save()
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
logger.debug('Exception at ensureJobsInDatabase in JobsFactory: {0}, {1}'.format(e.__class__, e))
|
logger.debug('Exception at ensureJobsInDatabase in JobsFactory: {0}, {1}'.format(e.__class__, e))
|
||||||
|
|
||||||
|
|
||||||
def lookup(self, typeName):
|
def lookup(self, typeName):
|
||||||
try:
|
try:
|
||||||
return self._jobs[typeName]
|
return self._jobs[typeName]
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
'''
|
'''
|
||||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||||
'''
|
'''
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.db import transaction, DatabaseError
|
from django.db import transaction, DatabaseError
|
||||||
@ -37,14 +38,18 @@ from uds.models import Scheduler as dbScheduler, getSqlDatetime, State
|
|||||||
from uds.core.jobs.JobsFactory import JobsFactory
|
from uds.core.jobs.JobsFactory import JobsFactory
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from socket import gethostname
|
from socket import gethostname
|
||||||
import threading, time
|
import threading
|
||||||
|
import time
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
__updated__ = '2014-02-19'
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class JobThread(threading.Thread):
|
class JobThread(threading.Thread):
|
||||||
def __init__(self, jobInstance, dbJob):
|
def __init__(self, jobInstance, dbJob):
|
||||||
super(JobThread,self).__init__()
|
super(JobThread, self).__init__()
|
||||||
self._jobInstance = jobInstance
|
self._jobInstance = jobInstance
|
||||||
self._dbJobId = dbJob.id
|
self._dbJobId = dbJob.id
|
||||||
|
|
||||||
@ -66,18 +71,18 @@ class JobThread(threading.Thread):
|
|||||||
logger.info('Database access locked... Retrying')
|
logger.info('Database access locked... Retrying')
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def __updateDb(self):
|
def __updateDb(self):
|
||||||
job = dbScheduler.objects.select_for_update().get(id=self._dbJobId)
|
job = dbScheduler.objects.select_for_update().get(id=self._dbJobId)
|
||||||
job.state = State.FOR_EXECUTE
|
job.state = State.FOR_EXECUTE
|
||||||
job.owner_server = ''
|
job.owner_server = ''
|
||||||
job.next_execution = getSqlDatetime() + timedelta(seconds = job.frecuency)
|
job.next_execution = getSqlDatetime() + timedelta(seconds=job.frecuency)
|
||||||
# Update state and last execution time at database
|
# Update state and last execution time at database
|
||||||
job.save()
|
job.save()
|
||||||
|
|
||||||
|
|
||||||
class Scheduler(object):
|
class Scheduler(object):
|
||||||
granularity = 2 # We check for cron jobs every THIS seconds
|
granularity = 2 # We check for cron jobs every THIS seconds
|
||||||
|
|
||||||
# to keep singleton Scheduler
|
# to keep singleton Scheduler
|
||||||
_scheduler = None
|
_scheduler = None
|
||||||
@ -88,7 +93,7 @@ class Scheduler(object):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def scheduler():
|
def scheduler():
|
||||||
if Scheduler._scheduler == None:
|
if Scheduler._scheduler == None:
|
||||||
Scheduler._scheduler = Scheduler()
|
Scheduler._scheduler = Scheduler()
|
||||||
return Scheduler._scheduler
|
return Scheduler._scheduler
|
||||||
|
|
||||||
@ -101,12 +106,12 @@ class Scheduler(object):
|
|||||||
'''
|
'''
|
||||||
jobInstance = None
|
jobInstance = None
|
||||||
try:
|
try:
|
||||||
now = getSqlDatetime() # Datetimes are based on database server times
|
now = getSqlDatetime() # Datetimes are based on database server times
|
||||||
filter = Q(state = State.FOR_EXECUTE) & (Q(owner_server = self._hostname) | Q(owner_server = '')) & (Q(last_execution__gt = now) | Q(next_execution__lt = now))
|
fltr = Q(state=State.FOR_EXECUTE) & (Q(owner_server=self._hostname) | Q(owner_server='')) & (Q(last_execution__gt=now) | Q(next_execution__lt=now))
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
# If next execution is before now or last execution is in the future (clock changed on this server, we take that task as executable)
|
# If next execution is before now or last execution is in the future (clock changed on this server, we take that task as executable)
|
||||||
# This params are all set inside filter (look at __init__)
|
# This params are all set inside fltr (look at __init__)
|
||||||
job = dbScheduler.objects.select_for_update().filter(filter).order_by('next_execution')[0]
|
job = dbScheduler.objects.select_for_update().filter(fltr).order_by('next_execution')[0]
|
||||||
job.state = State.RUNNING
|
job.state = State.RUNNING
|
||||||
job.owner_server = self._hostname
|
job.owner_server = self._hostname
|
||||||
job.last_execution = now
|
job.last_execution = now
|
||||||
@ -119,7 +124,7 @@ class Scheduler(object):
|
|||||||
job.delete()
|
job.delete()
|
||||||
return
|
return
|
||||||
logger.debug('Executing job:>{0}<'.format(job.name))
|
logger.debug('Executing job:>{0}<'.format(job.name))
|
||||||
JobThread(jobInstance, job).start() # Do not instatiate thread, just run it
|
JobThread(jobInstance, job).start() # Do not instatiate thread, just run it
|
||||||
except IndexError:
|
except IndexError:
|
||||||
# Do nothing, there is no jobs for execution
|
# Do nothing, there is no jobs for execution
|
||||||
return
|
return
|
||||||
@ -135,8 +140,7 @@ class Scheduler(object):
|
|||||||
'''
|
'''
|
||||||
Releases all scheduleds being executed by this scheduler
|
Releases all scheduleds being executed by this scheduler
|
||||||
'''
|
'''
|
||||||
dbScheduler.objects.select_for_update().filter(owner_server = self._hostname).update(owner_server = '', state = State.FOR_EXECUTE)
|
dbScheduler.objects.select_for_update().filter(owner_server=self._hostname).update(owner_server='', state=State.FOR_EXECUTE)
|
||||||
|
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
# We ensure that the jobs are also in database so we can
|
# We ensure that the jobs are also in database so we can
|
||||||
@ -150,4 +154,3 @@ class Scheduler(object):
|
|||||||
self.executeOneJob()
|
self.executeOneJob()
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
logger.exception('Unexpected exception at run loop {0}: {1}'.format(e.__class__, e))
|
logger.exception('Unexpected exception at run loop {0}: {1}'.format(e.__class__, e))
|
||||||
|
|
||||||
|
@ -32,9 +32,14 @@ UDS jobs related modules
|
|||||||
|
|
||||||
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
|
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
|
||||||
'''
|
'''
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from Job import Job
|
from Job import Job
|
||||||
from DelayedTask import DelayedTask
|
from DelayedTask import DelayedTask
|
||||||
|
|
||||||
|
__updated__ = '2014-02-19'
|
||||||
|
|
||||||
|
|
||||||
def factory():
|
def factory():
|
||||||
from JobsFactory import JobsFactory
|
from JobsFactory import JobsFactory
|
||||||
return JobsFactory.factory()
|
return JobsFactory.factory()
|
@ -93,8 +93,8 @@ class RDPTransport(Transport):
|
|||||||
gui.boolToStr(self._allowDrives), gui.boolToStr(self._allowSerials),
|
gui.boolToStr(self._allowDrives), gui.boolToStr(self._allowSerials),
|
||||||
self._fixedName, self._fixedPassword, self._fixedDomain ] )
|
self._fixedName, self._fixedPassword, self._fixedDomain ] )
|
||||||
|
|
||||||
def unmarshal(self, str):
|
def unmarshal(self, str_):
|
||||||
data = str.split('\t')
|
data = str_.split('\t')
|
||||||
if data[0] == 'v1':
|
if data[0] == 'v1':
|
||||||
self._useEmptyCreds = gui.strToBool(data[1])
|
self._useEmptyCreds = gui.strToBool(data[1])
|
||||||
self._allowSmartcards = gui.strToBool(data[2])
|
self._allowSmartcards = gui.strToBool(data[2])
|
||||||
|
@ -104,8 +104,8 @@ class TSRDPTransport(Transport):
|
|||||||
gui.boolToStr(self._allowDrives), gui.boolToStr(self._allowSerials),
|
gui.boolToStr(self._allowDrives), gui.boolToStr(self._allowSerials),
|
||||||
self._fixedName, self._fixedPassword, self._fixedDomain, self._tunnelServer, self._tunnelCheckServer ] )
|
self._fixedName, self._fixedPassword, self._fixedDomain, self._tunnelServer, self._tunnelCheckServer ] )
|
||||||
|
|
||||||
def unmarshal(self, str):
|
def unmarshal(self, str_):
|
||||||
data = str.split('\t')
|
data = str_.split('\t')
|
||||||
if data[0] == 'v1':
|
if data[0] == 'v1':
|
||||||
self._useEmptyCreds = gui.strToBool(data[1])
|
self._useEmptyCreds = gui.strToBool(data[1])
|
||||||
self._allowSmartcards = gui.strToBool(data[2])
|
self._allowSmartcards = gui.strToBool(data[2])
|
||||||
|
Loading…
x
Reference in New Issue
Block a user