More pep8 related fixes, also some refactoring

This commit is contained in:
Adolfo Gómez 2014-02-19 15:17:55 +00:00
parent dab3e26223
commit 89addaf585
24 changed files with 975 additions and 905 deletions

View File

@ -32,6 +32,8 @@
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
from django.utils.translation import ugettext_noop as _
from uds.core.ui.UserInterface import gui
from uds.core import auths

View File

@ -4,27 +4,27 @@
# Copyright (c) 2012 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
@ -36,109 +36,111 @@ from uds.core import auths
import logging
__updated__ = '2014-02-19'
logger = logging.getLogger(__name__)
class SampleAuth(auths.Authenticator):
'''
This class represents a sample authenticator.
As this, it will provide:
* The authenticator functionality
* The authenticator functionality
* 3 Groups, "Mortals", "Gods" and "Daemons", just random group names selected.. :-),
plus groups that we enter at Authenticator form, from admin interface.
* Search of groups (inside the 3 groups used in this sample plus entered)
* Search for people (will return the search string + 000...999 as usernames)
* The Required form description for administration interface, so admins can create
new authenticators of this kind.
new authenticators of this kind.
In this sample, we will provide a simple standard auth, with owner drawn
login form that will simply show users that has been created and allow web user
to select one of them.
For this class to get visible at administration client as a authenticator type,
we MUST register it at package __init__
:note: At class level, the translations must be simply marked as so
using ugettext_noop. This is done in this way because we will translate
using ugettext_noop. This is done in this way because we will translate
the string when it is sent to the administration client.
'''
#: Name of type, used at administration interface to identify this
#: authenticator (i.e. LDAP, SAML, ...)
#: This string will be translated when provided to admin interface
#: using ugettext, so you can mark it as "_" at derived classes (using ugettext_noop)
#: if you want so it can be translated.
# : Name of type, used at administration interface to identify this
# : authenticator (i.e. LDAP, SAML, ...)
# : This string will be translated when provided to admin interface
# : using ugettext, so you can mark it as "_" at derived classes (using ugettext_noop)
# : if you want so it can be translated.
typeName = _('Sample Authenticator')
#: 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
#: module implementator will be the one that will provide a name that
#: will relation the class (type) and that name.
# : 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
# : module implementator will be the one that will provide a name that
# : will relation the class (type) and that name.
typeType = 'SampleAuthenticator'
#: Description shown at administration level for this authenticator.
#: This string will be translated when provided to admin interface
#: using ugettext, so you can mark it as "_" at derived classes (using ugettext_noop)
#: if you want so it can be translated.
# : Description shown at administration level for this authenticator.
# : This string will be translated when provided to admin interface
# : using ugettext, so you can mark it as "_" at derived classes (using ugettext_noop)
# : if you want so it can be translated.
typeDescription = _('Sample dummy authenticator')
#: 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
#: your own :py:meth:uds.core.BaseModule.BaseModule.icon method.
# : 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
# : your own :py:meth:uds.core.BaseModule.BaseModule.icon method.
iconFile = 'auth.png'
#: Mark this authenticator as that the users comes from outside the UDS
#: database, that are most authenticator (except Internal DB)
#: True is the default value, so we do not need it in fact
# isExternalSource = True
#: If we need to enter the password for this user when creating a new
#: user at administration interface. Used basically by internal authenticator.
#: False is the default value, so this is not needed in fact
#: needsPassword = False
#: Label for username field, shown at administration interface user form.
# : Mark this authenticator as that the users comes from outside the UDS
# : database, that are most authenticator (except Internal DB)
# : True is the default value, so we do not need it in fact
# isExternalSource = True
# : If we need to enter the password for this user when creating a new
# : user at administration interface. Used basically by internal authenticator.
# : False is the default value, so this is not needed in fact
# : needsPassword = False
# : Label for username field, shown at administration interface user form.
userNameLabel = _('Fake User')
# Label for group field, shown at administration interface user form.
groupNameLabel = _('Fake Group')
#: Definition of this type of authenticator form
#: We will define a simple form where we will use a simple
#: list editor to allow entering a few group names
groups = gui.EditableList(label=_('Groups'), values = ['Gods', 'Daemons', 'Mortals'])
# : Definition of this type of authenticator form
# : We will define a simple form where we will use a simple
# : list editor to allow entering a few group names
groups = gui.EditableList(label=_('Groups'), values=['Gods', 'Daemons', 'Mortals'])
def initialize(self, values):
'''
Simply check if we have
at least one group in the list
'''
# To avoid problems, we only check data if values are passed
# If values are not passed in, form data will only be available after
# unserialization, and at this point all will be default values
# so self.groups.value will be []
if values is not None and len(self.groups.value) < 2:
raise auths.Authenticator.ValidationException(_('We need more that two items!'))
def searchUsers(self, pattern):
'''
Here we will receive a pattern for searching users.
This method is invoked from interface, so an administrator can search users.
If we do not provide this method, the authenticator will not provide search
facility for users. In our case, we will simply return a list of users
(array of dictionaries with ids and names) with the pattern plus 1..10
(array of dictionaries with ids and names) with the pattern plus 1..10
'''
return [ { 'id' : '{0}-{1}'.format(pattern, a), 'name' : '{0} number {1}'.format(pattern, a) } for a in range(1, 10)]
def searchGroups(self, pattern):
'''
Here we we will receive a patter for searching groups.
In this sample, we will try to locate elements that where entered at
sample authenticator form (when created), and return the ones that
contains the pattern indicated.
@ -149,52 +151,52 @@ class SampleAuth(auths.Authenticator):
if g.lower().find(pattern) != -1:
res.append({'id' : g, 'name' : ''})
return res
def authenticate(self, username, credentials, groupsManager):
'''
This method is invoked by UDS whenever it needs an user to be authenticated.
It is used from web interface, but also from administration interface to
check credentials and access of user.
The tricky part of this method is the groupsManager, but it's easy to
understand what is used it for.
Imagine some authenticator, for example, an LDAP. It has its users, it has
its groups, and it has it relations (which user belongs to which group).
Now think about UDS. UDS know nothing about this, it only knows what
the administator has entered at admin interface (groups mainly, but he can
create users also).
UDS knows about this groups, but we need to relation those with the ones
know by the authenticator.
know by the authenticator.
To do this, we have created a simple mechanism, where the authenticator
receives a groupsManager, that knows all groups known by UDS, and has
the method so the authenticator can say, for the username being validated,
to which uds groups it belongs to.
This is done using the :py:meth:uds.core.auths.GroupsManager.GroupsManager.validate
method of the provided groups manager.
At return, UDS will do two things:
* If there is no group inside the groupsManager mareked as valid, it will
denied access.
* If there is some groups marked as valid, it will refresh the known
UDS relations (this means that the database will be refresehd so the user
has valid groups).
This also means that the group membership is only checked at user login (well,
in fact its also checked when an administrator tries to modify an user)
So, authenticate must not also validate the user credentials, but also
indicate the group membership of this user inside UDS.
indicate the group membership of this user inside UDS.
: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
# Now the tricky part. We will make this user belong to groups that contains at leat
# two letters equals to the groups names known by UDS
# For this, we will ask the groups manager for the groups names, and will check that and,
@ -202,18 +204,18 @@ class SampleAuth(auths.Authenticator):
for g in groupsManager.getGroupsNames():
if len(set(g.lower()).intersection(username.lower())) >= 2:
groupsManager.validate(g)
return True
def getGroups(self, username, groupsManager):
'''
As with authenticator part related to groupsManager, this
method will fill the groups to which the specified username belongs to.
We have to fill up groupsManager from two different places, so it's not
a bad idea to make a method that get the "real" authenticator groups and
them simply call to :py:meth:uds.core.auths.GroupsManager.GroupsManager.validate
In our case, we simply repeat the process that we also do at authenticate
'''
for g in groupsManager.getGroupsNames():
@ -224,82 +226,81 @@ class SampleAuth(auths.Authenticator):
'''
If we override this method from the base one, we are telling UDS
that we want to draw our own authenticator.
This way, we can do whataver we want here (for example redirect to a site
for a single sign on) generation our ouwn html (and javascript ofc).
'''
# Here there is a sample, commented out
# 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.
#res = ''
#for u in self.dbAuthenticator().users.all():
# res = ''
# for u in self.dbAuthenticator().users.all():
# res += '<a class="myNames" id="{0}" href="">{0}</a><br/>'.format(u.name)
#
#res += '<script type="text/javascript">$(".myNames").click(function() { '
#res += '$("#id_user").val(this.id); $("#id_password").val(this.id); $("#loginform").submit(); return false;});</script>'
#return res
#
# res += '<script type="text/javascript">$(".myNames").click(function() { '
# res += '$("#id_user").val(this.id); $("#id_password").val(this.id); $("#loginform").submit(); return false;});</script>'
# return res
# 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><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>'
return res
def authCallback(self, parameters, gm):
'''
We provide this as a sample of callback for an user.
We will accept all petitions that has "user" parameter
This method will get invoked by url redirections, probably by an SSO.
The idea behind this is that we can provide:
* Simple user/password authentications
* Own authentications (not UDS, authenticator "owned"), but with no redirections
* Own authentications via redirections (as most SSO will do)
Here, we will receive the parameters for this
'''
user = parameters.get('user', None)
return user
def createUser(self, usrData):
'''
This method provides a "check oportunity" to authenticators for users created
manually at administration interface.
If we do not provide this method, the administration interface will not allow
to create new users "by hand", i mean, the "new" options from menus will dissapear.
usrData is a dictionary that contains the input parameters from user,
usrData is a dictionary that contains the input parameters from user,
with at least name, real_name, comments, state & password.
We can modify this parameters, we can modify ALL, but name is not recommended to
We can modify this parameters, we can modify ALL, but name is not recommended to
modify it unles you know what you are doing.
Here, we will set the state to "Inactive" and realName to the same as username, but twice :-)
'''
from uds.core.util.State import State
usrData['real_name'] = usrData['name'] + ' ' + usrData['name']
usrData['state'] = State.INACTIVE
def modifyUser(self, usrData):
'''
This method provides a "check opportunity" to authenticator for users modified
at administration interface.
If we do not provide this method, nothing will happen (default one does nothing, but
it's valid).
usrData is a dictionary that contains the input parameters from user,
usrData is a dictionary that contains the input parameters from user,
with at least name, real_name, comments, state & password.
We can modify this parameters, we can modify ALL, but name is not recommended to
We can modify this parameters, we can modify ALL, but name is not recommended to
modify it unless you know what you are doing.
Here, we will simply update the realName of the user, and (we have to take care
this this kind of things) modify the userName to a new one, the original plus '-1'
'''

View File

@ -4,27 +4,27 @@
# Copyright (c) 2012 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@ -34,5 +34,8 @@ take care of registering it as provider
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
'''
from SampleAuth import SampleAuth
from __future__ import unicode_literals
from SampleAuth import SampleAuth
__updated__ = '2014-02-19'

View File

@ -4,27 +4,27 @@
# Copyright (c) 2012 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@ -32,38 +32,43 @@
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
from django.utils.translation import ugettext_noop as _
from uds.core.ui.UserInterface import gui
from uds.core.auths import Authenticator
import ldap
import logging
from uds.core.auths.Exceptions import AuthenticatorException
import ldap
import logging
__updated__ = '2014-02-19'
logger = logging.getLogger(__name__)
LDAP_RESULT_LIMIT = 50
class SimpleLDAPAuthenticator(Authenticator):
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)
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)
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)
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)
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)
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)
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')
class SimpleLDAPAuthenticator(Authenticator):
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)
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)
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)
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)
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)
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)
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')
typeType = 'SimpleLdapAuthenticator'
typeDescription = _('Simple LDAP authenticator')
iconFile = 'auth.png'
iconFile = 'auth.png'
# If it has and external source where to get "new" users (groups must be declared inside UDS)
isExternalSource = True
@ -76,7 +81,7 @@ class SimpleLDAPAuthenticator(Authenticator):
# Label for password field
passwordLabel = _("Password")
def __init__(self, dbAuth, environment, values = None):
def __init__(self, dbAuth, environment, values=None):
super(SimpleLDAPAuthenticator, self).__init__(dbAuth, environment, values)
if values != None:
self._host = values['host']
@ -91,7 +96,7 @@ class SimpleLDAPAuthenticator(Authenticator):
self._userIdAttr = values['userIdAttr']
self._groupIdAttr = values['groupIdAttr']
self._memberAttr = values['memberAttr']
self._userNameAttr = values['userNameAttr'].replace(' ', '') # Removes white spaces
self._userNameAttr = values['userNameAttr'].replace(' ', '') # Removes white spaces
else:
self._host = None
self._port = None
@ -107,109 +112,109 @@ class SimpleLDAPAuthenticator(Authenticator):
self._memberAttr = None
self._userNameAttr = None
self._connection = None
def valuesDict(self):
return { 'host' : self._host, 'port' : self._port, 'ssl' : gui.boolToStr(self._ssl),
'username' : self._username, 'password' : self._password, 'timeout' : self._timeout,
'ldapBase' : self._ldapBase, 'userClass' : self._userClass, 'groupClass' : self._groupClass,
'userIdAttr' : self._userIdAttr, 'groupIdAttr' : self._groupIdAttr, 'memberAttr' : self._memberAttr,
'userNameAttr' : self._userNameAttr
}
return {
'host': self._host, 'port': self._port, 'ssl': gui.boolToStr(self._ssl),
'username': self._username, 'password': self._password, 'timeout': self._timeout,
'ldapBase': self._ldapBase, 'userClass': self._userClass, 'groupClass': self._groupClass,
'userIdAttr': self._userIdAttr, 'groupIdAttr': self._groupIdAttr, 'memberAttr': self._memberAttr,
'userNameAttr': self._userNameAttr
}
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(
self._username, self._password, self._host, self._port, self._ldapBase, self._userClass, self._groupClass, self._userIdAttr, self._groupIdAttr, self._memberAttr,
self._userNameAttr)
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._ldapBase, self._userClass, self._groupClass, self._userIdAttr, self._groupIdAttr, self._memberAttr, self._userNameAttr ])
def unmarshal(self, str):
data = str.split('\t')
self._ldapBase, self._userClass, self._groupClass, self._userIdAttr, self._groupIdAttr, self._memberAttr, self._userNameAttr])
def unmarshal(self, str_):
data = str_.split('\t')
if data[0] == 'v1':
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._ssl = gui.strToBool(self._ssl)
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
self._ssl = gui.strToBool(self._ssl)
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
l = None
cache = False
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)
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)
logger.debug('Ldap uri: {0}'.format(uri))
l = ldap.initialize(uri=uri)
l.network_timeout = l.timeout = int(self._timeout)
l.protocol_version = ldap.VERSION3
if username is None:
cache = True
username = self._username
password = self._password
l.simple_bind_s(who = username, cred = password)
l.simple_bind_s(who=username, cred=password)
except ldap.LDAPError, e:
str = _('Ldap connection error: ')
str_ = _('Ldap connection error: ')
if type(e.message) == dict:
str += e.message.has_key('info') and e.message['info'] + ',' or ''
str += e.message.has_key('desc') and e.message['desc'] or ''
else :
str += str(e)
raise Exception(str)
str_ += 'info' in e.message and e.message['info'] + ',' or ''
str_ += 'desc' in e.message and e.message['desc'] or ''
else:
str_ += str_(e)
raise Exception(str_)
if cache is True:
self._connection = l
else:
return l # Do not cache nor overwrite "global" connection
return l # Do not cache nor overwrite "global" connection
return self._connection
def __getUser(self, username):
try:
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]
logger.debug('Getuser filter: {0}, attr list: {1}'.format(filter, attrlist))
res = con.search_ext_s(base = self._ldapBase, scope = ldap.SCOPE_SUBTREE,
filterstr = filter, attrlist = attrlist, sizelimit = LDAP_RESULT_LIMIT)[0]
usr = dict(( k, '' ) for k in attrlist)
logger.debug('Getuser filter_: {0}, attr list: {1}'.format(filter_, attrlist))
res = con.search_ext_s(base=self._ldapBase, scope=ldap.SCOPE_SUBTREE,
filterstr=filter_, attrlist=attrlist, sizelimit=LDAP_RESULT_LIMIT)[0]
usr = dict((k, '') for k in attrlist)
usr.update(res[1])
usr.update( {'dn' : res[0], '_id' : username })
usr.update({'dn': res[0], '_id': username})
logger.debug('Usr: {0}'.format(usr))
return usr
except Exception, e:
except Exception:
logger.exception('Exception:')
return None
def __getGroup(self, groupName):
try:
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]
logger.debug('Getgroup filter: {0}, attr list {1}'.format(filter, attrlist))
res = con.search_ext_s(base = self._ldapBase, scope = ldap.SCOPE_SUBTREE,
filterstr = filter, attrlist = attrlist, sizelimit = LDAP_RESULT_LIMIT)[0]
grp = dict(( k, [''] ) for k in attrlist)
logger.debug('Getgroup filter_: {0}, attr list {1}'.format(filter_, attrlist))
res = con.search_ext_s(base=self._ldapBase, scope=ldap.SCOPE_SUBTREE,
filterstr=filter_, attrlist=attrlist, sizelimit=LDAP_RESULT_LIMIT)[0]
grp = dict((k, ['']) for k in attrlist)
grp.update(res[1])
grp.update( {'dn' : res[0], '_id' : groupName })
grp.update({'dn': res[0], '_id': groupName})
logger.debug('Group: {0}'.format(grp))
return grp
except Exception, e:
except Exception:
logger.exception('Exception:')
return None
def __getGroups(self, usr):
try:
con = self.__connection()
filter = '(&(objectClass=%s)(|(%s=%s)(%s=%s)))' % (self._groupClass, self._memberAttr, usr['_id'], self._memberAttr, usr['dn'])
logger.debug('Filter: {0}'.format(filter))
res = con.search_ext_s(base = self._ldapBase, scope = ldap.SCOPE_SUBTREE, filterstr = filter, attrlist = [self._groupIdAttr],
sizelimit = LDAP_RESULT_LIMIT)
filter_ = '(&(objectClass=%s)(|(%s=%s)(%s=%s)))' % (self._groupClass, self._memberAttr, usr['_id'], self._memberAttr, usr['dn'])
logger.debug('Filter: {0}'.format(filter_))
res = con.search_ext_s(base=self._ldapBase, scope=ldap.SCOPE_SUBTREE, filterstr=filter_, attrlist=[self._groupIdAttr],
sizelimit=LDAP_RESULT_LIMIT)
groups = {}
for g in res:
v = g[1][self._groupIdAttr]
@ -218,19 +223,18 @@ class SimpleLDAPAuthenticator(Authenticator):
for gg in v:
groups[str(gg)] = g[0]
logger.debug('Groups: {0}'.format(groups))
return groups
return groups
except Exception:
return {}
def __getUserRealName(self, usr):
'''
Tries to extract the real name for this user. Will return all atttributes (joint)
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):
'''
Must authenticate the user.
@ -238,31 +242,31 @@ class SimpleLDAPAuthenticator(Authenticator):
1.- The authenticator is external source, what means that users may be unknown to system before callig this
2.- The authenticator isn't external source, what means that users have been manually added to system and are known before this call
We receive the username, the credentials used (normally password, but can be a public key or something related to pk) and a group manager.
The group manager is responsible for letting know the authenticator which groups we currently has active.
The group manager is responsible for letting know the authenticator which groups we currently has active.
@see: uds.core.auths.GroupsManager
'''
try:
# Locate the user at LDAP
# Locate the user at LDAP
usr = self.__getUser(username)
if usr is None:
return False
# 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())
return True
except Exception:
return False
def createUser(self, usrData):
'''
Groups are only used in case of internal users (non external sources) that must know to witch groups this user belongs to
@param usrData: Contains data received from user directly, that is, a dictionary with at least: name, realName, comments, state & password
@return: Raises an exception (AuthException) it things didn't went fine
@param usrData: Contains data received from user directly, that is, a dictionary with at least: name, realName, comments, state & password
@return: Raises an exception (AuthException) it things didn't went fine
'''
res = self.__getUser(usrData['name'])
if res is None:
@ -278,30 +282,29 @@ class SimpleLDAPAuthenticator(Authenticator):
if res is None:
return username
return self.__getUserRealName(res)
def modifyUser(self, usrData):
'''
We must override this method in authenticators not based on external sources (i.e. database users, text file users, etc..)
Modify user has no reason on external sources, so it will never be used (probably)
Groups are only used in case of internal users (non external sources) that must know to witch groups this user belongs to
@param usrData: Contains data received from user directly, that is, a dictionary with at least: name, realName, comments, state & password
@return: Raises an exception it things don't goes fine
@param usrData: Contains data received from user directly, that is, a dictionary with at least: name, realName, comments, state & password
@return: Raises an exception it things don't goes fine
'''
return self.createUser(usrData)
def createGroup(self, groupData):
'''
We must override this method in authenticators not based on external sources (i.e. database users, text file users, etc..)
External sources already has its own groups and, at most, it can check if it exists on external source before accepting it
Groups are only used in case of internal users (non external sources) that must know to witch groups this user belongs to
@params groupData: a dict that has, at least, name, comments and active
@return: Raises an exception it things don't goes fine
@return: Raises an exception it things don't goes fine
'''
res = self.__getGroup(groupData['name'])
if res is None:
raise AuthenticatorException(_('Group not found'))
def getGroups(self, username, groupsManager):
'''
Looks for the real groups to which the specified user belongs
@ -312,36 +315,39 @@ class SimpleLDAPAuthenticator(Authenticator):
if user is None:
raise AuthenticatorException(_('Username not found'))
groupsManager.validate(self.__getGroups(user).keys())
def searchUsers(self, pattern):
try:
con = self.__connection()
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 = type(usrId) == list and usrId[0] or usrId
res.append( { 'id' : usrId,
'name' : self.__getUserRealName(r[1]) } )
res.append({
'id': usrId,
'name': self.__getUserRealName(r[1])
})
return res
except Exception, e:
except Exception:
logger.exception("Exception: ")
raise AuthenticatorException(_('Too many results, be more specific'))
def searchGroups(self, pattern):
try:
con = self.__connection()
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 = type(grpId) == list and grpId[0] or grpId
res.append( { 'id' : grpId,
'name' : grpId } )
res.append({
'id': grpId,
'name': grpId
})
return res
except Exception, e:
except Exception:
logger.exception("Exception: ")
raise AuthenticatorException(_('Too many results, be more specific'))
@staticmethod
def test(env, data):
try:
@ -350,20 +356,20 @@ class SimpleLDAPAuthenticator(Authenticator):
except Exception, e:
logger.error("Exception found testing Simple LDAP auth {0}: {1}".format(e.__class__, e))
return [False, "Error testing connection"]
def testConnection(self):
try:
con = self.__connection()
except Exception, e:
return [False, str(e)]
try:
con.search_s(base = self._ldapBase, scope = ldap.SCOPE_BASE)
con.search_s(base=self._ldapBase, scope=ldap.SCOPE_BASE)
except Exception:
return [False, _('Ldap search base is incorrect')]
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()
return [False, _('Ldap user class seems to be incorrect (no user found by that class)')]
except Exception, e:
@ -371,7 +377,7 @@ class SimpleLDAPAuthenticator(Authenticator):
pass
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()
return [False, _('Ldap group class seems to be incorrect (no group found by that class)')]
except Exception, e:
@ -379,46 +385,43 @@ class SimpleLDAPAuthenticator(Authenticator):
pass
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()
return [False, _('Ldap user id attribute seems to be incorrect (no user found by that attribute)')]
except Exception, e:
# If found 1 or more, all right
pass
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()
return [False, _('Ldap group id attribute seems to be incorrect (no group found by that attribute)')]
except Exception, e:
# If found 1 or more, all right
pass
# Now test objectclass and attribute of users
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()
return [False, _('Ldap user class or user id attr is probably wrong (can\'t find any user with both conditions)')]
except Exception, e:
# If found 1 or more, all right
pass
# And group part, with membership
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:
raise Exception(_('Ldap group class or group id attr is probably wrong (can\'t find any group with both conditions)'))
ok = False
for r in res:
if r[1].has_key(self._memberAttr) is True:
if self._memberAttr in r[1]:
ok = True
break
if ok is False:
raise Exception(_('Can\'t locate any group with the membership attribute specified'))
except Exception, e:
return [False, str(e)]
return [True, _("Connection params seem correct, test was succesfully executed")]

View File

@ -4,27 +4,27 @@
# Copyright (c) 2012 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@ -32,5 +32,8 @@
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
from Authenticator import SimpleLDAPAuthenticator
__updated__ = '2014-02-19'

View File

@ -70,7 +70,7 @@ class Serializable(object):
In that case, initialize the object with default values
Args:
str _ : String readed from persistent storage to deseralilize
str_ _ : String readed from persistent storage to deseralilize
:note: This method must be overridden
'''

View File

@ -4,66 +4,70 @@
# Copyright (c) 2012 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
__updated__ = '2014-02-19'
class AuthsFactory(object):
'''
This class holds the register of all known authentication modules
inside UDS.
It provides a way to register and recover Authentication providers.
'''
_factory = None
def __init__(self):
self._auths = {}
@staticmethod
@staticmethod
def factory():
'''
Returns the factory that keeps the register of authentication providers.
'''
if AuthsFactory._factory == None:
if AuthsFactory._factory == None:
AuthsFactory._factory = AuthsFactory()
return AuthsFactory._factory
def providers(self):
'''
Returns the list of authentication providers already registered.
'''
return self._auths
def insert(self, type_):
def insert(self, type_):
'''
Registers a new authentication provider
'''
self._auths[type_.type()] = type_
def lookup(self, typeName):
'''
Tries to locate an authentication provider and by its name, and, if

View File

@ -3,27 +3,27 @@
# Copyright (c) 2012 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
@ -31,126 +31,130 @@ Base module for all authenticators
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
from uds.core import Module
from django.utils.translation import ugettext_noop as _
from GroupsManager import GroupsManager
from Exceptions import InvalidUserException
import logging
__updated__ = '2014-02-19'
logger = logging.getLogger(__name__)
class Authenticator(Module):
'''
This class represents the base interface to implement authenticators.
An authenticator is responsible for managing user and groups of a kind
inside UDS. As so, it must provide a number of method and mechanics to
allow UDS to manage users and groups using that kind of authenticator.
Some samples of authenticators are LDAP, Internal Database, SAML, CAS, ...
As always, if you override __init__, do not forget to invoke base __init__ as this::
super(self.__class__, self).__init__(self, dbAuth, environment, values)
This is a MUST, so internal structured gets filled correctly, so don't forget it!.
The preferred method of doing initialization is to provide the :py:meth:`.initialize`,
and do not override __init__ method. This (initialize) will be invoked after
all internal initialization.
There are basically two kind of authenticators, that are "Externals" and
"Internals".
"Internals".
Internal authenticators are those where and administrator has created manually
the user at admin interface. The users are not created from an external source,
so if an user do not exist at UDS database, it will not be valid.
so if an user do not exist at UDS database, it will not be valid.
In other words, if you have an authenticator where you must create users,
you can modify them, you must assign passwords manually, and group membership
also must be assigned manually, the authenticator is not an externalSource.
As you can notice, almost avery authenticator except internal db will be
external source, so, by default, attribute that indicates that is an external
source is set to True.
In fact, internal source authenticator is intended to allow UDS to identify
In fact, internal source authenticator is intended to allow UDS to identify
if the users come from internal DB (just the case of local authenticator),
or the users come from other sources. Also, this allos UDS to know when to
"update" group membership information for an user whenever it logs in.
External authenticator are in fact all authenticators except local database,
so we have defined isExternalSource as True by default, that will be most
so we have defined isExternalSource as True by default, that will be most
cases.
:note: All attributes that are "_" here means that they will be
translated when provided to administration interface, so remember
to mark them in your own authenticators as "_" using
ugettext_noop. We have aliased it here to "_" so it's
ugettext_noop. We have aliased it here to "_" so it's
easier to understand.
'''
#: Name of type, used at administration interface to identify this
#: authenticator (i.e. LDAP, SAML, ...)
#: This string will be translated when provided to admin interface
#: using ugettext, so you can mark it as "_" at derived classes (using ugettext_noop)
#: if you want so it can be translated.
# : Name of type, used at administration interface to identify this
# : authenticator (i.e. LDAP, SAML, ...)
# : This string will be translated when provided to admin interface
# : using ugettext, so you can mark it as "_" at derived classes (using ugettext_noop)
# : if you want so it can be translated.
typeName = _('Base Authenticator')
#: 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
#: module implementator will be the one that will provide a name that
#: will relation the class (type) and that name.
# : 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
# : module implementator will be the one that will provide a name that
# : will relation the class (type) and that name.
typeType = 'BaseAuthenticator'
#: Description shown at administration level for this authenticator.
#: This string will be translated when provided to admin interface
#: using ugettext, so you can mark it as "_" at derived classes (using ugettext_noop)
#: if you want so it can be translated.
# : Description shown at administration level for this authenticator.
# : This string will be translated when provided to admin interface
# : using ugettext, so you can mark it as "_" at derived classes (using ugettext_noop)
# : if you want so it can be translated.
typeDescription = _('Base Authenticator')
#: 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
#: your own :py:meth:uds.core.BaseModule.BaseModule.icon method.
# : 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
# : your own :py:meth:uds.core.BaseModule.BaseModule.icon method.
iconFile = 'auth.png'
#: Mark this authenticator as that the users comes from outside the UDS
#: database, that are most authenticator (except Internal DB)
#: So, isInternalSource means that "user is kept at database only"
# : Mark this authenticator as that the users comes from outside the UDS
# : database, that are most authenticator (except Internal DB)
# : So, isInternalSource means that "user is kept at database only"
isExternalSource = True
#: If we need to enter the password for this user when creating a new
#: user at administration interface. Used basically by internal authenticator.
# : If we need to enter the password for this user when creating a new
# : user at administration interface. Used basically by internal authenticator.
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')
#: Label for group field, shown at administration interface user form.
# : Label for group field, shown at administration interface user form.
groupNameLabel = _('Group name')
#: Label for password field, , shown at administration interface user form.
#: Not needed for external authenticators (where credentials are stored with
#: an already existing user.
# : Label for password field, , shown at administration interface user form.
# : Not needed for external authenticators (where credentials are stored with
# : an already existing user.
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
from User import User
from Group import Group
#: 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
#: user class
# : 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
# : user class
userType = User
#: 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
#: group class
# : 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
# : group class
groupType = Group
def __init__(self, dbAuth, environment, values):
'''
Instantiathes the authenticator.
@ -161,24 +165,24 @@ class Authenticator(Module):
self._dbAuth = dbAuth
super(Authenticator, self).__init__(environment, values)
self.initialize(values)
def initialize(self, values):
'''
This method will be invoked from __init__ constructor.
This is provided so you don't have to provide your own __init__ method,
and invoke base methods.
This will get invoked when all initialization stuff is done
Args:
Values: If values is not none, this object is being initialized
from administration interface, and not unmarshal will be done.
If it's None, this is initialized internally, and unmarshal will
be called after this.
Default implementation does nothing
'''
pass
def dbAuthenticator(self):
'''
Helper method to access the Authenticator database object
@ -190,24 +194,24 @@ class Authenticator(Module):
Helper method, not needed to be overriden.
It simply checks if the source is external and if so, recreates
the user groups for storing them at database.
user param is a database user object
'''
if self.isExternalSource == True:
groupsManager = GroupsManager(self._dbAuth)
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):
'''
Helper method to return callback url for self (authenticator).
This method will allow us to know where to do redirection in case
we need to use callback for authentication
'''
from auth import authCallbackUrl
return authCallbackUrl(self.dbAuthenticator())
def infoUrl(self):
'''
Helper method to return info url for this authenticator
@ -221,169 +225,169 @@ class Authenticator(Module):
Helper to query if a class is custom (implements getHtml method)
'''
return cls.getHtml != Authenticator.getHtml
@classmethod
def canCheckUserPassword(cls):
'''
Helper method to query if a class can do a login using credentials
'''
return cls.authenticate != Authenticator.authenticate
def searchUsers(self, pattern):
'''
If you provide this method, the user will be allowed to search users,
that is, the search button at administration interface, at user form,
will be enabled.
Returns an array of users that match the supplied pattern
If none found, returns empty array.
Must return is an array of dictionaries that must contains 'id' and 'name'
example: [ {'id': 'user1', 'name': 'Nombre 1'} ]
Args:
pattern: Pattern to search for (simple pattern, string)
Returns
Returns
a list of found users for the pattern specified
'''
return []
def searchGroups(self, pattern):
'''
Returns an array of groups that match the supplied pattern
If none found, returns empty array. Items returned are BaseGroups (or derived)
If you override this method, the admin interface will allow the use of
"search" at group form. If not overriden, the search will not be allowed.
Must return array of dictionaries that must contains 'id' and 'name'
example: [ {'id': 'user1', 'name': 'Nombre 1'} ]
Default implementation returns empty array, but is never used because if
not overriden, search of groups will not be allowed.
'''
return []
def authenticate(self, username, credentials, groupsManager):
'''
This method must be overriden, and is responsible for authenticating
users.
users.
We can have to different situations here:
* The authenticator is external source, what means that users may
* The authenticator is external source, what means that users may
be unknown to system before callig this
* The authenticator isn't external source, what means that users have
* The authenticator isn't external source, what means that users have
been manually added to system and are known before this call.
This will only happen at Internal DB Authenticator.
We receive the username, the credentials used (normally password, but can
We receive the username, the credentials used (normally password, but can
be a public key or something related to pk) and a group manager.
The group manager is responsible for letting know the authenticator which
The group manager is responsible for letting know the authenticator which
groups we currently has active.
Args:
username: User name to authenticate
credentilas: Credentials for this user, (password, pki, or whatever needs to be used). (string)
groupManager: Group manager to modify with groups to which this users belongs to.
Returns:
True if authentication success, False if don't.
True if authentication success, False if don't.
See uds.core.auths.GroupsManager
:note: This method must check not only that the user has valid credentials, but also
check the valid groups from groupsManager.
If this method returns false, of method getValidGroups of the groupsManager
passed into this method has no elements, the user will be considered invalid.
So remember to check validity of groups this user belongs to (inside the authenticator,
not inside UDS) using groupsManager.validate(group to which this users belongs to).
This is done in this way, because UDS has only a subset of groups for this user, and
we let the authenticator decide inside wich groups of UDS this users is included.
'''
return False
def transformUsername(self, username):
'''
On login, this method get called so we can "transform" provided user name.
Args:
username: Username to transform
Returns
Transformed user name
:note: You don't need to implement this method if your authenticator (as most authenticators does), does not
transforms 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
the username/credentials in an alternative way.
For example, ip authenticator generates, inside the custom html, a 1 time password
that will be used to authenticate the ip. If we create a custom dispatcher and we want
to auth the user without the html part being displayed, we have a big problem.
Using this method, the authenticator has the oportunitiy to, (for example, in case of
IP auth), ignore "credentials"
Args:
username: User name to authenticate
credentilas: Credentials for this user, (password, pki, or whatever needs to be used). (string)
groupManager: Group manager to modify with groups to which this users belongs to.
Returns:
True if authentication success, False if don't.
By default, internalAuthenticate simply invokes authenticate, but this method
is here so you can provide your own method if needed
is here so you can provide your own method if needed
See uds.core.auths.GroupsManager
:note: This method must check not only that the user has valid credentials, but also
check the valid groups from groupsManager.
If this method returns false, of method getValidGroups of the groupsManager
passed into this method has no elements, the user will be considered invalid.
So remember to check validity of groups this user belongs to (inside the authenticator,
not inside UDS) using groupsManager.validate(group to which this users belongs to).
This is done in this way, because UDS has only a subset of groups for this user, and
we let the authenticator decide inside wich groups of UDS this users is included.
'''
return self.authenticate(username, credentials, groupsManager)
def logout(self, username):
'''
Invoked whenever an user logs out.
Notice that authenticators that provides getHtml method are considered "custom", and
these authenticators will never be used to allow an user to access administration interface
(they will be filtered out)
By default, this method does nothing.
Args:
username: Name of the user that logged out
Returns:
None if nothing has to be done by UDS. An URL (absolute or relative), if it has to redirect
the user to somewhere.
the user to somewhere.
:note: This method will be invoked also for administration log out (it it's done), but return
result will be passed to administration interface, that will invoke the URL but nothing
will be shown to the user.
Also, notice that this method will only be invoked "implicity", this means that will be
invoked if user requests "log out", but maybe it will never be invoked.
'''
return None
def getForAuth(self, username):
'''
Process the username for this authenticator and returns it.
@ -392,92 +396,92 @@ class Authenticator(Module):
this method.
For example, an authenticator can add '@domain' so transport use the complete
'user@domain' instead of 'user'.
Right now, all authenticators keep this value "as is", i mean, it simply
returns the unprocessed username
'''
return username
def getGroups(self, username, groupsManager):
'''
Looks for the real groups to which the specified user belongs.
You MUST override this method, UDS will call it whenever it needs to refresh an user group membership.
The expected behavior of this method is to mark valid groups in the :py:class:`uds.core.auths.GroupsManager` provided, normally
calling its :py:meth:`uds.core.auths.GroupsManager.validate` method with groups names provided by the authenticator itself
(for example, LDAP, AD, ...)
'''
pass
def getHtml(self, request):
'''
If you override this method, and returns something different of None,
UDS will consider your authenticator as "Owner draw", that is, that it
will not use the standard form for user authentication.
Args:
Request is the DJango request received for generating this html,
with included user ip at request.ip.
We have here a few things that we should know for creating our own
html for authenticator:
* We use jQuery, so your javascript can use it
* The id of the username input field is **id_user**
* The id of the password input field is **id_password**
* The id of the login form is **loginform**
* The id of the "back to login" link is **backToLogin**
This is what happens when an authenticator that has getHtml method is
selected in the front end (from the combo shown):
* The div with id **login** is hidden.
* The div with id **nonStandard** is shown
* Using Ajax, the html provided by this method is requested for
the authenticator
* The returned html is rendered inside **nonStandardLogin** div.
* The **nonStandard** div is shown.
* The **nonStandard** div is shown.
**nonStandard** div has two inner divs, **nonStandardLogin** and
**divBackToLogin**. If there is no standard auths, divBackToLogin is
erased.
With this, and :py:meth:.authCallback method, we can add SSO engines
to UDS with no much problems.
'''
return None
def authCallback(self, parameters, gm):
'''
There is a view inside UDS, an url, that will redirect the petition
to this callback.
If someone gets authenticated via this callback, the method will return
an "username" must be return. This username will be used to:
* Add user to UDS
* Get user groups.
So, if this callback is called, also get the membership to groups of the user, and keep them.
This method will have to keep track of those until UDS request that groups
using getGroups. (This is easy, using storage() provided with the environment (env())
If this returns None, or empty, the authentication will be considered "invalid"
and an error will be shown.
Args:
parameters: all GET and POST received parameters
gm: Groups manager, you MUST check group membership using this gm
Return:
An username if validation check is successfull, None if not
You can also return an exception here and, if you don't wont to check the user login,
you can raise :py:class:uds.core.auths.Exceptions.Redirect to redirect user to somewhere.
In this case, no user checking will be done. This is usefull to use this url to provide
other functionality appart of login, (such as logout)
:note: Keeping user information about group membership inside storage is highly recommended.
There will be calls to getGroups one an again, and also to getRealName, not just
at login, but at future (from admin interface, at user editing for example)
@ -488,16 +492,16 @@ class Authenticator(Module):
'''
This method is invoked whenever the authinfo url is invoked, with the name of the authenticator
If this is implemented, information returned by this will be shown via web.
:note: You can return here a single element or a list (or tuple), where first element will be content itself,
and second will be the content type (i.e. "text/plain").
:note: You can return here a single element or a list (or tuple), where first element will be content itself,
and second will be the content type (i.e. "text/plain").
'''
return None
def getRealName(self, username):
'''
Tries to get the real name of an user
Default implementation returns just the same user name that is passed in.
'''
return username
@ -505,147 +509,144 @@ class Authenticator(Module):
def createUser(self, usrData):
'''
This method is used when creating an user to allow the authenticator:
* Check that the name inside usrData is fine
* Fill other (not name, if you don't know what are you doing) usrData dictionary values.
This will be invoked from admin interface, when admin wants to create a new user
modified usrData will be used to store values at database.
Args:
usrData: Contains data received from user directly, that is a dictionary
with at least: name, real_name, comments, state & password.
This is an in/out parameter, so you can modify, for example,
**realName**
**realName**
Returns:
Raises an exception if things didn't went fine,
Raises an exception if things didn't went fine,
return value is ignored, but modified usrData is used if this does not
raises an exception.
raises an exception.
Take care with whatever you modify here, you can even modify provided
name (login name!) to a new one!
:note: If you have an SSO where you can't create an user from admin interface,
raise an exception here indicating that the creation can't be done.
Default implementation simply raises "AuthenticatorException" and
says that user can't be created manually
'''
raise InvalidUserException(_('Users can\'t be created inside this authenticator'))
def modifyUser(self, usrData):
'''
This method is used when modifying an user to allow the authenticator:
* Check that the name inside usrData is fine
* Fill other (not name, if you don't know what are you doing) usrData dictionary values.
Args:
usrData: Contains data received from user directly, that is a dictionary
with at least: name, real_name, comments, state & password.
This is an in/out parameter, so you can modify, for example,
**realName**
**realName**
Returns:
Raises an exception if things didn't went fine,
Raises an exception if things didn't went fine,
return value is ignored, but modified usrData is used if this does not
raises an exception.
raises an exception.
Take care with whatever you modify here, you can even modify provided
name (login name!) to a new one!
:note: By default, this will do nothing, as we can only modify "accesory" internal
data of users.
'''
pass
def createGroup(self, groupData):
'''
This method is used when creating a new group to allow the authenticator:
* Check that the name inside groupData is fine
* Fill other (not name, if you don't know what are you doing) usrData dictionary values.
This will be invoked from admin interface, when admin wants to create a new group.
modified groupData will be used to store values at database.
Args:
groupData: Contains data received from user directly, that is a dictionary
with at least: name, comments and state. (State.ACTIVE, State.INACTIVE)
This is an in/out parameter, so you can modify, for example,
**comments**
**comments**
Returns:
Raises an exception if things didn't went fine,
Raises an exception if things didn't went fine,
return value is ignored, but modified groupData is used if this does not
raises an exception.
raises an exception.
Take care with whatever you modify here, you can even modify provided
name (group name) to a new one!
'''
pass
def modifyGroup(self, groupData):
'''
This method is used when modifying group to allow the authenticator:
* Check that the name inside groupData is fine
* Fill other (not name, if you don't know what are you doing) usrData dictionary values.
This will be invoked from admin interface, when admin wants to create a new group.
modified groupData will be used to store values at database.
Args:
groupData: Contains data received from user directly, that is a dictionary
with at least: name, comments and state. (State.ACTIVE, State.INACTIVE)
This is an in/out parameter, so you can modify, for example,
**comments**
**comments**
Returns:
Raises an exception if things didn't went fine,
Raises an exception if things didn't went fine,
return value is ignored, but modified groupData is used if this does not
raises an exception.
raises an exception.
Note: 'name' output parameter will be ignored
'''
pass
def removeUser(self, username):
'''
Remove user is used whenever from the administration interface, or from other
internal workers, an user needs to be removed.
This is a notification method, whenever an user gets removed from UDS, this
will get called.
This is a notification method, whenever an user gets removed from UDS, this
will get called.
You can do here whatever you want, but you are not requested to do anything
at your authenticators.
If this method raises an exception, the user will not be removed from UDS
'''
pass
# We don't have a "modify" group option. Once u have created it, the only way of changing it if removing it an recreating it with another name
def removeGroup(self, groupname):
'''
Remove user is used whenever from the administration interface, or from other
internal workers, an group needs to be removed.
This is a notification method, whenever an group gets removed from UDS, this
will get called.
This is a notification method, whenever an group gets removed from UDS, this
will get called.
You can do here whatever you want, but you are not requested to do anything
at your authenticators.
If this method raises an exception, the group will not be removed from UDS
'''
pass

View File

@ -4,59 +4,66 @@
# Copyright (c) 2012 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
__updated__ = '2014-02-19'
class AuthenticatorException(Exception):
'''
Generic authentication exception
'''
pass
class InvalidUserException(AuthenticatorException):
'''
Invalid user specified. The user cant access the requested service
'''
pass
class InvalidAuthenticatorException(AuthenticatorException):
'''
Invalida authenticator has been specified
'''
pass
class Redirect(Exception):
'''
This exception indicates that a redirect is required.
Used in authUrlCallback to indicate that redirect is needed
'''
class Logout(Exception):
'''
This exceptions redirects logouts an user and redirects to an url
'''

View File

@ -4,45 +4,49 @@
# Copyright (c) 2012 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification,are permitted provided that the following conditions are
# Redistribution and use in source and binary forms, with or without
# modification,are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright notice,
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
import logging
__updated__ = '2014-02-19'
logger = logging.getLogger(__name__)
class Group(object):
'''
A group is simply a database group associated with its authenticator instance
It's only constructor expect a database group as parameter.
'''
def __init__(self, dbGroup):
'''
Initializes internal data
@ -54,8 +58,8 @@ class Group(object):
'''
Returns the database authenticator associated with this group
'''
return self._manager
return self._manager
def dbGroup(self):
'''
Returns the database group associated with this

View File

@ -4,61 +4,65 @@
# Copyright (c) 2012 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
from uds.core.util.State import State
from uds.models import Group as dbGroup
from Group import Group
import inspect
import inspect
import logging
__updated__ = '2014-02-19'
logger = logging.getLogger(__name__)
class GroupsManager(object):
'''
Manages registered groups for an specific authenticator.
Most authenticators (except internal database one, that is an special case)
has their database of users and passwords outside UDS. Think, for example,
about LDAP. It has its own database of users and groups, and has its own
correspondence of which user belongs to which group.
UDS Only knows a subset of this groups, those that the administrator has
registered inside UDS.
To manage the equivalence between groups from the authenticator and UDS groups,
we provide a list of "known groups" by uds. The authenticator then makes the
we provide a list of "known groups" by uds. The authenticator then makes the
correspondence, marking the groups (UDS groups) that the user belongs to as
valid.
Managed groups names are compared using case insensitive comparison.
'''
def __init__(self, dbAuthenticator):
'''
Initializes the groups manager.
@ -67,14 +71,14 @@ class GroupsManager(object):
'''
self._dbAuthenticator = dbAuthenticator
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):
self._groups[g.name.lower()] = { 'group': Group(g), 'valid': False }
for g in dbAuthenticator.groups.filter(state=State.ACTIVE, is_meta=False):
self._groups[g.name.lower()] = {'group': Group(g), 'valid': False}
def contains(self, groupName):
'''
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):
'''
@ -83,7 +87,7 @@ class GroupsManager(object):
'''
for g in self._groups.itervalues():
yield g['group'].dbGroup().name
def getValidGroups(self):
'''
returns the list of valid groups (:py:class:uds.core.auths.Group.Group)
@ -95,12 +99,11 @@ class GroupsManager(object):
yield g['group']
# Now, get metagroups and also return them
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()
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
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
# This group matches
yield Group(g)
def hasValidGroups(self):
'''
Checks if this groups manager has at least one group that has been
@ -110,26 +113,26 @@ class GroupsManager(object):
if g['valid'] is True:
return True
return False
def getGroup(self, groupName):
'''
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.
'''
if self._groups.has_key(groupName.lower()):
if groupName.lower() in self._groups:
return self._groups[groupName.lower()]['group']
else:
return None
def validate(self, groupName):
'''
Validates that the group groupName passed in is valid for this group manager.
It check that the group specified is known by this group manager.
Args:
groupName: string, list or tuple of values (strings) to check
Returns nothing, it changes the groups this groups contains attributes,
so they reflect the known groups that are considered valid.
'''
@ -137,19 +140,17 @@ class GroupsManager(object):
for n in groupName:
self.validate(n)
else:
if self._groups.has_key(groupName.lower()):
if groupName.lower() in self._groups:
self._groups[groupName.lower()]['valid'] = True
def isValid(self, groupName):
'''
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.
'''
if self._groups.has_key(groupName.lower()):
if groupName.lower() in self._groups:
return self._groups[groupName.lower()]['valid']
return False
def __str__(self):
return "Groupsmanager: {0}".format(self._groups)

View File

@ -4,50 +4,53 @@
# Copyright (c) 2012 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
import logging
__updated__ = '2014-02-19'
logger = logging.getLogger(__name__)
class User(object):
'''
An user represents a database user, associated with its authenticator (instance)
and its groups.
'''
def __init__(self, dbUser):
self._manager = dbUser.getManager()
self._grpsManager = None
self._dbUser = dbUser
self._groups = None
def _groupsManager(self):
'''
If the groups manager for this user already exists, it returns this.
@ -55,22 +58,22 @@ class User(object):
returns it.
'''
from GroupsManager import GroupsManager
if self._grpsManager == None:
self._grpsManager = GroupsManager(self._manager.dbAuthenticator())
return self._grpsManager
def groups(self):
'''
Returns the valid groups for this user.
To do this, it will validate groups throuht authenticator instance using
:py:meth:`uds.core.auths.Authenticator.getGroups` method.
:note: Once obtained valid groups, it caches them until object removal.
'''
from uds.models import User as DbUser
from Group import Group
if self._groups == None:
if self._manager.isExternalSource == True:
self._manager.getGroups(self._dbUser.name, self._groupsManager())
@ -88,17 +91,15 @@ class User(object):
usr = DbUser.objects.get(pk=self._dbUser.id)
self._groups = [Group(g) for g in usr.getGroups()]
return self._groups
def manager(self):
'''
Returns the authenticator instance
'''
return self._manager
return self._manager
def dbUser(self):
'''
Returns the database user
'''
return self._dbUser

View File

@ -4,27 +4,27 @@
# Copyright (c) 2012 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
@ -32,17 +32,20 @@ UDS authentication related interfaces and classes
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
from BaseAuthenticator import Authenticator
from User import User
from Group import Group
from GroupsManager import GroupsManager
import Exceptions
__updated__ = '2014-02-19'
def factory():
'''
Returns factory for register/access to authenticators
'''
from AuthsFactory import AuthsFactory
return AuthsFactory.factory()

View File

@ -4,33 +4,33 @@
# Copyright (c) 2012 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
Provides useful functions for authenticating, used by web interface.
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
@ -43,47 +43,52 @@ from django.utils.translation import ugettext as _
from uds.core.util.Config import GlobalConfig
from uds.core.util import log
from uds.core import auths
from uds.core.managers.CryptoManager import CryptoManager
from uds.core.managers.CryptoManager import CryptoManager
from uds.core.util.State import State
from uds.models import User
import logging
__updated__ = '2014-02-19'
logger = logging.getLogger(__name__)
authLogger = logging.getLogger('authLog')
USER_KEY = 'uk'
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():
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.getGroups = lambda: []
u.updateLastAccess = 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
Returns the obtained IP, that is always be a valid ip address.
'''
try:
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]
except KeyError:
request.ip = request.META['REMOTE_ADDR']
return request.ip
# Decorator to make easier protect pages that needs to be logged in
def webLoginRequired(view_func):
'''
Decorator to set protection to access page
Look for samples at uds.core.web.views
Look for samples at uds.core.web.views
'''
@wraps(view_func)
def _wrapped_view(request, *args, **kwargs):
@ -95,10 +100,10 @@ def webLoginRequired(view_func):
try:
if user == ROOT_ID:
user = getRootUser()
else:
else:
user = User.objects.get(pk=user)
except User.DoesNotExist:
user = None
user = None
if user is None:
url = request.build_absolute_uri(GlobalConfig.LOGIN_URL.get())
if GlobalConfig.REDIRECT_TO_HTTPS.getBool() is True:
@ -106,17 +111,18 @@ def webLoginRequired(view_func):
logger.debug('No user found, redirecting to {0}'.format(url))
return HttpResponseRedirect(url)
# Refresh session duration
#request.session.set_expiry(GlobalConfig.USER_SESSION_LENGTH.getInt())
# request.session.set_expiry(GlobalConfig.USER_SESSION_LENGTH.getInt())
request.user = user
getIp(request)
return view_func(request, *args, **kwargs)
return _wrapped_view
# Decorator to protect pages that needs to be accessed from "trusted sites"
def trustedSourceRequired(view_func):
'''
Decorator to set protection to access page
look for sample at uds.dispatchers.pam
look for sample at uds.dispatchers.pam
'''
@wraps(view_func)
def _wrapped_view(request, *args, **kwargs):
@ -139,17 +145,17 @@ def __registerUser(authenticator, authInstance, username):
'''
username = authInstance.transformUsername(username)
logger.debug('Transformed username: {0}'.format(username))
usr = authenticator.getOrCreateUser(username, authInstance.getRealName(username))
if usr is not None and State.isActive(usr.state):
# Now we update database groups for this user
usr.getManager().recreateGroups(usr)
return usr
return None
def authenticate(username, password, authenticator, useInternalAuthenticate = False):
return None
def authenticate(username, password, authenticator, useInternalAuthenticate=False):
'''
Given an username, password and authenticator, try to authenticate user
@param username: username to authenticate
@ -157,41 +163,41 @@ def authenticate(username, password, authenticator, useInternalAuthenticate = Fa
@param authenticator: Authenticator (database object) used to authenticate with provided credentials
@param useInternalAuthenticate: If True, tries to authenticate user using "internalAuthenticate". If false, it uses "authenticate".
This is so because in some situations we may want to use a "trusted" method (internalAuthenticate is never invoked directly from web)
@return: None if authentication fails, User object (database object) if authentication is o.k.
@return: None if authentication fails, User object (database object) if authentication is o.k.
'''
logger.debug('Authenticating user {0} with authenticator {1}'.format(username, authenticator))
# If global root auth is enabled && user/password is correct,
# If global root auth is enabled && user/password is correct,
if GlobalConfig.SUPER_USER_ALLOW_WEBACCESS.getBool(True) and username == GlobalConfig.SUPER_USER_LOGIN.get(True) and password == GlobalConfig.SUPER_USER_PASS.get(True):
return getRootUser()
gm = auths.GroupsManager(authenticator)
authInstance = authenticator.getInstance()
if useInternalAuthenticate is False:
res = authInstance.authenticate(username, password, gm)
else:
res = authInstance.internalAuthenticate(username, password, gm)
if res is False:
return None
logger.debug('Groups manager: {0}'.format(gm))
# If do not have any valid group
if gm.hasValidGroups() is False:
return None
return __registerUser(authenticator, authInstance, username)
def authenticateViaCallback(authenticator, params):
'''
Given an username, this method will get invoked whenever the url for a callback
for an authenticator is requested.
The idea behind this is that, with authenticators that are based on url redirections
(SSO auths), we provide a mechanism to allow the authenticator to login the user.
This will:
* Check that the authenticator supports a callback, raise an error if it
doesn't support it.
@ -205,18 +211,19 @@ def authenticateViaCallback(authenticator, params):
'''
gm = auths.GroupsManager(authenticator)
authInstance = authenticator.getInstance()
# If there is no callback for this authenticator...
if authInstance.authCallback == auths.Authenticator.authCallback:
raise auths.Exceptions.InvalidAuthenticatorException()
username = authInstance.authCallback(params, gm)
if username is None or username == '' or gm.hasValidGroups() is False:
raise auths.Exceptions.InvalidUserException('User don\'t has access to UDS')
return __registerUser(authenticator, authInstance, username)
def authCallbackUrl(authenticator):
'''
Helper method, so we can get the auth call back url for an authenticator
@ -224,26 +231,28 @@ def authCallbackUrl(authenticator):
from django.core.urlresolvers import reverse
return reverse('uds.web.views.authCallback', kwargs={'authName': authenticator.name})
def authInfoUrl(authenticator):
'''
Helper method, so we can get the info url for an authenticator
'''
from django.core.urlresolvers import reverse
if isinstance(authenticator,unicode) or isinstance(authenticator, str):
if isinstance(authenticator, unicode) or isinstance(authenticator, str):
name = authenticator
else:
name = authenticator.name
return reverse('uds.web.views.authInfo', kwargs={'authName': name})
def webLogin(request, response, user, password):
'''
Helper function to, once the user is authenticated, store the information at the user session.
@return: Always returns True
'''
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
else:
manager_id = -1
@ -262,11 +271,12 @@ def webPassword(request):
session (db) and client browser cookies. This method uses this two values to recompose the user password
so we can provide it to remote sessions.
@param request: DJango Request
@return: Unscrambled user password
@return: Unscrambled user password
'''
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
by django in regular basis.
@ -278,29 +288,29 @@ def webLogout(request, exit_url = None):
# Try to delete session
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
'''
if logStr == '':
logStr = 'Logged in'
javaStr = java and 'Java' or 'No Java'
authLogger.info('|'.join([authenticator.name, userName, javaStr, os['OS'], logStr, request.META['HTTP_USER_AGENT']]))
level = (logStr == 'Logged in') and log.INFO or log.ERROR
log.doLog(authenticator, level, 'user {0} has {1} from {2} {3} java and os is {4}'.format(userName, logStr,
request.ip, java and 'has' or 'has NOT', os['OS']), log.WEB)
try:
user = authenticator.users.get(name=userName)
log.doLog(user, level, '{0} from {1} {2} java and os is {3}'.format(logStr,
request.ip, java and 'has' or 'has NOT', os['OS']), log.WEB)
except:
pass
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, log.INFO, 'has logged out from {0}'.format(request.ip), log.WEB)

View File

@ -7,67 +7,70 @@ Author:
from django.db import models, connection
import logging
__updated__ = '2014-02-19'
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
class LockingManager(models.Manager):
""" Add lock/unlock functionality to manager.
Example::
class Job(models.Model):
manager = LockingManager()
counter = models.IntegerField(null=True, default=0)
@staticmethod
def do_atomic_update(job_id)
''' Updates job integer, keeping it below 5 '''
try:
# Ensure only one HTTP request can do this update at once.
Job.objects.lock()
job = Job.object.get(id=job_id)
# If we don't lock the tables two simultanous
# requests might both increase the counter
# going over 5
if job.counter < 5:
job.counter += 1
job.counter += 1
job.save()
finally:
Job.objects.unlock()
"""
"""
def lock(self):
""" Lock table.
""" Lock table.
Locks the object model table so that atomic update is possible.
Simulatenous database access request pend until the lock is unlock()'ed.
Note: If you need to lock multiple tables, you need to do lock them
all in one SQL clause and this function is not enough. To avoid
dead lock, all tables must be locked in the same order.
See http://dev.mysql.com/doc/refman/5.0/en/lock-tables.html
"""
"""
con = connection
cursor = con.cursor()
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)
row = cursor.fetchone()
return row
def unlock(self):
""" Unlock the table. """
#logger.debug("Unlocked tables")
# logger.debug("Unlocked tables")
con = connection
cursor = con.cursor()
#table = self.model._meta.db_table
# table = self.model._meta.db_table
cursor.execute("UNLOCK TABLES")
row = cursor.fetchone()
return row
return row

View File

@ -3,30 +3,31 @@
# Copyright (c) 2013 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
__updated__ = '2014-02-19'

View File

@ -4,52 +4,55 @@
# Copyright (c) 2012 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
from uds.core.Environment import Environmentable
import logging
__updated__ = '2014-02-19'
logger = logging.getLogger(__name__)
class DelayedTask(Environmentable):
def __init__(self):
'''
Remember to invoke parent init in derived clases using super(myClass,self).__init__() to let this initialize its own variables
'''
Environmentable.__init__(self, None)
def execute(self):
try:
self.run()
except Exception, e:
logger.error('Job {0} raised an exception: {1}'.format(self.__class__, e))
def run(self):
'''
You must provide your own "run" method to do whatever you need
@ -61,8 +64,8 @@ class DelayedTask(Environmentable):
Utility method that allows to register a Delayedtask
'''
from DelayedTaskRunner import DelayedTaskRunner
if check is True and DelayedTaskRunner.runner().checkExists(tag):
return
DelayedTaskRunner.runner().insert(self, suggestedTime, tag)

View File

@ -4,27 +4,27 @@
# Copyright (c) 2012 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
@ -35,46 +35,50 @@ from __future__ import unicode_literals
from django.db import transaction
from django.db.models import Q
from uds.models import DelayedTask as dbDelayedTask, getSqlDatetime
from uds.core.util.Decorators import retryOnException
from ..Environment import Environment
from socket import gethostname
from pickle import loads, dumps
from datetime import datetime, timedelta
import threading, time
from datetime import timedelta
import threading
import time
import logging
__updated__ = '2014-02-19'
logger = logging.getLogger(__name__)
class DelayedTaskThread(threading.Thread):
def __init__(self, taskInstance):
super(DelayedTaskThread,self).__init__()
super(DelayedTaskThread, self).__init__()
self._taskInstance = taskInstance
def run(self):
try:
self._taskInstance.execute()
except Exception, e:
logger.debug("Exception in thread {0}: {1}".format(e.__class__, e))
class DelayedTaskRunner(object):
CODEC = 'base64' # Can be zip, hez, bzip, base64, uuencoded
# How often tasks r checked
granularity = 2
# to keep singleton DelayedTaskRunner
_runner = None
def __init__(self):
logger.debug("Initializing delayed task runner")
self._hostname = gethostname()
self._keepRunning = True
def notifyTermination(self):
self._keepRunning = False
@staticmethod
def runner():
if DelayedTaskRunner._runner == None:
if DelayedTaskRunner._runner == None:
DelayedTaskRunner._runner = DelayedTaskRunner()
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)
taskInstance = None
try:
with transaction.atomic(): # Encloses
with transaction.atomic(): # Encloses
task = dbDelayedTask.objects.select_for_update().filter(filt).order_by('execution_time')[0]
task.delete()
taskInstance = loads(task.instance.decode(self.CODEC))
@ -92,7 +96,7 @@ class DelayedTaskRunner(object):
# Transaction have been rolled back using the "with atomic", so here just return
# Note that is taskInstance can't be loaded, this task will not be retried
return
if taskInstance != None:
env = Environment.getEnvForType(taskInstance.__class__)
taskInstance.setEnv(env)
@ -100,17 +104,17 @@ class DelayedTaskRunner(object):
def __insert(self, instance, delay, tag):
now = getSqlDatetime()
exec_time = now + timedelta(seconds = delay)
exec_time = now + timedelta(seconds=delay)
cls = instance.__class__
instanceDump = dumps(instance).encode(self.CODEC)
typeName = str(cls.__module__ + '.' + cls.__name__)
logger.debug('Inserting delayed task {0} with {1} bytes ({2})'.format(typeName, len(instanceDump), exec_time))
dbDelayedTask.objects.create(type = typeName, instance = instanceDump,
insert_date = now, execution_delay = delay, execution_time = exec_time, tag = tag)
def insert(self, instance, delay, tag = ''):
logger.debug('Inserting delayed task {0} with {1} bytes ({2})'.format(typeName, len(instanceDump), exec_time))
dbDelayedTask.objects.create(type=typeName, instance=instanceDump,
insert_date=now, execution_delay=delay, execution_time=exec_time, tag=tag)
def insert(self, instance, delay, tag=''):
retries = 3
while retries > 0:
retries -= 1
@ -119,29 +123,29 @@ class DelayedTaskRunner(object):
break
except Exception, 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:
logger.error("Could not insert delayed task!!!! {0} {1} {2}".format(instance, delay, tag))
return False
return True
@transaction.atomic
def remove(self, tag):
try:
dbDelayedTask.objects.select_for_update().filter(tag=tag).delete()
except Exception as e:
logger.exception('Exception removing a delayed task {0}: {1}'.format(str(e.__class__), e))
def checkExists(self, tag):
if tag == '' or tag is None:
return False
number = 0
try:
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))
return number > 0

View File

@ -4,59 +4,63 @@
# Copyright (c) 2012 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
from uds.core import Environmentable
import logging
__updated__ = '2014-02-19'
logger = logging.getLogger(__name__)
class Job(Environmentable):
# 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
frecuency = 24*3600+3
frecuency = 24 * 3600 + 3
friendly_name = 'Unknown'
def __init__(self, environment):
'''
Remember to invoke parent init in derived clases using super(myClass,self).__init__(environmnet) if u want to use env(), cache() and storage() methods
'''
Environmentable.__init__(self, environment)
def execute(self):
try:
self.run()
except Exception, e:
except Exception:
logger.exception('Job {0} raised an exception:'.format(self.__class__))
def run(self):
'''
You must provide your own "run" method to do whatever you need
'''
logging.debug("Base run of job called for class")
pass
pass

View File

@ -4,82 +4,86 @@
# Copyright (c) 2012 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
import datetime
import logging
__updated__ = '2014-02-19'
logger = logging.getLogger(__name__)
class JobsFactory(object):
_factory = None
def __init__(self):
self._jobs = {}
@staticmethod
@staticmethod
def factory():
if JobsFactory._factory == None:
if JobsFactory._factory == None:
JobsFactory._factory = JobsFactory()
return JobsFactory._factory
def jobs(self):
return self._jobs
def insert(self, name, type_):
logger.debug('Inserting job {0} of type {1}'.format(name, type_))
try:
self._jobs[name] = type_
except Exception, e:
logger.debug('Exception at insert in JobsFactory: {0}, {1}'.format(e.__class__, e))
def ensureJobsInDatabase(self):
from uds.models import Scheduler, getSqlDatetime
from uds.core.util.State import State
try:
logger.debug('Ensuring that jobs are registered inside database')
for name, type_ in self._jobs.iteritems():
try:
# We use database server datetime
now = getSqlDatetime()
next_ = now
job = Scheduler.objects.create(name = name, frecuency = type_.frecuency, last_execution = now, next_execution = next_, state = State.FOR_EXECUTE)
except Exception: # already exists
next_ = now
job = Scheduler.objects.create(name=name, frecuency=type_.frecuency, last_execution=now, next_execution=next_, state=State.FOR_EXECUTE)
except Exception: # already exists
logger.debug('Already added {0}'.format(name))
job = Scheduler.objects.get(name=name)
job.frecuency = 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);
if job.next_execution > job.last_execution + datetime.timedelta(seconds=type_.frecuency):
job.next_execution = job.last_execution + datetime.timedelta(seconds=type_.frecuency)
job.save()
except Exception, e:
logger.debug('Exception at ensureJobsInDatabase in JobsFactory: {0}, {1}'.format(e.__class__, e))
def lookup(self, typeName):
try:
return self._jobs[typeName]

View File

@ -4,32 +4,33 @@
# Copyright (c) 2012 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
from django.db.models import Q
from django.db import transaction, DatabaseError
@ -37,24 +38,28 @@ from uds.models import Scheduler as dbScheduler, getSqlDatetime, State
from uds.core.jobs.JobsFactory import JobsFactory
from datetime import timedelta
from socket import gethostname
import threading, time
import threading
import time
import logging
__updated__ = '2014-02-19'
logger = logging.getLogger(__name__)
class JobThread(threading.Thread):
def __init__(self, jobInstance, dbJob):
super(JobThread,self).__init__()
super(JobThread, self).__init__()
self._jobInstance = jobInstance
self._dbJobId = dbJob.id
def run(self):
try:
self._jobInstance.execute()
except Exception:
logger.debug("Exception executing job {0}".format(self._dbJobId))
self.jobDone()
def jobDone(self):
done = False
while done is False:
@ -65,33 +70,33 @@ class JobThread(threading.Thread):
# Databases locked, maybe because we are on a multitask environment, let's try again in a while
logger.info('Database access locked... Retrying')
time.sleep(1)
@transaction.atomic
def __updateDb(self):
job = dbScheduler.objects.select_for_update().get(id=self._dbJobId)
job.state = State.FOR_EXECUTE
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
job.save()
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
_scheduler = None
def __init__(self):
self._hostname = gethostname()
self._keepRunning = True
@staticmethod
def scheduler():
if Scheduler._scheduler == None:
if Scheduler._scheduler == None:
Scheduler._scheduler = Scheduler()
return Scheduler._scheduler
def notifyTermination(self):
self._keepRunning = False
@ -101,27 +106,27 @@ class Scheduler(object):
'''
jobInstance = None
try:
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))
now = getSqlDatetime() # Datetimes are based on database server times
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():
# 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__)
job = dbScheduler.objects.select_for_update().filter(filter).order_by('next_execution')[0]
# This params are all set inside fltr (look at __init__)
job = dbScheduler.objects.select_for_update().filter(fltr).order_by('next_execution')[0]
job.state = State.RUNNING
job.owner_server = self._hostname
job.last_execution = now
job.save()
jobInstance = job.getInstance()
if jobInstance == None:
logger.error('Job instance can\'t be resolved for {0}, removing it'.format(job))
job.delete()
return
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:
# Do nothing, there is no jobs for execution
# Do nothing, there is no jobs for execution
return
except DatabaseError:
# Whis will happen whenever a connection error or a deadlock error happens
@ -135,11 +140,10 @@ class Scheduler(object):
'''
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):
# We ensure that the jobs are also in database so we can
# We ensure that the jobs are also in database so we can
logger.debug('Run Scheduler thread')
JobsFactory.factory().ensureJobsInDatabase()
self.releaseOwnShedules()
@ -150,4 +154,3 @@ class Scheduler(object):
self.executeOneJob()
except Exception, e:
logger.exception('Unexpected exception at run loop {0}: {1}'.format(e.__class__, e))

View File

@ -4,27 +4,27 @@
# Copyright (c) 2012 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
@ -32,9 +32,14 @@ UDS jobs related modules
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
from Job import Job
from DelayedTask import DelayedTask
__updated__ = '2014-02-19'
def factory():
from JobsFactory import JobsFactory
return JobsFactory.factory()
return JobsFactory.factory()

View File

@ -93,8 +93,8 @@ class RDPTransport(Transport):
gui.boolToStr(self._allowDrives), gui.boolToStr(self._allowSerials),
self._fixedName, self._fixedPassword, self._fixedDomain ] )
def unmarshal(self, str):
data = str.split('\t')
def unmarshal(self, str_):
data = str_.split('\t')
if data[0] == 'v1':
self._useEmptyCreds = gui.strToBool(data[1])
self._allowSmartcards = gui.strToBool(data[2])

View File

@ -104,8 +104,8 @@ class TSRDPTransport(Transport):
gui.boolToStr(self._allowDrives), gui.boolToStr(self._allowSerials),
self._fixedName, self._fixedPassword, self._fixedDomain, self._tunnelServer, self._tunnelCheckServer ] )
def unmarshal(self, str):
data = str.split('\t')
def unmarshal(self, str_):
data = str_.split('\t')
if data[0] == 'v1':
self._useEmptyCreds = gui.strToBool(data[1])
self._allowSmartcards = gui.strToBool(data[2])