From 89addaf5851445ff19d8f213d2e1ba9fc09d9f0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20G=C3=B3mez?= Date: Wed, 19 Feb 2014 15:17:55 +0000 Subject: [PATCH] More pep8 related fixes, also some refactoring --- .../src/uds/auths/RegexLdap/Authenticator.py | 2 + server/src/uds/auths/Sample/SampleAuth.py | 233 +++++----- server/src/uds/auths/Sample/__init__.py | 33 +- .../src/uds/auths/SimpleLDAP/Authenticator.py | 283 ++++++------ server/src/uds/auths/SimpleLDAP/__init__.py | 31 +- server/src/uds/core/Serializable.py | 2 +- server/src/uds/core/auths/AuthsFactory.py | 50 +- .../src/uds/core/auths/BaseAuthenticator.py | 435 +++++++++--------- server/src/uds/core/auths/Exceptions.py | 41 +- server/src/uds/core/auths/Group.py | 42 +- server/src/uds/core/auths/GroupsManager.py | 85 ++-- server/src/uds/core/auths/User.py | 53 +-- server/src/uds/core/auths/__init__.py | 35 +- server/src/uds/core/auths/auth.py | 128 +++--- server/src/uds/core/db/LockingManager.py | 45 +- server/src/uds/core/db/__init__.py | 29 +- server/src/uds/core/jobs/DelayedTask.py | 39 +- server/src/uds/core/jobs/DelayedTaskRunner.py | 82 ++-- server/src/uds/core/jobs/Job.py | 44 +- server/src/uds/core/jobs/JobsFactory.py | 62 +-- server/src/uds/core/jobs/Scheduler.py | 83 ++-- server/src/uds/core/jobs/__init__.py | 35 +- server/src/uds/transports/RDP/RDPTransport.py | 4 +- .../src/uds/transports/RDP/TSRDPTransport.py | 4 +- 24 files changed, 975 insertions(+), 905 deletions(-) diff --git a/server/src/uds/auths/RegexLdap/Authenticator.py b/server/src/uds/auths/RegexLdap/Authenticator.py index d998dc43..49325a8f 100644 --- a/server/src/uds/auths/RegexLdap/Authenticator.py +++ b/server/src/uds/auths/RegexLdap/Authenticator.py @@ -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 diff --git a/server/src/uds/auths/Sample/SampleAuth.py b/server/src/uds/auths/Sample/SampleAuth.py index b555cca1..1a759295 100644 --- a/server/src/uds/auths/Sample/SampleAuth.py +++ b/server/src/uds/auths/Sample/SampleAuth.py @@ -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 += '{0}
'.format(u.name) - # - #res += '' - #return res - + # + # res += '' + # return res + # I know, this is a bit ugly, but this is just a sample :-) - + res = '

Login name:

' - res +='

Login

' 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' ''' diff --git a/server/src/uds/auths/Sample/__init__.py b/server/src/uds/auths/Sample/__init__.py index 7ed261f4..18e9871e 100644 --- a/server/src/uds/auths/Sample/__init__.py +++ b/server/src/uds/auths/Sample/__init__.py @@ -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' diff --git a/server/src/uds/auths/SimpleLDAP/Authenticator.py b/server/src/uds/auths/SimpleLDAP/Authenticator.py index 97ec2077..9dc9ac2b 100644 --- a/server/src/uds/auths/SimpleLDAP/Authenticator.py +++ b/server/src/uds/auths/SimpleLDAP/Authenticator.py @@ -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")] - \ No newline at end of file diff --git a/server/src/uds/auths/SimpleLDAP/__init__.py b/server/src/uds/auths/SimpleLDAP/__init__.py index 88d874be..2f8091bc 100644 --- a/server/src/uds/auths/SimpleLDAP/__init__.py +++ b/server/src/uds/auths/SimpleLDAP/__init__.py @@ -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' diff --git a/server/src/uds/core/Serializable.py b/server/src/uds/core/Serializable.py index 173653ff..a3ff8512 100644 --- a/server/src/uds/core/Serializable.py +++ b/server/src/uds/core/Serializable.py @@ -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 ''' diff --git a/server/src/uds/core/auths/AuthsFactory.py b/server/src/uds/core/auths/AuthsFactory.py index b28f776e..8cfe1c06 100644 --- a/server/src/uds/core/auths/AuthsFactory.py +++ b/server/src/uds/core/auths/AuthsFactory.py @@ -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 diff --git a/server/src/uds/core/auths/BaseAuthenticator.py b/server/src/uds/core/auths/BaseAuthenticator.py index be71f91b..4b818b4f 100644 --- a/server/src/uds/core/auths/BaseAuthenticator.py +++ b/server/src/uds/core/auths/BaseAuthenticator.py @@ -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 diff --git a/server/src/uds/core/auths/Exceptions.py b/server/src/uds/core/auths/Exceptions.py index 0b7086da..7e324f48 100644 --- a/server/src/uds/core/auths/Exceptions.py +++ b/server/src/uds/core/auths/Exceptions.py @@ -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 ''' - \ No newline at end of file diff --git a/server/src/uds/core/auths/Group.py b/server/src/uds/core/auths/Group.py index e1178abb..55b9620c 100644 --- a/server/src/uds/core/auths/Group.py +++ b/server/src/uds/core/auths/Group.py @@ -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 diff --git a/server/src/uds/core/auths/GroupsManager.py b/server/src/uds/core/auths/GroupsManager.py index ad004ed6..98c4476e 100644 --- a/server/src/uds/core/auths/GroupsManager.py +++ b/server/src/uds/core/auths/GroupsManager.py @@ -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) - - diff --git a/server/src/uds/core/auths/User.py b/server/src/uds/core/auths/User.py index de69f0b6..3743867b 100644 --- a/server/src/uds/core/auths/User.py +++ b/server/src/uds/core/auths/User.py @@ -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 - diff --git a/server/src/uds/core/auths/__init__.py b/server/src/uds/core/auths/__init__.py index 605340f7..80e973bb 100644 --- a/server/src/uds/core/auths/__init__.py +++ b/server/src/uds/core/auths/__init__.py @@ -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() - - diff --git a/server/src/uds/core/auths/auth.py b/server/src/uds/core/auths/auth.py index eb01b1cf..be43aa2a 100644 --- a/server/src/uds/core/auths/auth.py +++ b/server/src/uds/core/auths/auth.py @@ -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) - diff --git a/server/src/uds/core/db/LockingManager.py b/server/src/uds/core/db/LockingManager.py index ea3bb344..0f6f9454 100644 --- a/server/src/uds/core/db/LockingManager.py +++ b/server/src/uds/core/db/LockingManager.py @@ -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 \ No newline at end of file + return row diff --git a/server/src/uds/core/db/__init__.py b/server/src/uds/core/db/__init__.py index 46ba7af2..509a4e8d 100644 --- a/server/src/uds/core/db/__init__.py +++ b/server/src/uds/core/db/__init__.py @@ -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' diff --git a/server/src/uds/core/jobs/DelayedTask.py b/server/src/uds/core/jobs/DelayedTask.py index d58aa8c7..b69ad612 100644 --- a/server/src/uds/core/jobs/DelayedTask.py +++ b/server/src/uds/core/jobs/DelayedTask.py @@ -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) diff --git a/server/src/uds/core/jobs/DelayedTaskRunner.py b/server/src/uds/core/jobs/DelayedTaskRunner.py index aa00b449..602b42ed 100644 --- a/server/src/uds/core/jobs/DelayedTaskRunner.py +++ b/server/src/uds/core/jobs/DelayedTaskRunner.py @@ -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 diff --git a/server/src/uds/core/jobs/Job.py b/server/src/uds/core/jobs/Job.py index e5a8bfeb..77485a07 100644 --- a/server/src/uds/core/jobs/Job.py +++ b/server/src/uds/core/jobs/Job.py @@ -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 \ No newline at end of file + pass diff --git a/server/src/uds/core/jobs/JobsFactory.py b/server/src/uds/core/jobs/JobsFactory.py index 0fbee292..675f22e9 100644 --- a/server/src/uds/core/jobs/JobsFactory.py +++ b/server/src/uds/core/jobs/JobsFactory.py @@ -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] diff --git a/server/src/uds/core/jobs/Scheduler.py b/server/src/uds/core/jobs/Scheduler.py index d62c1a6b..34e94fc3 100644 --- a/server/src/uds/core/jobs/Scheduler.py +++ b/server/src/uds/core/jobs/Scheduler.py @@ -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)) - diff --git a/server/src/uds/core/jobs/__init__.py b/server/src/uds/core/jobs/__init__.py index c1aea440..2a4cb3fd 100644 --- a/server/src/uds/core/jobs/__init__.py +++ b/server/src/uds/core/jobs/__init__.py @@ -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() \ No newline at end of file + return JobsFactory.factory() diff --git a/server/src/uds/transports/RDP/RDPTransport.py b/server/src/uds/transports/RDP/RDPTransport.py index 92df03ed..96fead2d 100644 --- a/server/src/uds/transports/RDP/RDPTransport.py +++ b/server/src/uds/transports/RDP/RDPTransport.py @@ -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]) diff --git a/server/src/uds/transports/RDP/TSRDPTransport.py b/server/src/uds/transports/RDP/TSRDPTransport.py index 766d4f20..d1e2b769 100644 --- a/server/src/uds/transports/RDP/TSRDPTransport.py +++ b/server/src/uds/transports/RDP/TSRDPTransport.py @@ -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])