forked from shaba/openuds
More pep8 related fixes, also some refactoring
This commit is contained in:
parent
dab3e26223
commit
89addaf585
@ -32,6 +32,8 @@
|
||||
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
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
|
||||
|
@ -4,27 +4,27 @@
|
||||
# Copyright (c) 2012 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
'''
|
||||
@ -36,109 +36,111 @@ from uds.core import auths
|
||||
|
||||
import logging
|
||||
|
||||
__updated__ = '2014-02-19'
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SampleAuth(auths.Authenticator):
|
||||
'''
|
||||
This class represents a sample authenticator.
|
||||
|
||||
|
||||
As this, it will provide:
|
||||
* The authenticator functionality
|
||||
* The authenticator functionality
|
||||
* 3 Groups, "Mortals", "Gods" and "Daemons", just random group names selected.. :-),
|
||||
plus groups that we enter at Authenticator form, from admin interface.
|
||||
* Search of groups (inside the 3 groups used in this sample plus entered)
|
||||
* Search for people (will return the search string + 000...999 as usernames)
|
||||
* The Required form description for administration interface, so admins can create
|
||||
new authenticators of this kind.
|
||||
|
||||
new authenticators of this kind.
|
||||
|
||||
In this sample, we will provide a simple standard auth, with owner drawn
|
||||
login form that will simply show users that has been created and allow web user
|
||||
to select one of them.
|
||||
|
||||
|
||||
For this class to get visible at administration client as a authenticator type,
|
||||
we MUST register it at package __init__
|
||||
|
||||
:note: At class level, the translations must be simply marked as so
|
||||
using ugettext_noop. This is done in this way because we will translate
|
||||
using ugettext_noop. This is done in this way because we will translate
|
||||
the string when it is sent to the administration client.
|
||||
'''
|
||||
|
||||
#: Name of type, used at administration interface to identify this
|
||||
#: authenticator (i.e. LDAP, SAML, ...)
|
||||
#: This string will be translated when provided to admin interface
|
||||
#: using ugettext, so you can mark it as "_" at derived classes (using ugettext_noop)
|
||||
#: if you want so it can be translated.
|
||||
|
||||
# : Name of type, used at administration interface to identify this
|
||||
# : authenticator (i.e. LDAP, SAML, ...)
|
||||
# : This string will be translated when provided to admin interface
|
||||
# : using ugettext, so you can mark it as "_" at derived classes (using ugettext_noop)
|
||||
# : if you want so it can be translated.
|
||||
typeName = _('Sample Authenticator')
|
||||
|
||||
#: Name of type used by Managers to identify this type of service
|
||||
#: We could have used here the Class name, but we decided that the
|
||||
#: module implementator will be the one that will provide a name that
|
||||
#: will relation the class (type) and that name.
|
||||
|
||||
# : Name of type used by Managers to identify this type of service
|
||||
# : We could have used here the Class name, but we decided that the
|
||||
# : module implementator will be the one that will provide a name that
|
||||
# : will relation the class (type) and that name.
|
||||
typeType = 'SampleAuthenticator'
|
||||
|
||||
#: Description shown at administration level for this authenticator.
|
||||
#: This string will be translated when provided to admin interface
|
||||
#: using ugettext, so you can mark it as "_" at derived classes (using ugettext_noop)
|
||||
#: if you want so it can be translated.
|
||||
|
||||
# : Description shown at administration level for this authenticator.
|
||||
# : This string will be translated when provided to admin interface
|
||||
# : using ugettext, so you can mark it as "_" at derived classes (using ugettext_noop)
|
||||
# : if you want so it can be translated.
|
||||
typeDescription = _('Sample dummy authenticator')
|
||||
|
||||
|
||||
#: Icon file, used to represent this authenticator at administration interface
|
||||
#: This file should be at same folder as this class is, except if you provide
|
||||
#: your own :py:meth:uds.core.BaseModule.BaseModule.icon method.
|
||||
|
||||
# : Icon file, used to represent this authenticator at administration interface
|
||||
# : This file should be at same folder as this class is, except if you provide
|
||||
# : your own :py:meth:uds.core.BaseModule.BaseModule.icon method.
|
||||
iconFile = 'auth.png'
|
||||
|
||||
#: Mark this authenticator as that the users comes from outside the UDS
|
||||
#: database, that are most authenticator (except Internal DB)
|
||||
#: True is the default value, so we do not need it in fact
|
||||
# isExternalSource = True
|
||||
|
||||
#: If we need to enter the password for this user when creating a new
|
||||
#: user at administration interface. Used basically by internal authenticator.
|
||||
#: False is the default value, so this is not needed in fact
|
||||
#: needsPassword = False
|
||||
|
||||
#: Label for username field, shown at administration interface user form.
|
||||
|
||||
# : Mark this authenticator as that the users comes from outside the UDS
|
||||
# : database, that are most authenticator (except Internal DB)
|
||||
# : True is the default value, so we do not need it in fact
|
||||
# isExternalSource = True
|
||||
|
||||
# : If we need to enter the password for this user when creating a new
|
||||
# : user at administration interface. Used basically by internal authenticator.
|
||||
# : False is the default value, so this is not needed in fact
|
||||
# : needsPassword = False
|
||||
|
||||
# : Label for username field, shown at administration interface user form.
|
||||
userNameLabel = _('Fake User')
|
||||
|
||||
|
||||
# Label for group field, shown at administration interface user form.
|
||||
groupNameLabel = _('Fake Group')
|
||||
|
||||
#: Definition of this type of authenticator form
|
||||
#: We will define a simple form where we will use a simple
|
||||
#: list editor to allow entering a few group names
|
||||
|
||||
groups = gui.EditableList(label=_('Groups'), values = ['Gods', 'Daemons', 'Mortals'])
|
||||
|
||||
|
||||
# : Definition of this type of authenticator form
|
||||
# : We will define a simple form where we will use a simple
|
||||
# : list editor to allow entering a few group names
|
||||
|
||||
groups = gui.EditableList(label=_('Groups'), values=['Gods', 'Daemons', 'Mortals'])
|
||||
|
||||
def initialize(self, values):
|
||||
'''
|
||||
Simply check if we have
|
||||
at least one group in the list
|
||||
'''
|
||||
|
||||
|
||||
# To avoid problems, we only check data if values are passed
|
||||
# If values are not passed in, form data will only be available after
|
||||
# unserialization, and at this point all will be default values
|
||||
# so self.groups.value will be []
|
||||
if values is not None and len(self.groups.value) < 2:
|
||||
raise auths.Authenticator.ValidationException(_('We need more that two items!'))
|
||||
|
||||
|
||||
def searchUsers(self, pattern):
|
||||
'''
|
||||
Here we will receive a pattern for searching users.
|
||||
|
||||
|
||||
This method is invoked from interface, so an administrator can search users.
|
||||
|
||||
|
||||
If we do not provide this method, the authenticator will not provide search
|
||||
facility for users. In our case, we will simply return a list of users
|
||||
(array of dictionaries with ids and names) with the pattern plus 1..10
|
||||
(array of dictionaries with ids and names) with the pattern plus 1..10
|
||||
'''
|
||||
return [ { 'id' : '{0}-{1}'.format(pattern, a), 'name' : '{0} number {1}'.format(pattern, a) } for a in range(1, 10)]
|
||||
|
||||
|
||||
def searchGroups(self, pattern):
|
||||
'''
|
||||
Here we we will receive a patter for searching groups.
|
||||
|
||||
|
||||
In this sample, we will try to locate elements that where entered at
|
||||
sample authenticator form (when created), and return the ones that
|
||||
contains the pattern indicated.
|
||||
@ -149,52 +151,52 @@ class SampleAuth(auths.Authenticator):
|
||||
if g.lower().find(pattern) != -1:
|
||||
res.append({'id' : g, 'name' : ''})
|
||||
return res
|
||||
|
||||
|
||||
def authenticate(self, username, credentials, groupsManager):
|
||||
'''
|
||||
This method is invoked by UDS whenever it needs an user to be authenticated.
|
||||
It is used from web interface, but also from administration interface to
|
||||
check credentials and access of user.
|
||||
|
||||
|
||||
The tricky part of this method is the groupsManager, but it's easy to
|
||||
understand what is used it for.
|
||||
|
||||
|
||||
Imagine some authenticator, for example, an LDAP. It has its users, it has
|
||||
its groups, and it has it relations (which user belongs to which group).
|
||||
|
||||
|
||||
Now think about UDS. UDS know nothing about this, it only knows what
|
||||
the administator has entered at admin interface (groups mainly, but he can
|
||||
create users also).
|
||||
|
||||
|
||||
UDS knows about this groups, but we need to relation those with the ones
|
||||
know by the authenticator.
|
||||
|
||||
know by the authenticator.
|
||||
|
||||
To do this, we have created a simple mechanism, where the authenticator
|
||||
receives a groupsManager, that knows all groups known by UDS, and has
|
||||
the method so the authenticator can say, for the username being validated,
|
||||
to which uds groups it belongs to.
|
||||
|
||||
|
||||
This is done using the :py:meth:uds.core.auths.GroupsManager.GroupsManager.validate
|
||||
method of the provided groups manager.
|
||||
|
||||
|
||||
At return, UDS will do two things:
|
||||
* If there is no group inside the groupsManager mareked as valid, it will
|
||||
denied access.
|
||||
* If there is some groups marked as valid, it will refresh the known
|
||||
UDS relations (this means that the database will be refresehd so the user
|
||||
has valid groups).
|
||||
|
||||
|
||||
This also means that the group membership is only checked at user login (well,
|
||||
in fact its also checked when an administrator tries to modify an user)
|
||||
|
||||
|
||||
So, authenticate must not also validate the user credentials, but also
|
||||
indicate the group membership of this user inside UDS.
|
||||
|
||||
indicate the group membership of this user inside UDS.
|
||||
|
||||
:note: groupsManager is an in/out parameter
|
||||
'''
|
||||
if username != credentials: # All users with same username and password are allowed
|
||||
if username != credentials: # All users with same username and password are allowed
|
||||
return False
|
||||
|
||||
|
||||
# Now the tricky part. We will make this user belong to groups that contains at leat
|
||||
# two letters equals to the groups names known by UDS
|
||||
# For this, we will ask the groups manager for the groups names, and will check that and,
|
||||
@ -202,18 +204,18 @@ class SampleAuth(auths.Authenticator):
|
||||
for g in groupsManager.getGroupsNames():
|
||||
if len(set(g.lower()).intersection(username.lower())) >= 2:
|
||||
groupsManager.validate(g)
|
||||
|
||||
|
||||
return True
|
||||
|
||||
def getGroups(self, username, groupsManager):
|
||||
'''
|
||||
As with authenticator part related to groupsManager, this
|
||||
method will fill the groups to which the specified username belongs to.
|
||||
|
||||
|
||||
We have to fill up groupsManager from two different places, so it's not
|
||||
a bad idea to make a method that get the "real" authenticator groups and
|
||||
them simply call to :py:meth:uds.core.auths.GroupsManager.GroupsManager.validate
|
||||
|
||||
|
||||
In our case, we simply repeat the process that we also do at authenticate
|
||||
'''
|
||||
for g in groupsManager.getGroupsNames():
|
||||
@ -224,82 +226,81 @@ class SampleAuth(auths.Authenticator):
|
||||
'''
|
||||
If we override this method from the base one, we are telling UDS
|
||||
that we want to draw our own authenticator.
|
||||
|
||||
|
||||
This way, we can do whataver we want here (for example redirect to a site
|
||||
for a single sign on) generation our ouwn html (and javascript ofc).
|
||||
|
||||
|
||||
'''
|
||||
# Here there is a sample, commented out
|
||||
# In this sample, we will make a list of valid users, and when clicked,
|
||||
# it will fill up original form with username and same password, and submit it.
|
||||
#res = ''
|
||||
#for u in self.dbAuthenticator().users.all():
|
||||
# res = ''
|
||||
# for u in self.dbAuthenticator().users.all():
|
||||
# res += '<a class="myNames" id="{0}" href="">{0}</a><br/>'.format(u.name)
|
||||
#
|
||||
#res += '<script type="text/javascript">$(".myNames").click(function() { '
|
||||
#res += '$("#id_user").val(this.id); $("#id_password").val(this.id); $("#loginform").submit(); return false;});</script>'
|
||||
#return res
|
||||
|
||||
#
|
||||
# res += '<script type="text/javascript">$(".myNames").click(function() { '
|
||||
# res += '$("#id_user").val(this.id); $("#id_password").val(this.id); $("#loginform").submit(); return false;});</script>'
|
||||
# return res
|
||||
|
||||
# I know, this is a bit ugly, but this is just a sample :-)
|
||||
|
||||
|
||||
res = '<p>Login name: <input id="logname" type="text"/></p>'
|
||||
res +='<p><a href="" onclick="window.location.replace(\'' + self.callbackUrl() + '?user='
|
||||
res += '<p><a href="" onclick="window.location.replace(\'' + self.callbackUrl() + '?user='
|
||||
res += '\' + $(\'#logname\').val()); return false;">Login</a></p>'
|
||||
return res
|
||||
|
||||
|
||||
|
||||
def authCallback(self, parameters, gm):
|
||||
'''
|
||||
We provide this as a sample of callback for an user.
|
||||
We will accept all petitions that has "user" parameter
|
||||
|
||||
|
||||
This method will get invoked by url redirections, probably by an SSO.
|
||||
|
||||
|
||||
The idea behind this is that we can provide:
|
||||
* Simple user/password authentications
|
||||
* Own authentications (not UDS, authenticator "owned"), but with no redirections
|
||||
* Own authentications via redirections (as most SSO will do)
|
||||
|
||||
|
||||
Here, we will receive the parameters for this
|
||||
'''
|
||||
user = parameters.get('user', None)
|
||||
|
||||
|
||||
return user
|
||||
|
||||
def createUser(self, usrData):
|
||||
'''
|
||||
This method provides a "check oportunity" to authenticators for users created
|
||||
manually at administration interface.
|
||||
|
||||
|
||||
If we do not provide this method, the administration interface will not allow
|
||||
to create new users "by hand", i mean, the "new" options from menus will dissapear.
|
||||
|
||||
usrData is a dictionary that contains the input parameters from user,
|
||||
|
||||
usrData is a dictionary that contains the input parameters from user,
|
||||
with at least name, real_name, comments, state & password.
|
||||
|
||||
We can modify this parameters, we can modify ALL, but name is not recommended to
|
||||
|
||||
We can modify this parameters, we can modify ALL, but name is not recommended to
|
||||
modify it unles you know what you are doing.
|
||||
|
||||
|
||||
Here, we will set the state to "Inactive" and realName to the same as username, but twice :-)
|
||||
'''
|
||||
from uds.core.util.State import State
|
||||
usrData['real_name'] = usrData['name'] + ' ' + usrData['name']
|
||||
usrData['state'] = State.INACTIVE
|
||||
|
||||
|
||||
def modifyUser(self, usrData):
|
||||
'''
|
||||
This method provides a "check opportunity" to authenticator for users modified
|
||||
at administration interface.
|
||||
|
||||
|
||||
If we do not provide this method, nothing will happen (default one does nothing, but
|
||||
it's valid).
|
||||
|
||||
usrData is a dictionary that contains the input parameters from user,
|
||||
|
||||
usrData is a dictionary that contains the input parameters from user,
|
||||
with at least name, real_name, comments, state & password.
|
||||
|
||||
We can modify this parameters, we can modify ALL, but name is not recommended to
|
||||
|
||||
We can modify this parameters, we can modify ALL, but name is not recommended to
|
||||
modify it unless you know what you are doing.
|
||||
|
||||
|
||||
Here, we will simply update the realName of the user, and (we have to take care
|
||||
this this kind of things) modify the userName to a new one, the original plus '-1'
|
||||
'''
|
||||
|
@ -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'
|
||||
|
@ -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")]
|
||||
|
@ -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'
|
||||
|
@ -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
|
||||
'''
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
'''
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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()
|
||||
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -7,67 +7,70 @@ Author:
|
||||
from django.db import models, connection
|
||||
import logging
|
||||
|
||||
__updated__ = '2014-02-19'
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# Table locking in mysql at least is based on thread requesting it, so we should not have problems with a bit of care
|
||||
class LockingManager(models.Manager):
|
||||
""" Add lock/unlock functionality to manager.
|
||||
|
||||
|
||||
Example::
|
||||
|
||||
|
||||
class Job(models.Model):
|
||||
|
||||
|
||||
manager = LockingManager()
|
||||
|
||||
|
||||
counter = models.IntegerField(null=True, default=0)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def do_atomic_update(job_id)
|
||||
''' Updates job integer, keeping it below 5 '''
|
||||
try:
|
||||
# Ensure only one HTTP request can do this update at once.
|
||||
Job.objects.lock()
|
||||
|
||||
|
||||
job = Job.object.get(id=job_id)
|
||||
# If we don't lock the tables two simultanous
|
||||
# requests might both increase the counter
|
||||
# going over 5
|
||||
if job.counter < 5:
|
||||
job.counter += 1
|
||||
job.counter += 1
|
||||
job.save()
|
||||
|
||||
|
||||
finally:
|
||||
Job.objects.unlock()
|
||||
|
||||
|
||||
"""
|
||||
|
||||
|
||||
"""
|
||||
|
||||
def lock(self):
|
||||
""" Lock table.
|
||||
|
||||
""" Lock table.
|
||||
|
||||
Locks the object model table so that atomic update is possible.
|
||||
Simulatenous database access request pend until the lock is unlock()'ed.
|
||||
|
||||
|
||||
Note: If you need to lock multiple tables, you need to do lock them
|
||||
all in one SQL clause and this function is not enough. To avoid
|
||||
dead lock, all tables must be locked in the same order.
|
||||
|
||||
|
||||
See http://dev.mysql.com/doc/refman/5.0/en/lock-tables.html
|
||||
"""
|
||||
"""
|
||||
con = connection
|
||||
cursor = con.cursor()
|
||||
table = self.model._meta.db_table
|
||||
#logger.debug("Locking table %s" % table)
|
||||
# logger.debug("Locking table %s" % table)
|
||||
cursor.execute("LOCK TABLES %s WRITE" % table)
|
||||
row = cursor.fetchone()
|
||||
return row
|
||||
|
||||
|
||||
def unlock(self):
|
||||
""" Unlock the table. """
|
||||
#logger.debug("Unlocked tables")
|
||||
# logger.debug("Unlocked tables")
|
||||
con = connection
|
||||
cursor = con.cursor()
|
||||
#table = self.model._meta.db_table
|
||||
# table = self.model._meta.db_table
|
||||
cursor.execute("UNLOCK TABLES")
|
||||
row = cursor.fetchone()
|
||||
return row
|
||||
return row
|
||||
|
@ -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'
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -4,59 +4,63 @@
|
||||
# Copyright (c) 2012 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from uds.core import Environmentable
|
||||
import logging
|
||||
|
||||
__updated__ = '2014-02-19'
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Job(Environmentable):
|
||||
# Default frecuency, once a day. Remenber that precision will be based on "granurality" of Scheduler
|
||||
# If a job is used for delayed execution, this attribute is in fact ignored
|
||||
frecuency = 24*3600+3
|
||||
frecuency = 24 * 3600 + 3
|
||||
friendly_name = 'Unknown'
|
||||
|
||||
|
||||
def __init__(self, environment):
|
||||
'''
|
||||
Remember to invoke parent init in derived clases using super(myClass,self).__init__(environmnet) if u want to use env(), cache() and storage() methods
|
||||
'''
|
||||
Environmentable.__init__(self, environment)
|
||||
|
||||
|
||||
def execute(self):
|
||||
try:
|
||||
self.run()
|
||||
except Exception, e:
|
||||
except Exception:
|
||||
logger.exception('Job {0} raised an exception:'.format(self.__class__))
|
||||
|
||||
|
||||
def run(self):
|
||||
'''
|
||||
You must provide your own "run" method to do whatever you need
|
||||
'''
|
||||
logging.debug("Base run of job called for class")
|
||||
pass
|
||||
pass
|
||||
|
@ -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]
|
||||
|
@ -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))
|
||||
|
||||
|
@ -4,27 +4,27 @@
|
||||
# Copyright (c) 2012 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
'''
|
||||
@ -32,9 +32,14 @@ UDS jobs related modules
|
||||
|
||||
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from Job import Job
|
||||
from DelayedTask import DelayedTask
|
||||
|
||||
__updated__ = '2014-02-19'
|
||||
|
||||
|
||||
def factory():
|
||||
from JobsFactory import JobsFactory
|
||||
return JobsFactory.factory()
|
||||
return JobsFactory.factory()
|
||||
|
@ -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])
|
||||
|
@ -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])
|
||||
|
Loading…
x
Reference in New Issue
Block a user